/*
 * BLTZ visual language, in CSS.
 *
 * Consumes packages/tokens/dist/tokens.css and never defines color,
 * duration or size on its own. If a value is not in the tokens, it goes
 * into the tokens first. See LANGUAGE.md alongside this file.
 *
 * Dark only. There is no light theme, by decision.
 *
 * Every layout number below is mirrored in table/geometry.ts, block by
 * block, and the 21 collision tests assert on that mirror. Move a number
 * here and the model has to move with it, or the tests pass against a
 * table nobody is looking at.
 */

:root {
  color-scheme: dark;
}

html {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--font-weight-body);
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-optical-sizing: auto;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Every money figure. Non negotiable. Set in the identity face, heavy and
   condensed, with tabular figures so a rolling digit never changes width. */
.num {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-stretch: var(--font-stretch-display);
  letter-spacing: var(--font-tracking-display);
  /* The thousands separator is a thin space and a thin space is a break
     opportunity. A money figure that wraps is a money figure misread. */
  white-space: nowrap;
}

.display {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-display);
  letter-spacing: var(--font-tracking-display);
}

.label {
  font-family: var(--font-display);
  font-size: var(--step-n2);
  font-weight: var(--font-weight-label);
  letter-spacing: var(--font-tracking-label);
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- ground */

/* The table's ground. The class survives from the felt era because the DOM
   still writes it, and what it paints now is nothing: the ground colour,
   flat. No gradient, no grain, no rail. The arena draws its own surface. */
.felt {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ground);
}

/* ----------------------------------------------------------------- card */

.card {
  --w: var(--space-card-board);
  position: relative;
  inline-size: var(--w);
  aspect-ratio: var(--space-card-ratio);
  /* The card radius is capped at a tenth of the width so a 32 px card does
     not turn into a pill. */
  border-radius: min(var(--space-radius-card), calc(var(--w) * .1));
  background: var(--card-face);
  box-shadow:
    inset 0 0 0 1px var(--card-edge),
    0 4px 12px rgba(0, 0, 0, .4);
  color: var(--suit, var(--suit-spades));
  container-type: inline-size;
  container-name: card;
  overflow: hidden;
  flex: none;
  user-select: none;
}
.card[data-s="c"] { --suit: var(--suit-clubs); }
.card[data-s="d"] { --suit: var(--suit-diamonds); }
.card[data-s="h"] { --suit: var(--suit-hearts); }
.card[data-s="s"] { --suit: var(--suit-spades); }

/* Two color deck is an option. Spades and clubs in black, hearts and diamonds in red. */
[data-deck="two"] .card[data-s="c"] { --suit: var(--suit-spades); }
[data-deck="two"] .card[data-s="d"] { --suit: var(--suit-hearts); }

.card > .rank,
.card > .pip,
.card > .art {
  position: absolute;
  line-height: 1;
}
/* Index in the identity face, condensed and heavy, so the card and the
   interface speak with one voice. Does not scale linearly: the smaller the
   card, the bigger the rank relative to it, and the corner art disappears.
   Mirrored in card.ts TIERS, which has no gate, so move both. */
.card > .rank {
  top: 6cqw;
  left: 9cqw;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-stretch: 78%;
  font-size: 38cqw;
  letter-spacing: -.04em;
}
.card > .pip {
  top: 46cqw;
  left: 10cqw;
  font-size: 24cqw;
}
.card > .art {
  right: 8cqw;
  bottom: 6cqw;
  font-size: 44cqw;
  opacity: .95;
}
@container card (max-width: 59px) {
  .card > .rank { font-size: 42cqw; top: 5cqw; left: 8cqw; }
  .card > .pip { font-size: 26cqw; top: 50cqw; }
  .card > .art { font-size: 40cqw; opacity: .85; }
}
@container card (max-width: 40px) {
  .card > .rank { font-size: 50cqw; top: 14cqw; left: 50%; translate: -50% 0; }
  .card > .pip { font-size: 34cqw; top: auto; bottom: 12cqw; left: 50%; translate: -50% 0; }
  .card > .art { display: none; }
}

/* Back. The one printed object on the table. A diagonal rule in the accent
   at a tenth of its strength, and an inner frame. Gradient as material on a
   single object is allowed, gradient as background is not. */
.card.back {
  background:
    repeating-linear-gradient(135deg, var(--card-back-line) 0 2px, transparent 2px 9px),
    var(--card-back);
  box-shadow:
    inset 0 0 0 1px var(--line),
    0 4px 12px rgba(0, 0, 0, .4);
}
.card.back::after {
  content: "";
  position: absolute;
  inset: 10cqw;
  border-radius: calc(var(--space-radius-card) * .7);
  box-shadow: inset 0 0 0 1.5px var(--volt-line);
  opacity: .65;
}

/* Canonical sizes. */
.card.hero { --w: var(--space-card-hero); }
.card.board { --w: var(--space-card-board); }
.card.opp { --w: var(--space-card-opponent); }

/* Fanned cards, a two card hand. The first card leans back by the resting
   tilt and the second fans over it. A hand square to the table is the tell
   of a rendering, not of a dealt hand. The canvas reads the tilt off the
   ghost card, so the value written here is the value drawn. */
.hand {
  display: flex;
}
.hand > .card:first-child {
  rotate: calc(-1 * var(--space-card-tilt));
  transform-origin: 40% 100%;
}
.hand > .card + .card {
  margin-inline-start: calc(var(--w) * -.38);
  rotate: 7deg;
  transform-origin: 20% 100%;
}
/* Board cards land one at a time and never perfectly square. Half the
   resting tilt, alternating, is enough to read as dealt and not as pasted. */
.board > .card:nth-child(odd) { rotate: calc(var(--space-card-tilt) * -.5); }
.board > .card:nth-child(even) { rotate: calc(var(--space-card-tilt) * .5); }

/* Showdown. The five used gain the accent edge, the others drop. */
.card.used {
  box-shadow:
    inset 0 0 0 2px var(--volt),
    0 4px 12px rgba(0, 0, 0, .4);
}
.card.dead {
  opacity: var(--alpha-out);
}

/* ----------------------------------------------------------------- chip */

/* Flat, seen from above, matte. Six edge inserts, the way a clay chip has
   them. Never in perspective, never floating. Mirrored in chip.ts. */
.chip {
  --s: 26px;
  --fill: var(--chip-1-fill);
  --ring: var(--chip-1-ring);
  --inkc: var(--chip-1-ink);
  position: relative;
  inline-size: var(--s);
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: none;
  color: var(--inkc);
  font-family: var(--font-display);
  font-style: normal;
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-stretch: 80%;
  font-size: calc(var(--s) * .34);
  letter-spacing: -.02em;
  line-height: 1;
  background:
    radial-gradient(circle, var(--fill) 0 60%, transparent 61%),
    repeating-conic-gradient(from -11deg, var(--ring) 0 22deg, var(--fill) 22deg 60deg);
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, .22),
    0 1px 0 rgba(0, 0, 0, .45),
    0 0 0 1px rgba(255, 255, 255, .04);
}
.chip[data-v="1"]    { --fill: var(--chip-1-fill);    --ring: var(--chip-1-ring);    --inkc: var(--chip-1-ink); }
.chip[data-v="5"]    { --fill: var(--chip-5-fill);    --ring: var(--chip-5-ring);    --inkc: var(--chip-5-ink); }
.chip[data-v="10"]   { --fill: var(--chip-10-fill);   --ring: var(--chip-10-ring);   --inkc: var(--chip-10-ink); }
.chip[data-v="25"]   { --fill: var(--chip-25-fill);   --ring: var(--chip-25-ring);   --inkc: var(--chip-25-ink); }
.chip[data-v="100"]  { --fill: var(--chip-100-fill);  --ring: var(--chip-100-ring);  --inkc: var(--chip-100-ink); }
.chip[data-v="500"]  { --fill: var(--chip-500-fill);  --ring: var(--chip-500-ring);  --inkc: var(--chip-500-ink); }
.chip[data-v="1000"] { --fill: var(--chip-1000-fill); --ring: var(--chip-1000-ring); --inkc: var(--chip-1000-ink); }

/* Bet on the table. A pill: the chips, then the value, and the value is
   what carries it. 22 px tall, and the revealed choice tile is the same
   height because the two share one grid area and the seat box must not
   change when one replaces the other. */
.bet {
  --h: 22px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  block-size: var(--h);
  padding: 0 8px 0 2px;
  border-radius: var(--space-radius-small);
  background: var(--surface-raised);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: var(--font-weight-label);
  font-stretch: var(--font-stretch-display);
  font-size: var(--step-n1);
  letter-spacing: var(--font-tracking-display);
  line-height: 1;
}
.bet > .chips {
  display: flex;
}
.bet > .chips > .chip { --s: 18px; font-size: 0; }
.bet > .chips > .chip + .chip { margin-inline-start: -8px; }

/* ----------------------------------------------------------------- seat */

/* The slot is the container. The seat adapts to what the slot gave it. */
.slot {
  container-type: inline-size;
  container-name: seat;
  inline-size: var(--slot-w, 112px);
  flex: none;
}

/*
 * The seat. A horizontal pod, avatar tile on the left, name over stack on
 * the right, and the bet pill hanging under it. The pod is painted by the
 * ::before so the DOM needs no wrapper: it covers the first grid row plus
 * the padding, which with a 36 px avatar and 4 px of padding is exactly one
 * touch target tall.
 *
 * The state ring is a border on that same ::before, inside the box. The
 * measured box is the visible box, and a ring drawn outside it would touch
 * a neighbour the collision tests had cleared.
 *
 * The row gap is the pad plus four, because the pod's lower padding sits
 * inside the gap and the four is the air between the pod and the pill.
 *
 * Whole box: 4 + 36 + 8 + 22. Measured in the browser, not added up, and
 * written into geometry.ts seatHeight().
 */
.seat {
  position: relative;
  --face: var(--space-seat-avatar);
  --pad: 4px;
  --pod-h: calc(var(--face) + 2 * var(--pad));
  --ring-w: var(--space-ring);
  --ring-c: var(--line-strong);
  --ring-style: solid;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "avatar meta"
    "bet bet";
  column-gap: 8px;
  row-gap: calc(var(--pad) + 4px);
  align-items: center;
  inline-size: 100%;
  max-inline-size: 100%;
  padding: var(--pad) 10px 0 var(--pad);
  overflow: hidden;
  color: var(--ink);
  transition:
    opacity var(--motion-state-swap) var(--motion-ease-out),
    scale var(--motion-state-swap) var(--motion-ease-out);
}
.seat::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  block-size: var(--pod-h);
  border-radius: var(--space-radius-small);
  background: var(--surface-pod);
  border: var(--ring-w) var(--ring-style) var(--ring-c);
  transition: border-color var(--motion-state-swap) var(--motion-ease-out);
  pointer-events: none;
}
.seat > .avatar {
  grid-area: avatar;
  position: relative;
  inline-size: var(--face);
  aspect-ratio: 1;
  border-radius: calc(var(--space-radius-small) - 2px);
  display: grid;
  place-items: center;
  flex: none;
  /* A flat hue per seat position, set on the slot below. Not a gradient:
     a tile is one colour, and the initials are the only thing on it. */
  background: hsl(var(--h, 220) 30% 30%);
  transition:
    filter var(--motion-state-swap) var(--motion-ease-out),
    opacity var(--motion-state-swap) var(--motion-ease-out);
}
.seat > .avatar > .face {
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-stretch: 85%;
  font-size: calc(var(--face) * .4);
  letter-spacing: -.01em;
  line-height: 1;
}
/* The mark. A glyph per state, in a mask, on the tile's corner and inside
   the pod, for peripheral reading. */
.seat > .avatar > .badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  inline-size: 14px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--surface-pressed);
  box-shadow: 0 0 0 2px var(--surface-pod);
  display: none;
  place-items: center;
}
.seat > .avatar > .badge::before {
  content: "";
  inline-size: 64%;
  aspect-ratio: 1;
  background: var(--badge-c, var(--ink));
  -webkit-mask: var(--badge-svg) center / contain no-repeat;
  mask: var(--badge-svg) center / contain no-repeat;
}

.seat > .meta {
  grid-area: meta;
  /* Positioned so it paints above the pod's ::before. In flow it painted
     under it, and every name and stack vanished the moment the surface
     token resolved. */
  position: relative;
  display: grid;
  line-height: 1.15;
  min-inline-size: 0;
}
.seat .name {
  font-family: var(--font-display);
  font-size: var(--step-n1);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.seat .stack {
  font-size: var(--step-n1);
  font-weight: 600;
  color: var(--ink-muted);
}
.seat .stack::after {
  content: attr(data-long);
}
.seat > .bet,
.seat > .choice {
  grid-area: bet;
  justify-self: start;
  white-space: nowrap;
}
/* What a seat shows at showdown.
   Absolute, and inside the seat's own box, because the ring is sized for the
   seat as it looks during the hand. A seat that grows a card row at the end
   lands on its neighbour: measured at 844x390, where six landscape leaves
   12 px between the two side seats and a 32 px card adds 45. The seat clips
   its own overflow, so this can never reach past the box the layout
   reserved.

   Over the avatar tile, top left, and not bottom right. Bottom right put
   the two cards over the stack and turned 1 667 into 1 66 in a screenshot
   at 844x390. The initials are the one thing on the pod the name already
   says, so they are the one thing the cards may cover. */
.seat > .hand {
  grid-area: cards;
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 1;
}
.seat > .hand:empty { display: none; }
/* Inside a seat the pair overlaps by more and leans by less than the hero's
   hand does. The tile and its gap are 48 px wide, and the hero's fan is 52,
   so at the hero's fan the second card covered the first letter of the
   name. At 55% overlap and four degrees the pair stays on the tile. */
.seat > .hand > .card:first-child { rotate: 0deg; }
.seat > .hand > .card + .card {
  margin-inline-start: calc(var(--w) * -.55);
  rotate: 4deg;
}

/* The seven states. A shape per state, never colour alone. */
.seat[data-state="empty"] {
  --ring-c: var(--line-strong);
  --ring-style: dashed;
}
.seat[data-state="empty"]::before { background: transparent; }
.seat[data-state="empty"] > .avatar {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--line);
}
.seat[data-state="empty"] > .avatar > .face {
  color: var(--ink-faint);
  font-weight: 500;
  font-size: calc(var(--face) * .55);
}
.seat[data-state="empty"] .name { color: var(--ink-faint); font-weight: 500; }
.seat[data-state="empty"] .stack { visibility: hidden; }

.seat[data-state="joining"] {
  --ring-c: var(--info);
  --ring-style: dotted;
  --badge-c: var(--info);
  --badge-svg: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M8 3v9M4.5 8.5L8 12l3.5-3.5' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  opacity: var(--alpha-dim);
  scale: .94;
}
.seat[data-state="joining"] > .avatar > .badge { display: grid; }
.seat[data-state="joining"] .stack { color: var(--ink-faint); }

.seat[data-state="ready"] {
  --ring-c: var(--line-strong);
}

.seat[data-state="choosing"] {
  --ring-c: var(--ink-faint);
  --ring-w: var(--space-ring-locked);
  --badge-c: var(--ink);
  --badge-svg: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='4.5' fill='none' stroke='black' stroke-width='2.2'/></svg>");
}
.seat[data-state="choosing"] > .avatar > .badge { display: grid; }

/* The only place the accent appears on an opponent. It says locked and
   never which choice. */
.seat[data-state="locked"] {
  --ring-w: var(--space-ring-locked);
  --ring-c: var(--volt);
  --badge-c: var(--volt-ink);
  --badge-svg: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3 8.5l3.2 3L13 4.5' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
.seat[data-state="locked"] > .avatar > .badge { display: grid; background: var(--volt); }

.seat[data-state="out"] {
  --ring-c: transparent;
  --badge-c: var(--ink-faint);
  --badge-svg: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 4l8 8M12 4l-8 8' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'/></svg>");
  opacity: var(--alpha-out);
}
.seat[data-state="out"] > .avatar { filter: saturate(0); }
.seat[data-state="out"] > .avatar > .badge { display: grid; }
.seat[data-state="out"] .name { color: var(--ink-muted); text-decoration: line-through; }
.seat[data-state="out"] .stack { color: var(--ink-faint); }
.seat[data-state="out"] > .hand { display: none; }

.seat[data-state="disconnected"] {
  --ring-c: var(--ink-faint);
  --ring-style: dashed;
  --badge-c: var(--risk);
  --badge-svg: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M2 6a9 9 0 0 1 12 0M4.5 9a5.5 5.5 0 0 1 7 0' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/><circle cx='8' cy='12.5' r='1.5'/><path d='M3 14L13 3' stroke='black' stroke-width='2' stroke-linecap='round'/></svg>");
  opacity: var(--alpha-dim);
}
.seat[data-state="disconnected"] > .avatar { filter: saturate(0); }
.seat[data-state="disconnected"] > .avatar > .badge { display: grid; }
.seat[data-state="disconnected"] .name,
.seat[data-state="disconnected"] .stack { color: var(--ink-muted); }

/* A hue per seat position. Eleven positions, spread around the wheel so
   two neighbours never share a colour. Flat, desaturated, and the same
   for the whole session, which is what a player needs to find a seat
   again without reading its name. */
.slot[data-i="1"]  { --h: 210; }
.slot[data-i="2"]  { --h: 20; }
.slot[data-i="3"]  { --h: 290; }
.slot[data-i="4"]  { --h: 150; }
.slot[data-i="5"]  { --h: 45; }
.slot[data-i="6"]  { --h: 330; }
.slot[data-i="7"]  { --h: 180; }
.slot[data-i="8"]  { --h: 260; }
.slot[data-i="9"]  { --h: 90; }
.slot[data-i="10"] { --h: 0; }
.slot[data-i="11"] { --h: 120; }

/* Compact. It is what lets twelve fit in portrait. Smaller tile, short
   name, abbreviated stack, one chip, and the whole state carried by the
   ring and the mark. Whole box: 3 + 28 + 5 + 18. */
@container seat (max-width: 100px) {
  .seat {
    --face: var(--space-seat-avatar-compact);
    --pad: 3px;
    column-gap: 6px;
    row-gap: calc(var(--pad) + 2px);
    padding-inline-end: 6px;
  }
  .seat > .avatar { border-radius: 7px; }
  .seat > .avatar > .badge { inline-size: 12px; right: -3px; bottom: -3px; }
  .seat .name { font-size: var(--step-n2); }
  .seat .stack { font-size: var(--step-n2); }
  .seat .stack::after { content: attr(data-short); }
  .seat > .bet,
  .seat > .choice { --h: 18px; font-size: var(--step-n2); }
  .seat > .bet { gap: 4px; padding-inline-end: 6px; }
  .seat > .bet > .chips > .chip { --s: 14px; }
  /* Only the highest value chip. The number is what carries the value. */
  .seat > .bet > .chips > .chip + .chip { display: none; }
  .seat > .hand > .card { --w: var(--space-card-opponent); }
}

/* --------------------------------------------------------------- choice */

/* The revealed choice. While locked it is a closed tab: an accent frame and
   no text. Same height as the bet it replaces. */
.choice {
  --h: 22px;
  display: inline-grid;
  grid-auto-flow: column;
  align-items: center;
  justify-content: center;
  min-inline-size: 52px;
  block-size: var(--h);
  padding-inline: 8px;
  border-radius: var(--space-radius-small);
  /* Two steps over the ground and not one. The tile stands on the arena
     with nothing under it, and at one step it read as loose text. */
  background: var(--surface-pressed);
  font-family: var(--font-display);
  font-size: var(--step-n2);
  font-weight: var(--font-weight-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  transform-style: preserve-3d;
  backface-visibility: hidden;
}
.choice > .amt {
  margin-inline-start: 5px;
  font-weight: 600;
  letter-spacing: var(--font-tracking-display);
  text-transform: none;
  color: var(--ink-muted);
}
.choice[data-c="locked"] {
  color: transparent;
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--volt-line);
}
.choice[data-c="FOLD"] { background: var(--surface-pod); color: var(--ink-faint); text-decoration: line-through; }
.choice[data-c="MAX"] { background: var(--volt); color: var(--volt-ink); }
.choice[data-c="MAX"] > .amt { color: var(--volt-ink); }

/* ----------------------------------------------------------------- clock */

/* One clock, large, at the centre, the pot inside it. Time runs around a
   rounded square, the pot is what the player reads, and the counter sits
   under the pot inside the same square so the centre stack is one object
   and not three. The seconds appear only in the last five. */
.clock {
  --p: .6;
  --size: var(--space-clock);
  --arc: var(--clock-neutral);
  position: relative;
  inline-size: var(--size);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  flex: none;
  border-radius: var(--space-radius-large);
  background: var(--surface-pod);
  z-index: var(--layer-clock);
}
.clock > svg {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  overflow: visible;
}
.clock > svg path {
  fill: none;
  stroke-width: var(--space-clock-stroke);
  stroke-linecap: round;
}
.clock > svg .track { stroke: var(--clock-track); }
.clock > svg .arc {
  stroke: var(--arc);
  stroke-dasharray: calc(var(--p) * 100) 100;
  transition: stroke var(--motion-clock-to-amber) var(--motion-ease-in-out);
}
.clock > .inner {
  display: grid;
  justify-items: center;
  line-height: 1;
  gap: 3px;
  /* Lifts the pot so that pot, label and counter centre as a group. */
  padding-block-end: calc(var(--size) * .15);
}
.clock .pot-label {
  font-family: var(--font-display);
  font-size: var(--step-n2);
  letter-spacing: var(--font-tracking-label);
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: var(--font-weight-label);
}
.clock .pot {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-display);
  font-size: calc(var(--size) * .27);
  letter-spacing: var(--font-tracking-display);
  color: var(--ink);
}
.clock .secs {
  position: absolute;
  left: 50%;
  bottom: -11px;
  translate: -50% 0;
  min-inline-size: 32px;
  padding: 3px 8px;
  border-radius: var(--space-radius-small);
  background: var(--arc);
  color: var(--ground);
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-size: var(--step-n2);
  line-height: 1;
  text-align: center;
  opacity: 0;
  transition: opacity var(--motion-clock-to-amber) var(--motion-ease-out);
}
.clock[data-t="amber"] { --arc: var(--risk); }
.clock[data-t="pulse"] { --arc: var(--danger); }
.clock[data-t="amber"] .secs,
.clock[data-t="pulse"] .secs { opacity: 1; }
.clock[data-t="pulse"] {
  animation: clock-pulse var(--motion-clock-pulse) var(--motion-ease-in-out) infinite;
}
@keyframes clock-pulse {
  0%, 100% { scale: 1; }
  50% { scale: var(--motion-clock-pulse-scale); }
}

.clock-wrap {
  position: relative;
  display: grid;
  justify-items: center;
}
/* Inside the square, under the pot. Absolute so it adds nothing to the
   centre stack, which is the height every seat ring was tuned against. */
.clock-wrap > .ready-count {
  position: absolute;
  left: 50%;
  top: calc(50% + var(--space-clock) * .17);
  translate: -50% 0;
  z-index: var(--layer-clock);
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-size: var(--step-n2);
  font-weight: 600;
  color: var(--ink-faint);
  white-space: nowrap;
}
.ready-count::after { content: attr(data-long); }

/* -------------------------------------------------------------- top band */

/* The dealer band. Fixed place, the top, in every layout. The mark and the
   wordmark on the left, the voice line beside them, the hand counter on the
   right. No border under it: the arena surface below is lighter, and that
   is the separation. */
.dealer {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 10px;
  min-block-size: 40px;
  padding-inline: var(--space-gutter);
  background: var(--ground);
  z-index: var(--layer-hud);
}
/* The mark. A volt tile with the initial. It is also where the cards are
   dealt from, which is why it sits in the band and not in a corner. */
.dealer-mark {
  inline-size: 24px;
  aspect-ratio: 1;
  border-radius: 7px;
  background: var(--volt);
  color: var(--volt-ink);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-wordmark);
  font-size: 15px;
  line-height: 1;
}
.dealer-name {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-wordmark);
  font-size: var(--step-1);
  letter-spacing: -.01em;
  line-height: 1;
  color: var(--ink);
}
.dealer-name:empty { display: none; }
.dealer-line {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: var(--font-weight-body);
  font-size: var(--step-0);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dealer-count {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-size: var(--step-n1);
  font-weight: 600;
  color: var(--ink-muted);
  white-space: nowrap;
}

/* ------------------------------------------------------------ action bar */

/* The viewport's only glass surface. The values live on the buttons,
   which are opaque. Never a number over glass. Large radius: it is a
   sheet, and the buttons on it are the dense element with the small one. */
.bar {
  display: grid;
  /* FOLD takes half the share of one of the other three. It is one word
     and an icon, and the width it gave up is what keeps "1 000" on one line
     in a 306 px landscape bar. */
  grid-template-columns: minmax(64px, .5fr) 1fr;
  column-gap: var(--space-fold-gap);
  padding: 8px;
  border-radius: var(--space-radius-large);
  background: var(--glass-tint);
  -webkit-backdrop-filter: var(--glass-backdrop);
  backdrop-filter: var(--glass-backdrop);
  box-shadow: var(--glass-shadow);
  z-index: var(--layer-action-bar);
}
.bar > .acts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
@media (prefers-reduced-transparency: reduce) {
  .bar {
    background: var(--glass-fallback);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
.bar[data-glass="off"] {
  background: var(--glass-fallback);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .5);
}

/* The four buttons. Pressed hundreds of times an hour, so no animation
   beyond the press itself: a scale under the finger, synchronous, and a
   surface step. Five states and each is a different shape, not a tint. */
.act {
  appearance: none;
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 2px;
  min-block-size: 48px;
  min-inline-size: var(--space-touch);
  padding: 6px 8px;
  border: 0;
  border-radius: var(--space-radius-small);
  background: var(--surface-raised);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    scale var(--motion-press-in) var(--motion-ease-out),
    translate var(--motion-press-in) var(--motion-ease-out),
    background var(--motion-state-swap) var(--motion-ease-out),
    opacity var(--motion-state-swap) var(--motion-ease-out);
}
.act > .lbl {
  font-family: var(--font-display);
  font-size: var(--step-n2);
  font-weight: var(--font-weight-label);
  letter-spacing: var(--font-tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
  line-height: 1;
}
.act > .amt {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-display);
  font-size: var(--step-1);
  letter-spacing: var(--font-tracking-display);
  line-height: 1.1;
  white-space: nowrap;
}
@media (hover: hover) {
  .act:hover { background: var(--surface-pressed); }
  .act.fold:hover { background: var(--surface-pod); }
}
.act:active {
  scale: var(--motion-press-scale);
  translate: 0 1px;
}
/* Locked sinks by the press depth and keeps the accent frame. It stays
   sunk for the rest of the round. */
.act.is-locked {
  translate: 0 var(--space-press-depth);
  background: var(--surface-pod);
  box-shadow:
    inset 0 0 0 var(--space-ring-locked) var(--volt),
    inset 0 2px 0 rgba(0, 0, 0, .35);
}
.act.is-locked > .lbl { color: var(--volt); }
/* The slot always exists. Disabled dims it, does not remove it. */
.act[disabled] {
  opacity: var(--alpha-out);
  cursor: default;
}
.act[disabled]:active { scale: 1; translate: none; }
.act:focus-visible {
  outline: 2px solid var(--info);
  outline-offset: 2px;
}
.act.fold {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--line-strong);
}
.act.fold > .lbl { color: var(--ink); }
.act.fold > .lbl::before {
  content: "";
  display: block;
  inline-size: 10px;
  aspect-ratio: 1;
  margin: 0 auto 5px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 4l8 8M12 4l-8 8' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 4l8 8M12 4l-8 8' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round'/></svg>") center / contain no-repeat;
}
.act.fold.is-locked {
  background: var(--surface-pod);
  box-shadow:
    inset 0 0 0 var(--space-ring-locked) var(--volt),
    inset 0 2px 0 rgba(0, 0, 0, .35);
}

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

.table {
  --slot-w: 112px;
  position: relative;
  container: table / inline-size;
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  inline-size: 100%;
  block-size: 100%;
  overflow: hidden;
  isolation: isolate;
}
/* The renderer's canvas. Everything that animates lives on it: cards, chips
   and highlights. It covers the table exactly, out of the grid flow, and it
   takes no pointer events at all. A canvas that captured touches would steal
   every tap from the seat and the button underneath it, and interaction
   belongs to the DOM. */
.table > .table-canvas {
  position: absolute;
  inset: 0;
  z-index: var(--layer-cards);
  pointer-events: none;
}
.table > .dealer { grid-row: 1; }
.table > .arena {
  grid-row: 2;
  position: relative;
  min-block-size: 0;
  padding: 8px var(--space-gutter);
}
/* The table surface. One luminance step over the ground, a large radius
   because it is the biggest sheet on the screen, and no border. The seats
   are laid out against the arena box and not against this panel, so its
   inset is purely visual and the model does not know it exists. */
.table > .arena::before {
  content: "";
  position: absolute;
  inset: 2px 8px;
  z-index: var(--layer-felt);
  border-radius: var(--space-radius-large);
  background: var(--surface-arena);
  pointer-events: none;
}
.table > .hero-row {
  grid-row: 3;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 12px;
  padding: 4px var(--space-gutter);
}
.table > .bar-row {
  grid-row: 4;
  padding: 6px var(--space-gutter) max(var(--space-safe-bottom), var(--space-palm));
}

/* Opponent in the ring. Trigonometry in CSS, an angle per slot.
   --cy is the centre of the ring, which is not always the middle of the
   arena. A ring of six reaches twice as far up as it does down, so a layout
   whose arena is short has to push it down or the top seat lands on the
   dealer band, and a portrait arena is tall enough that a centred ring
   leaves a void over the hero. Every ellipse layout sets it today. */
.arena > .slot {
  position: absolute;
  left: var(--x, calc(50% + cos(var(--a)) * var(--rx)));
  top: var(--y, calc(var(--cy, 50%) + sin(var(--a)) * var(--ry)));
  translate: -50% -50%;
  z-index: var(--layer-seats);
}
.arena > .center {
  position: absolute;
  left: 50%;
  top: 44%;
  translate: -50% -50%;
  display: grid;
  justify-items: center;
  gap: 8px;
}
.board {
  display: flex;
  gap: 6px;
  justify-content: center;
}
.board > .card { --w: var(--space-card-board); }
.hand-name {
  font-family: var(--font-display);
  font-weight: var(--font-weight-label);
  font-size: var(--step-0);
  color: var(--ink);
  white-space: nowrap;
}
/* On a real table it hangs under the board instead of sitting in the row.
   .center is a column in portrait and a row in landscape, so a third child
   resizes the group, and this one only appears at showdown: in flow it shoves
   the clock sideways at the exact moment the player is reading the pot.
   Measured at 844x390, where it moved the clock 92 px left, onto two seats.
   Scoped to .center so the swatch in demo.html keeps its own flow. */
.arena > .center > .hand-name {
  position: absolute;
  inset-block-start: calc(100% + 6px);
  inset-inline-start: 50%;
  translate: -50% 0;
}

/* Hero. Always at the bottom, centered, across the six layouts. */
.hero-seat {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}
.hero-seat > .hand > .card { --w: var(--space-card-hero); }
.hero-seat > .meta {
  display: grid;
  gap: 2px;
  line-height: 1;
  padding-bottom: 4px;
}
.hero-seat .name {
  font-family: var(--font-display);
  font-size: var(--step-n1);
  font-weight: var(--font-weight-label);
  color: var(--ink-muted);
}
.hero-seat .stack {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-stretch: var(--font-stretch-display);
  font-size: var(--step-2);
  letter-spacing: var(--font-tracking-display);
  color: var(--ink);
}

/* 3 portrait. Vertical ellipse, two opponents in an arc above.
   The ring is 48% tall and not 42%. At 360 and 390 px a 120 px slot and an
   86 px clock cannot pass each other sideways inside a 16 px gutter, so the
   pair clears the clock by height instead. Measured in geometry.test.ts.
   The ring and the stack hang at 55% and 56%: centred, the pot sat above
   the eye line and a 150 px void opened between the pair and the hero. */
.table[data-format="3"][data-orient="portrait"] .arena { --rx: 30%; --ry: 48%; --cy: 55%; }
.table[data-format="3"][data-orient="portrait"] .arena > .center { top: 56%; }
.table[data-format="3"][data-orient="portrait"] { --slot-w: 120px; }

/* 6 portrait. Vertical ellipse, five in an arc. The ring hangs at 54% and
   the stack at 48%, for the same reason as three: the top seat still
   clears the band by 50 px at 390x844 and the void under the ring closes.
   Mirrored in geometry.ts, held by geometry.test.ts. */
.table[data-format="6"][data-orient="portrait"] .arena { --rx: 36%; --ry: 40%; --cy: 54%; }
.table[data-format="6"][data-orient="portrait"] .arena > .center { top: 48%; }
.table[data-format="6"][data-orient="portrait"] { --slot-w: 104px; }

/* 3 landscape. A line. */
.table[data-format="3"][data-orient="landscape"] .arena > .slot[data-i="1"] { --x: 15%; --y: 32%; }
.table[data-format="3"][data-orient="landscape"] .arena > .slot[data-i="2"] { --x: 85%; --y: 32%; }
.table[data-format="3"][data-orient="landscape"] { --slot-w: 128px; }

/* 6 landscape. Classic horizontal ellipse. */
.table[data-format="6"][data-orient="landscape"] .arena {
  --rx: min(40%, 480px);
  /* 40% and not 36%. The two side pairs sit exactly --ry apart, and at
     390 px of height a 36% ring left 3.5 px between two seats. */
  --ry: min(40%, 240px);
  /* The ring hangs. See --cy on .arena > .slot. Measured at 844x390, where
     a centred ring puts the top seat inside the dealer band. */
  --cy: 59%;
}
.table[data-format="6"][data-orient="landscape"] .arena > .center { top: 54%; }
.table[data-format="6"][data-orient="landscape"] { --slot-w: 120px; }

/* 12 landscape. A stadium, not an ellipse: five on top, one on each side,
   four at the bottom. With eleven in an ellipse, the ones at 120 and 240
   degrees fall on the clock on any phone. Clockwise order starting from the
   hero's left. */
.table[data-format="12"][data-orient="landscape"] { --slot-w: 112px; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="1"]  { --x: 20%; --y: 84%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="2"]  { --x: 9%;  --y: 84%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="3"]  { --x: 6%;  --y: 50%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="4"]  { --x: 9%;  --y: 16%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="5"]  { --x: 29%; --y: 16%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="6"]  { --x: 50%; --y: 16%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="7"]  { --x: 71%; --y: 16%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="8"]  { --x: 91%; --y: 16%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="9"]  { --x: 94%; --y: 50%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="10"] { --x: 91%; --y: 84%; }
.table[data-format="12"][data-orient="landscape"] .arena > .slot[data-i="11"] { --x: 80%; --y: 84%; }
/* The clock and board row drops into the bottom corridor instead of sitting
   at the middle. Five seats across the top and four across the bottom leave
   that corridor empty end to end, and at 390 px of height a centred row
   lands on the top seats. ADR 0004, measured in geometry.test.ts. */
.table[data-format="12"][data-orient="landscape"] .arena > .center { top: 62%; }
/* The query measures .table, so the rule lives on a descendant. The slot
   inherits from the arena. */
@container table (max-width: 1100px) {
  .table[data-format="12"][data-orient="landscape"] > .arena { --slot-w: 84px; }
  .table[data-orient="landscape"] .ready-count::after { content: attr(data-short); }
}

/* Landscape. Hero and bar split the bottom row. The hero stays at the
   exact center because the side columns are equal. */
.table[data-orient="landscape"] {
  grid-template-rows: auto 1fr auto;
}
.table[data-orient="landscape"] > .arena {
  inline-size: min(100%, var(--space-arena-max-width));
  max-block-size: var(--space-arena-max-height);
  justify-self: center;
  align-self: center;
  block-size: 100%;
}
.table[data-orient="landscape"] > .bottom {
  grid-row: 3;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  gap: 12px;
  padding: 4px var(--space-gutter) max(var(--space-safe-bottom), var(--space-palm));
}
.table[data-orient="landscape"] > .bottom > .hero-seat { grid-column: 2; }
.table[data-orient="landscape"] > .bottom > .bar {
  grid-column: 3;
  justify-self: end;
  inline-size: min(100%, 360px);
  min-inline-size: 0;
}
/* The landscape bar is a third of the width and capped at 360, so the
   amount drops one step. Measured at 844x390: at step 1 "1 000" wrapped at
   its thin space and the bar grew to 85 px. */
.table[data-orient="landscape"] > .bottom .act {
  padding-inline: 4px;
}
.table[data-orient="landscape"] > .bottom .act > .amt {
  font-size: var(--step-0);
}
.table[data-orient="landscape"] .arena > .center {
  grid-auto-flow: column;
  align-items: center;
  gap: 16px;
  top: 50%;
}

/* 12 portrait. The hard case. Two columns of compact seats on the sides,
   clock and pot in the center band, board on its own line. */
.table[data-format="12"][data-orient="portrait"] {
  --slot-w: var(--space-seat-compact);
}
.table[data-format="12"][data-orient="portrait"] .arena {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: repeat(5, 1fr) auto;
  align-items: center;
  column-gap: 6px;
  row-gap: 4px;
}
.table[data-format="12"][data-orient="portrait"] .arena > .slot {
  position: static;
  translate: none;
}
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="1"] { grid-area: 5 / 1; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="2"] { grid-area: 4 / 1; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="3"] { grid-area: 3 / 1; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="4"] { grid-area: 2 / 1; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="5"] { grid-area: 1 / 1; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="6"] { grid-area: 1 / 2; justify-self: center; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="7"] { grid-area: 1 / 3; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="8"] { grid-area: 2 / 3; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="9"] { grid-area: 3 / 3; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="10"] { grid-area: 4 / 3; }
.table[data-format="12"][data-orient="portrait"] .arena > .slot[data-i="11"] { grid-area: 5 / 3; }
.table[data-format="12"][data-orient="portrait"] .arena > .center {
  position: static;
  translate: none;
  display: contents;
}
.table[data-format="12"][data-orient="portrait"] .arena > .center > .clock-wrap {
  grid-area: 2 / 2 / 6 / 3;
  justify-self: center;
  align-self: center;
}
.table[data-format="12"][data-orient="portrait"] .arena > .center > .board {
  grid-area: 6 / 1 / 7 / 4;
  padding-top: 6px;
}
/* .center is display:contents here, so there is no box to hang from and the
   name goes back into the grid. */
.table[data-format="12"][data-orient="portrait"] .arena > .center > .hand-name {
  position: static;
  grid-area: 6 / 1 / 7 / 4;
  align-self: end;
  translate: 0 100%;
  font-size: var(--step-n1);
}

/* Squircle corners where the browser draws them. The radius stays the
   same, the curve gets continuous. Only on the large radius surfaces. */
@supports (corner-shape: squircle) {
  .clock,
  .bar,
  .table > .arena::before {
    corner-shape: squircle;
  }
}

/* --------------------------------------------------------------- motion */

@media (prefers-reduced-motion: reduce) {
  .clock[data-t="pulse"] { animation: none; }
  .seat,
  .seat::before,
  .seat > .avatar,
  .act,
  .clock > svg .arc,
  .clock .secs {
    transition: none;
  }
}
