/*
 * 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;

  /*
   * The hero's card, derived from the board's rather than declared.
   *
   * Measured on the deployed build the hero's two came out 61 and 68 px
   * against a board card at 52, which is 1.2 times. The twenty two apps in
   * design/REFERENCES-MOBILE-POKER.md draw the hero's at about twice the
   * board's, in a band of their own, and that is the single most consistent
   * decision in the category: it is how the screen says this hand is yours.
   *
   * `--space-card-hero` is a token of its own and is no longer read here,
   * because a second number is a second thing to keep at two times. The ratio
   * is the design, so the ratio is what is written.
   *
   * The `15vh` is the one place the ratio gives way, and only where the height
   * is the thing in short supply. In portrait it never binds: 15vh of an 844
   * px screen is 127 px against a 101 px card. In landscape on a phone it
   * resolves to exactly what the old `15vmin` token gave, so a 390 px tall
   * table keeps the bottom band it was measured with. A 360 px tall screen
   * cannot hold a ring of six, a dealer band, a board and a double height
   * hand at once, and ADR 0004 already says so.
   */
  --card-hero: min(calc(var(--space-card-board) * 2.6), 15vh);
}

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;
}
/*
 * The ten is two characters wide and every other rank is one.
 *
 * The board card came down from 48-56 px to 36-42 so that the side columns of
 * the ring could pass it, and the ten went off the edge: a screenshot of a
 * settled hand showed `0 heart` on the board and in the player's own hand.
 * Every other rank was fine, which is exactly why it is worth a rule of its
 * own rather than shrinking all thirteen.
 *
 * Condensed and tightened rather than scaled down, so a ten still reads at the
 * weight of the rank beside it instead of looking like a different card.
 */
.card[data-r="10"] > .rank {
  font-stretch: 55%;
  font-size: 30cqw;
  letter-spacing: -.08em;
}
.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[data-r="10"] > .rank { font-size: 35cqw; }
  .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[data-r="10"] > .rank { font-size: 40cqw; }
  .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. The hero's is twice the board's, see --card-hero. */
.card.hero { --w: var(--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;
}
/*
 * A hand fans, and it fans much less than it did.
 *
 * The two cards leaned a full tilt apart, which reads as a fan held up at a
 * live table and as two crooked rectangles on a phone. Two degrees is enough
 * to say these are two objects and not one, and it keeps both ranks upright
 * enough to read at a glance, which is the only thing the hero's cards are
 * for.
 */
.hand > .card:first-child {
  rotate: -2deg;
  transform-origin: 40% 100%;
}
.hand > .card + .card {
  margin-inline-start: calc(var(--w) * -.34);
  rotate: 2deg;
  transform-origin: 20% 100%;
}
/*
 * A card being dealt, which is a card arriving rather than a card appearing.
 *
 * Every seat is built holding two backs, so the dealing phase used to change
 * one line of text and leave the felt untouched for two and a half seconds.
 * The cards were already on the table before the hand began.
 *
 * `--i` is the card's place in the deal, written by the screen, so eighteen
 * cards at a nine handed table run off one timeline. Transform and opacity
 * only, so the compositor owns all of it and a deal cannot reflow the ring.
 *
 * It lands rather than flies. A card crossing the felt from the middle would
 * have to know its seat's distance from the centre, and that distance is a
 * percentage of the arena, which on a card resolves against the card. The
 * order and the panned sound are what make it read as dealing, and those are
 * exact.
 */
@keyframes card-deal {
  0% { opacity: 0; scale: .66; rotate: -16deg; translate: 0 -22%; }
  18% { opacity: 1; }
  100% { opacity: 1; scale: 1; rotate: 0deg; translate: 0 0; }
}
.card.dealing {
  animation: card-deal var(--motion-deal-per-card) var(--motion-ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--motion-deal-stagger));
}
@media (prefers-reduced-motion: reduce) {
  .card.dealing { animation-duration: 1ms; animation-delay: 0ms; }
}

/*
 * The hero's hand, tapped: the two cards change places.
 *
 * The first version moved one card sideways and brought it to the front, and
 * the owner read it for what it was: one card hiding the other's rank for a
 * moment and then landing back where it started. Nothing traded places.
 *
 * They travel now. The pair overlaps by 34 per cent of a card, so the gap
 * between their two resting positions is 66 per cent of one, and each walks
 * that distance in the opposite direction while one dips under and the other
 * rises over. At the end the left card is on the right and it stays there:
 * `data-order` on the hand holds which way round they are, so the next tap
 * walks them back along the same path reversed.
 *
 * Two rules keep it safe to leave on a betting screen. It never moves a card
 * out of the hand's own box, so nothing it does can cover a number. And it is
 * pure decoration, so it is the first thing reduced motion takes away.
 *
 * `translate` and `z-index` only. No layout property moves, so a tap cannot
 * reflow the felt mid animation and the compositor can run all of it.
 */
.hero-seat > .hand {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hero-seat > .hand > .card { --travel: calc(var(--w) * .66); }

/*
 * The card that ends on the RIGHT ends on top, and that is the whole fix.
 *
 * A fanned hand is readable because the rank lives in the top left corner and
 * the card on the right covers only the left card's right edge. Swap the
 * positions without swapping the stacking and you get the opposite: the card
 * now on the left sits on top and covers the other card's corner, which is
 * exactly the rank. The owner found it in one tap: "a de cima fica cobrindo a
 * de baixo, a pessoa nem consegue ver o numero".
 *
 * So the first card travels right and rises to the front, and it passes UNDER
 * on the way, which is the movement he asked for. `z-index` is animatable in
 * steps, so the change happens at the keyframe rather than smoothly, which is
 * what a card passing under looks like anyway.
 */
@keyframes card-swap-right {
  0%   { translate: 0 0; z-index: 1; }
  35%  { translate: calc(var(--travel) * .5) 14%; z-index: 1; }
  100% { translate: var(--travel) 0; z-index: 3; }
}
@keyframes card-swap-left {
  0%   { translate: 0 0; z-index: 2; }
  35%  { translate: calc(var(--travel) * -.5) -9%; z-index: 2; }
  100% { translate: calc(var(--travel) * -1) 0; z-index: 2; }
}

.hero-seat > .hand[data-order="b"] > .card:first-child {
  animation: card-swap-right var(--motion-showdown-per-seat) var(--motion-ease-in-out) both;
}
.hero-seat > .hand[data-order="b"] > .card + .card {
  animation: card-swap-left var(--motion-showdown-per-seat) var(--motion-ease-in-out) both;
}
/* The way back is the same two keyframes read in reverse, so the second tap is
   the first one undone rather than a third animation to keep in step. */
.hero-seat > .hand[data-order="a"] > .card:first-child {
  animation: card-swap-right var(--motion-showdown-per-seat) var(--motion-ease-in-out) both reverse;
}
.hero-seat > .hand[data-order="a"] > .card + .card {
  animation: card-swap-left var(--motion-showdown-per-seat) var(--motion-ease-in-out) both reverse;
}
@media (prefers-reduced-motion: reduce) {
  .hero-seat > .hand > .card { animation-duration: 1ms; }
}

/*
 * The board lies straight.
 *
 * It alternated half a tilt, on the reasoning that cards dealt by a hand are
 * never square. The owner read the same thing as crooked, and on a five card
 * row at 32 px the tilt is not craft, it is five cards that do not line up.
 * The deal reads as a deal from the turn, which is motion, rather than from a
 * permanent angle, which is just the resting state being wrong.
 */
.board > .card { rotate: 0deg; }

/* 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);
}
/*
 * Volume, from one light.
 *
 * The owner asked for light on top and shadow underneath so a card reads as an
 * object rather than as a rectangle. Everything below comes from that one
 * sentence and from one rule: there is a single light source, above and
 * slightly in front, and every surface in this interface is lit by it. Two
 * light sources is how an interface starts looking like a collage.
 *
 * Three layers, and each is doing a different job:
 *
 *   the inset white line along the top edge is the light catching the face
 *   the inset dark line along the bottom is the thickness of the card itself
 *   the drop shadow is the felt underneath, and it is soft because felt is
 *
 * No gradient on the face. A gradient across a playing card is the tell of a
 * rendering: a real one is flat stock and the volume is at its edges.
 */
.card {
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, white 70%, transparent),
    inset 0 -1px 0 color-mix(in oklab, black 22%, transparent),
    0 1px 1px color-mix(in oklab, black 22%, transparent),
    0 6px 12px -4px color-mix(in oklab, black 45%, transparent);
}
/* A card on the felt sits lower than one in a hand, so its shadow is tighter
   and darker. The hero's two are held up, and held up means further from what
   they cast onto. */
.board > .card {
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, white 62%, transparent),
    inset 0 -1px 0 color-mix(in oklab, black 24%, transparent),
    0 1px 2px color-mix(in oklab, black 38%, transparent);
}
.hero-seat .card {
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, white 78%, transparent),
    inset 0 -1px 0 color-mix(in oklab, black 20%, transparent),
    0 2px 2px color-mix(in oklab, black 20%, transparent),
    0 14px 24px -8px color-mix(in oklab, black 55%, transparent);
}

/*
 * The back of a card, and the turn that shows its face.
 *
 * A hand starts face down and the cards turn over, which is the owner's
 * request and also the only way a deal reads as a deal rather than as five
 * rectangles appearing. There is one element per card and it cannot hold two
 * faces, so the back is a state of the same element: the face is hidden, and
 * what is painted is the felt's own dark panel with a volt keyline.
 *
 * Drawn in CSS rather than by the procedural back in `cardback.ts`. That file
 * is 21.7 KiB and the table screen is the one that runs closest to the
 * JavaScript budget, so a card back that costs a stylesheet rule is the only
 * one this screen can afford.
 *
 * The turn is a rotation about the vertical axis with the face swapped at the
 * edge, which is what a card does. `both` keeps the final frame, so a card
 * that has turned stays turned.
 */
.card.back > * { visibility: hidden; }
.card.back {
  /*
   * A back, not an empty slot.
   *
   * The first version was the pressed surface with a volt keyline and it read
   * as a hole in the felt: an outlined rectangle where a card should be. A
   * back is a printed thing, so it gets a print: a fine diagonal lattice in
   * the brand's own two inks over the dark surface, and a hairline inset the
   * way a real back leaves a border of stock around its pattern.
   *
   * Two gradients and no image. It is drawn by the compositor, it costs no
   * request, and it scales from the 22 px card on a compact seat to the 100 px
   * one in the player's own hand without going soft.
   */
  background:
    repeating-linear-gradient(
      45deg,
      color-mix(in oklab, var(--volt) 14%, transparent) 0 1px,
      transparent 1px 6px
    ),
    repeating-linear-gradient(
      -45deg,
      color-mix(in oklab, var(--volt) 9%, transparent) 0 1px,
      transparent 1px 6px
    ),
    var(--surface-pressed);
  box-shadow:
    inset 0 0 0 1px color-mix(in oklab, var(--volt) 22%, transparent),
    inset 0 0 0 3px var(--surface-pressed),
    inset 0 1px 0 color-mix(in oklab, white 12%, transparent),
    0 1px 2px color-mix(in oklab, black 40%, transparent);
}

@keyframes card-turn {
  from { transform: rotateY(-90deg); }
  to   { transform: rotateY(0deg); }
}
.card.turn {
  /*
   * A beat per card, so a group arrives as a deal rather than as a board.
   *
   * `--i` is the card's place in its group, written by the screen. The delay
   * is the same `--motion-deal-stagger` the tokens already carried for a deal
   * that was never animated, which is why it is not a new number.
   */
  animation: card-turn var(--motion-board-flip) var(--motion-ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--motion-deal-stagger));
  transform-origin: 50% 50%;
  backface-visibility: hidden;
}
/* The turn is trajectory, and trajectory is the first thing to go. The card
   still arrives, it simply arrives already face up. */
@media (prefers-reduced-motion: reduce) {
  .card.turn { animation: none; }
}

/*
 * A folded card. Dark and grey, not transparent.
 *
 * It used to be `opacity: var(--alpha-out)`, and the owner found what that
 * costs: the hero's two cards overlap, so at 42 per cent each the overlap
 * shows one card through the other and neither is readable. Two see through
 * cards stacked is not a dimmed hand, it is a hole in the table.
 *
 * Grey and darker keeps the card opaque, keeps the overlap solid, and says the
 * same thing more plainly: this hand is out. It matches the seat, where a
 * folded player goes grey rather than faint.
 */
.card.dead {
  filter: grayscale(1) brightness(0.52) contrast(0.9);
  opacity: 1;
}

/* ----------------------------------------------------------------- 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);
  /*
   * Cards above the name, which is where they are on a real table.
   *
   * The `cards` row is new and it is the owner's request: every player shows
   * their two, from the deal, rather than only at showdown. It is first
   * because a hand is read top down and the cards are the subject.
   */
  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);
  /*
   * Not hidden. It was, to keep a long name inside the tile, and it clipped
   * the two cards the moment they moved above the tile: every seat showed a
   * three pixel sliver of green where a pair of cards should be.
   *
   * The name is clipped where the name is, which is the honest place for it.
   */
  overflow: visible;
  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);
  /* Stated rather than inferred from the ratio. The row measured 32 px against
     a 36 px token, which made the model four pixels out per seat and the seat
     box test red. A square that is told both of its sides cannot be squeezed
     by the row it is in. */
  block-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 > .meta { overflow: hidden; }
.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. */
/*
 * Two cards, ABOVE the seat and not inside it.
 *
 * The first version put them in a row of the tile, which made the tile a wide
 * panel with a card squeezed into one corner and an empty half beside it. The
 * owner described both halves of that exactly: the cards belong above, the
 * panel underneath is the player with their name and their number, and the
 * cards were far too small.
 *
 * So they leave the flow. The tile goes back to being a tile, the cards sit on
 * top of it and overlap it slightly the way cards in front of a player do, and
 * they are drawn at the hero's own scale rather than at a badge's.
 *
 * The seat's collision box in `geometry.ts` counts them, which is why this is
 * safe: they are out of the tile's flow and not out of the layout's model.
 */
.seat > .hand {
  position: absolute;
  inset-block-end: calc(100% - 6px);
  inset-inline-start: 50%;
  translate: -50% 0;
  display: flex;
  z-index: 2;
}
.seat > .hand > .card { --w: var(--space-card-opponent); }
/* Given a height so the model can mirror it. See --space-seat-meta. */
.seat > .meta { block-size: var(--space-seat-meta); }
.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) * -.45);
  rotate: 0deg;
}

/* The eight 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); }

/*
 * All in, from ADR 0009, and it is a third thing.
 *
 * It is not `out`, which is the seat that folded and is finished, and it is
 * not `locked`, which is a seat that has answered this round and will be
 * asked again. An all in seat is alive with its whole stake committed, the
 * barrier no longer waits on it, and it makes no further choice. So it keeps
 * the locked ring, because locked is exactly what it is and it stays locked
 * for the rest of the hand, and it takes its own mark: two chevrons pointing
 * in, and never the check, which would say this round only.
 *
 * The accent budget is unchanged. This is the same use as the locked ring,
 * held longer, and not a fifth place for volt to appear.
 */
.seat[data-state="allin"] {
  --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 3.5l4 4.5-4 4.5M9 3.5l4 4.5-4 4.5' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
.seat[data-state="allin"] > .avatar > .badge { display: grid; background: var(--volt); }
/*
 * ALL IN, said in words, above the player.
 *
 * The owner asked for it by name: a green ring round the seat and the words
 * over the character. The ring was already here and the words were not, and a
 * ring alone is a colour a player has to have been taught. This is the one
 * state at a table that changes what every other seat should do, so it is the
 * one state that gets a label.
 *
 * Above the cards rather than over them, because covering a card to announce
 * something about the hand is the trade this interface never makes.
 */
.seat[data-state="allin"] > .hand::after {
  content: "ALL IN";
  position: absolute;
  inset-block-end: calc(100% + 2px);
  inset-inline-start: 50%;
  translate: -50% 0;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--volt);
  color: var(--volt-ink);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: var(--step-n2);
  letter-spacing: .06em;
  line-height: 1.4;
  white-space: nowrap;
  z-index: 2;
}
.seat[data-state="allin"] > .hand { position: relative; }
/* The hero says it too, and in the same words. */
.hero-seat[data-state="allin"] > .hand { position: relative; }
.hero-seat[data-state="allin"] > .hand::after {
  content: "ALL IN";
  position: absolute;
  inset-block-end: calc(100% + 6px);
  inset-inline-start: 50%;
  translate: -50% 0;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--volt);
  color: var(--volt-ink);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: var(--step-n1);
  letter-spacing: .06em;
  white-space: nowrap;
  z-index: 2;
}
/* Its chips are on the table and not in its hands, so the stack reads as the
   zero it is rather than as a figure the player might still act on. */
.seat[data-state="allin"] .stack { color: var(--ink-faint); }

/*
 * Folded: grey, crossed, and ringed in the colour of leaving.
 *
 * The ring was transparent, so a folded seat said what it was with saturation
 * and a cross and nothing else. The owner asked for the ring too, and he is
 * right that the table should be readable by colour alone: grey and volt are
 * the normal two, so the third state gets the third colour and there is no
 * fourth.
 */
.seat[data-state="out"] {
  --ring-c: color-mix(in oklab, var(--danger) 55%, 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 > .meta { overflow: hidden; }
.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); }
  /* Centred under the pod, not tucked into its left corner. The tile measures
   symmetric and read crooked, because the one element below the panel was the
   only thing in it that was not centred. */
.seat > .bet { justify-self: center; }
.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-compact); }
  .seat > .meta { block-size: var(--space-seat-meta-compact); }
}

/* --------------------------------------------------------------- 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);
}
/* The three of ADR 0009. FOLD is struck through and faint, DOUBLE keeps the
   resting tile, ALLIN takes the accent because it is the one that ends the
   seat's decisions. */
.choice[data-c="FOLD"] { background: var(--surface-pod); color: var(--ink-faint); text-decoration: line-through; }
.choice[data-c="ALLIN"] { background: var(--volt); color: var(--volt-ink); }
.choice[data-c="ALLIN"] > .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. */
/*
 * The status band. The top row of the table, and the only thing above the felt.
 *
 * It carried a volt tile with a B, the word BLTZ and a line of dealer voice.
 * The owner asked for that to go, and he was right twice over: it named a
 * state he could already see, and it sat in the strip of a phone screen worth
 * the most. What it carries now is the countdown with the round's seconds
 * inside it and the pot beside it. ADR 0017.
 *
 * Both fill the band as the round runs. The fill is a single gradient stop
 * driven by `--p`, the same fraction the clock arc reads, so the band and the
 * ring can never disagree about how much time is left.
 */
.dealer {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  min-block-size: 44px;
  padding-inline: var(--space-gutter);
  background:
    linear-gradient(
      to right,
      color-mix(in oklab, var(--volt) 12%, transparent) 0 calc(var(--p, 0) * 100%),
      transparent calc(var(--p, 0) * 100%)
    ),
    var(--ground);
  z-index: var(--layer-hud);
}
/*
 * The pod: the clock and the two figures, in the middle of the felt.
 *
 * The owner asked for the clock to be bigger and for the band to go back to
 * the wordmark and the chat. Both are the same fix: the numbers a hand is
 * about belong on the table, which is also the only place there is room to
 * draw a ring somebody can read.
 */
.arena > .pod {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  display: grid;
  justify-items: center;
  gap: 6px;
  z-index: var(--layer-clock);
  pointer-events: none;
}
.arena > .pod > .pot-box,
.arena > .pod > .pot-box.own {
  display: grid;
  grid-auto-flow: column;
  align-items: baseline;
  gap: 6px;
}

/*
 * The clock, inside the status band.
 *
 * On the felt it was 86 px with the pot in its middle, the seconds on a badge
 * hanging below it, and the ready count tucked under the pot. None of that
 * fits a 44 px band and none of it should: the owner asked for the seconds
 * inside the ring and the pot beside it, which is two numbers in two places
 * instead of four numbers in one.
 *
 * So the ring is the height of the band, the seconds live in its middle and
 * are always on, and the ready count sits beside it rather than hiding inside
 * a square it no longer fits in.
 */
.arena > .pod .clock-wrap {
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: 8px;
}
/*
 * Big, because it is the one thing on the felt a player checks without
 * looking away from the cards, and because the owner said it was too small.
 * The token is a clamp against the viewport, so this is the size it was
 * designed at rather than a number invented here.
 */
.arena > .pod .clock {
  --size: var(--space-clock);
}
.arena > .pod .clock .secs {
  position: static;
  translate: none;
  min-inline-size: 0;
  padding: 0;
  background: none;
  color: var(--ink);
  /* The hand's sixty seconds, in the middle of the ring, at the size of the
     one number on the felt that is always true. */
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-variant-numeric: var(--font-numeric);
  font-size: var(--step-3);
  line-height: 1;
  /* Always on. The badge below the ring only appeared when time was short,
     which is the one moment a player is least able to go looking for it. */
  opacity: 1;
  transition: color var(--motion-clock-to-amber) var(--motion-ease-out);
}
.arena > .pod .clock[data-t="amber"] .secs { color: var(--risk); }
.arena > .pod .clock[data-t="pulse"] .secs { color: var(--danger); }
/* Beside the ring, not under a pot that is no longer there. */
.arena > .pod .clock-wrap > .ready-count {
  position: static;
  translate: none;
  font-size: var(--step-n2);
}
/* A count of seats matters while they fill and a stack matters while they
   play, and they are never both the answer. */
.table[data-phase="round"] .ready-count,
.table[data-phase="showdown"] .ready-count,
.table[data-phase="payout"] .ready-count { display: none; }
.table[data-phase="waiting"] .pot-box.own { display: none; }

/*
 * The pot, beside the clock rather than inside it.
 *
 * The ring used to hold the countdown and the pot together, and a ring that
 * holds two numbers shows neither well at 44 px. Separating them is what lets
 * the pot be the size a pot should be.
 */
.pot-box {
  display: grid;
  grid-auto-flow: column;
  align-items: baseline;
  justify-content: start;
  gap: 6px;
  min-inline-size: 0;
}
.pot-box > .pot-label {
  font-family: var(--font-sans);
  font-size: var(--step-n2);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--ink-muted);
}
.pot-box.own > .pot-label { color: var(--ink-faint); }
.pot-box > .chips-own,
.pot-box > .pot {
  font-family: var(--font-display);
  font-variant-numeric: var(--font-numeric);
  font-weight: var(--font-weight-display);
  font-size: var(--step-1);
  line-height: 1;
  color: var(--ink);
}
/*
 * The dealer's voice. It keeps its element and it has moved under the board,
 * which is where the hand name already was. Announced rather than read on
 * demand: a player watching the board must not look away to catch it.
 */
/*
 * The dealer's voice. A pill above the board, and silent most of the time.
 *
 * It was a line of loose text beside the cards reading "everyone chooses",
 * which is a label for a state the cards already show, in the one place the
 * player is already looking. The owner asked for it away from them and, if it
 * stays, in a box that belongs to the brand.
 *
 * So: the pod surface, the small radius, the hairline. The same three things
 * every other dense object in this interface is made of, and nothing invented
 * for it.
 */
.arena > .center > .dealer-line {
  margin: 0 0 var(--space-sm);
  justify-self: center;
  padding: 5px 12px;
  border-radius: var(--space-radius-small);
  background: var(--surface-pod);
  box-shadow: inset 0 0 0 1px var(--line);
  font-family: var(--font-sans);
  font-weight: var(--font-weight-body);
  font-size: var(--step-n2);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
  transition: opacity var(--motion-state-swap) var(--motion-ease-out);
}
.dealer-line:empty { display: none; }
.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;
}

/*
 * On a wide screen the band centres.
 *
 * The owner asked for the countdown and the pot to be centred above the cards
 * and above the dealer on the desktop, so that they are where the eye already
 * is. On a phone the band is nearly full of them and centring changes nothing;
 * on a 1440 px window it is the difference between a readout in a corner and
 * the two numbers the hand is about sitting over the board.
 */
/* The chat sits at the right end of the band, where it was before it became a
   floating button over the felt. */
.dealer > .chat-open { justify-self: end; grid-column: 3; }
.dealer > .dealer-count { grid-column: 2; justify-self: end; }

/* ------------------------------------------------------------ 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;
  /*
   * Four columns of one fraction each, so every button is the same width.
   *
   * FOLD used to take half the share of the others, which bought the width
   * that keeps "1 000" on one line in a 306 px landscape bar. ADR 0016 asks
   * for four equal buttons and the width has to come from somewhere: it comes
   * from STAY and FOLD, which carry no amount at all now, so the two widest
   * numbers have three columns between them instead of two.
   *
   * The gap is `--space-fold-gap` between every pair and not only beside FOLD.
   * That is what makes the four exactly equal: a wider gap in one place and a
   * narrower one elsewhere cannot be expressed without taking the difference
   * out of a button, and then they are not the same size any more. The
   * misclick guard the gap exists for is satisfied either way, and it is now
   * satisfied between every pair rather than one.
   */
  grid-template-columns: repeat(4, minmax(0, 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);
}
/* However many commits the rules offer, and never a fixed three.
   ADR 0009 leaves two, DOUBLE and ALLIN, and in the third round they cost the
   same and one of them is hidden, so the bar has to read with one button as
   well as with three. Auto columns give each visible button an equal share
   and a hidden one no track at all. */
/*
 * A choice that does not differ from another.
 *
 * In the third round a seat that doubled in the second reaches the stake with
 * either DOUBLE or ALLIN, so DOUBLE is shown and inert while ALLIN is live.
 * ADR 0009 forbids offering two buttons that do the same thing and ADR 0016
 * asks for four buttons that never move, and this is where the two meet: the
 * button keeps its place and stops being an offer.
 */
/*
 * Extrusion, and it goes away when the button goes down.
 *
 * A key on a keyboard has a lit top edge, a dark base and a shadow, and it
 * loses all three when it is pressed because it is no longer above anything.
 * That is the whole trick: the press is not a colour change, it is the object
 * arriving at the surface. The travel is two pixels, which is the distance the
 * eye reads as a press and the thumb does not read as a jump.
 *
 * `translate` and `box-shadow` only. No layout property moves, so a press
 * cannot reflow the bar, and the compositor can run both.
 */
.act {
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, white 12%, transparent),
    inset 0 -2px 0 color-mix(in oklab, black 45%, transparent),
    0 2px 3px color-mix(in oklab, black 35%, transparent);
  transition:
    translate var(--motion-press-in) var(--motion-ease-out),
    box-shadow var(--motion-press-in) var(--motion-ease-out),
    background var(--motion-state-swap) var(--motion-ease-out);
}
.act:active:not(:disabled) {
  translate: 0 2px;
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, white 6%, transparent),
    inset 0 -1px 0 color-mix(in oklab, black 30%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .act { transition: background var(--motion-state-swap) var(--motion-ease-out); }
  .act:active:not(:disabled) { translate: none; }
}

.act[data-same="yes"] {
  opacity: var(--alpha-dim);
  filter: saturate(0.4);
  cursor: default;
}

/* The three staking choices are grid items of the bar itself, so they share
   one set of equal columns with FOLD. A wrapper that made its own grid is what
   made FOLD a different width from the rest. */
.bar > .acts { display: contents; }
@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); }
/* Hidden is not disabled, and it is the one case where a button leaves.
   `.act` declares `display: grid`, which beats the browser's own rule for
   `[hidden]`, so the rule has to be written here or `hidden` does nothing at
   all. ADR 0009: in the third round DOUBLE and ALLIN cost the same and the
   bar shows one of them, not two buttons that do one thing. */
.act[hidden] { display: none; }
/* 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;
  /* Three rows since ADR 0017: the status band, the felt and the bar. The
     hero used to own a fourth between the last two. */
  grid-template-rows: auto 1fr 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);
  /*
   * What a closed ring keeps between its outermost rows and the arena's own
   * edges: half a seat, plus four. The seat box is 70 px, measured in the
   * browser and held by geometry.test.ts, so this is 35 and 4.
   *
   * It is the only number the closed layouts need. `--ry` is capped so the
   * ring's three rows span no more than the arena minus two of these, and
   * `--cy` hangs the bottom row one of these off the lower edge. Mirrored as
   * `ringInset` in table/geometry.ts.
   */
  /*
   * The seat box, built from the same parts `geometry.ts` adds up.
   *
   * It was a literal on both sides, 39 and then 49 and then 60, and each of
   * those was right until the seat changed. The seat grew two cards above its
   * name and the number was wrong three times in one afternoon, so it is
   * derived now and there is one place to be wrong.
   */
  /* The tile, plus the cards that hang above it. The cards left the tile's
     flow, so the two are added rather than one containing the other. */
  --seat-box: calc(
    4px + max(var(--space-seat-avatar), var(--space-seat-meta)) + 8px + 22px
    + max(0px, calc(var(--space-card-opponent) * 7 / 5 - 6px))
  );
  --seat-box-compact: calc(
    3px + max(var(--space-seat-avatar-compact), var(--space-seat-meta-compact))
    + 5px + 18px
    + max(0px, calc(var(--space-card-opponent-compact) * 7 / 5 - 6px))
  );
  --ring-inset: calc(var(--seat-box) / 2 + 4px);
  /*
   * What the ring clears at the BOTTOM, which is not what it clears at the
   * top any more.
   *
   * The top of the ring is a seat tile and clears by half of one, which is
   * `--ring-inset`. The bottom is the hero, and the hero carries the hole
   * cards a player reads under pressure, so its box is a card height plus the
   * name line under it. Using one inset for both is what pushed the hero
   * through the bottom of the table. Mirrored as `heroBoxH / 2 + ringClearance`
   * in geometry.ts. ADR 0017.
   */
  --hero-box: calc(var(--card-hero) * 7 / 5 + 20px);
  --hero-inset: calc(var(--hero-box) / 2 + 4px);
}
/* 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;
}
/* No `.hero-row` in portrait any more. The hero is a seat on the ring, inside
   the arena, and the band it used to own went back to the felt. ADR 0017. */
.table > .bar-row {
  grid-row: 3;
  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)));
  /*
   * The tile sits on the ring and the cards reach above it, so the slot is
   * translated by its own height and not by the whole box. Mirrored as the
   * `overhang / 2` shift in `onEllipse`: centring the whole thing on the ring
   * would push every tile down by half a card.
   */
  translate: -50% -50%;
  z-index: var(--layer-seats);
}
/* The pot is the centre of the table, so the pot is what sits on the arena's
   centre.
 *
 * Measured on the deployed build the arena's middle was y 343 while the clock
 * and its pot sat at 294 and the board at 380: the two things that are
 * supposed to BE the centre straddled it, 49 px above and 37 below, and the
 * composition had no anchor. Centring the whole stack instead only moves the
 * straddle down, because the stack is a clock over a board and its middle
 * falls in the gap between them.
 *
 * So the stack hangs from the clock's own middle: `top` is the arena's centre
 * and the translate lifts the group by half a clock, which puts the pot
 * exactly on it and lets the board fall directly under it. One centre, and it
 * is the arena's. */
/*
 * The centre of the felt, which is the board and nothing else.
 *
 * It used to hold the clock above the board and hang the group by half a clock
 * so that the pot landed on the arena's middle exactly. The clock is in the
 * status band now, so the group is its own middle and the translate is the
 * plain one. ADR 0017.
 */
.arena > .center {
  position: absolute;
  left: 50%;
  top: 50%;
  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.
 *
 * A column now and not a row: the cards on top and the name and stack under
 * them, the way every other seat carries its label. That is not only
 * consistency. Beside the cards the box is 247 px on a 360 px screen, sixty
 * nine per cent of the width, and the two seats next to it cannot clear it at
 * any radius that still fits. Under them it is about 150, and they can.
 */
.hero-seat {
  display: grid;
  justify-items: center;
  gap: 2px;
}
/* In portrait it is seat zero on the ring, positioned exactly like the other
   eight and by the same `--a`. ADR 0017. */
.table[data-orient="portrait"] .arena > .hero-seat {
  position: absolute;
  left: calc(50% + cos(var(--a)) * var(--rx));
  top: calc(var(--cy, 50%) + sin(var(--a)) * var(--ry));
  translate: -50% -50%;
  z-index: var(--layer-hud);
}
.hero-seat > .hand > .card { --w: var(--card-hero); }
/* Empty on the table, because the name and the stack moved to the status band.
   The element stays so the demo page and the reorientation keep their nodes. */
.hero-seat > .meta:empty { display: none; }
.hero-seat > .meta {
  display: grid;
  grid-auto-flow: column;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  line-height: 1;
}
.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 hangs at 57% and not 55%, which drops the pair as far as the top
   of the clock allows. Three handed is the one format the ring cannot close:
   two opponents make one row and the row the hero would close is the hero's
   own, outside the arena. The residual is measured in
   docs/audit/008-frente-mesa.md rather than hidden. */
/*
 * Three seats is two opponents, and two opponents cannot close a ring.
 *
 * `seatAngle(3, i)` puts them at 210 and 330 degrees, whose sines are both
 * minus a half, so they sit at one height however the ellipse is tuned. The
 * closed ring formula the other formats use cannot help here: there is no
 * third angle to reach the bottom with.
 *
 * So the arena stops being the leftover row and becomes only as tall as the
 * two seats, the clock and the board need. What it gives up goes to the hero,
 * which is where design/REFERENCES-MOBILE-POKER.md says the space belongs
 * anyway: the hero's own cards are the largest thing on a poker screen in
 * every app measured, and at three seats there is finally room to mean it.
 */

/* And the hero's cards grow into it, which is the whole reason the space was
   taken back. At three seats there is finally room to mean the rule that
   design/REFERENCES-MOBILE-POKER.md found in every app measured. */
/* Three handed has the fewest seats and therefore the most felt, so the hero's
   cards are the largest they get. The rule moved from the hero row, which no
   longer exists, onto the seat itself. */
.table[data-format="3"][data-orient="portrait"] .hero-seat {
  --card-hero: min(calc(var(--space-card-board) * 2.6), 21vh);
}
/* The ellipse is the one it always was. Hanging the ring low was tried and it
   put both opponents on the clock, because with two of them there is no angle
   that reaches the bottom without crossing the middle. The cap above is what
   closed the hole, not the ring. */
/* `--cy` is 48 and not 57. With the arena capped, the two opponents at 57 sat
   on the clock by 30 px, which the canvas harness measured and the app's own
   slot to slot probe could not see. Raising the ring is the right half of the
   trade: the pair belongs above the pot, and the space under the pot is the
   board's. */
/* Three handed closes on the hero like the others now, so it takes the same
   two calcs as six and nine rather than a fixed ring and a capped arena. Two
   opponents make one row at -ry/2, so the span is one and a half. */
.table[data-format="3"][data-orient="portrait"] .arena {
  --rx: 30%;
  --ry: min(48%, calc((100% - var(--ring-inset) - var(--hero-inset)) / 1.5));
  --cy: calc(100% - var(--hero-inset) - var(--ry));
}
.table[data-format="3"][data-orient="portrait"] {
  --slot-w: 120px;
}

/* 6 portrait. Vertical ellipse, five in an arc, and the ring now CLOSES.
   Six seats put rows at -ry, -ry/2 and +ry/2 of the ring's centre. At
   ry 40% and cy 54% the lowest row landed at 74% of the arena and left
   130 px, 15.4 per cent of a 390x844 screen, of table with nothing on it.
   At ry 55% and cy 64% the rows fall at 9, 36.5 and 91.5 per cent: the
   bottom pair sits on the arena's lower edge, where the hero closes the
   ring from its own band, and the top seat still clears the dealer band.
   Mirrored in geometry.ts, held by geometry.test.ts. */
.table[data-format="6"][data-orient="portrait"] .arena {
  --rx: 36%;
  /* Six spans 1.5 * --ry because the ring hangs: rows at -ry, -ry/2 and +ry/2.
     The hero joins it at +ry, so the bottom clearance is the hero's and not a
     seat's. ADR 0017. */
  --ry: min(40%, calc((100% - var(--ring-inset) - var(--hero-inset)) / 2));
  --cy: calc(100% - var(--hero-inset) - var(--ry));
}
.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: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="3"][data-orient="landscape"] .arena > .slot[data-i="2"] { --x: 85%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="3"][data-orient="landscape"] { --slot-w: 128px; }

/* 6 landscape. Five across the top and the sides, hero below, and not an
   ellipse. A phone sideways has about 240 px of arena and a seat is 118.8 px
   tall since it grew two cards above its name, so an ellipse of six puts its
   side pairs one radius apart and the radius caps under a seat. Measured as two
   overlapping boxes down each side. ADR 0017. */
.table[data-format="6"][data-orient="landscape"] .arena > .slot[data-i="1"] { --x: 8%; --y: calc(100% - var(--seat-box) / 2 - 4px); }
.table[data-format="6"][data-orient="landscape"] .arena > .slot[data-i="2"] { --x: 20%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="6"][data-orient="landscape"] .arena > .slot[data-i="3"] { --x: 50%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="6"][data-orient="landscape"] .arena > .slot[data-i="4"] { --x: 80%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="6"][data-orient="landscape"] .arena > .slot[data-i="5"] { --x: 92%; --y: calc(100% - var(--seat-box) / 2 - 4px); }
.table[data-format="6"][data-orient="landscape"] .arena {
  --rx: min(40%, 480px);
  /* The same closed ring the portrait layout draws, and for the same reason:
     the three rows span 1.5 times --ry, so the old 40% ring reached only to
     79% of the arena and left 132 px of empty table under the lowest pair at
     1440x900. The cap is what keeps it honest on a phone held sideways,
     where the arena is 226 px tall and the ring shrinks to fit rather than
     climbing into the dealer band. */
  --ry: min(55%, calc((100% - 2 * var(--ring-inset)) / 1.5));
  /* The ring hangs. See --cy on .arena > .slot. A ring of six reaches twice
     as far up as it does down, so a centred one puts the top seat inside the
     dealer band at 844x390. */
  --cy: calc(100% - var(--ring-inset) - var(--ry) / 2);
}
.table[data-format="6"][data-orient="landscape"] { --slot-w: 120px; }
/*
 * Six landscape, and the one place the pot does not sit on the arena's
 * centre.
 *
 * On a phone held sideways the arena is about 226 px tall and has to hold a
 * 40 px dealer band, a 70 px top seat and a 71 px board. Centred, the board
 * climbs into the seat, which is the collision ADR 0004 is about. Four per
 * cent down is what buys that seat its window, and it costs the centre 9 px.
 * Above 600 px of viewport height there is room for both and the pot goes
 * where it belongs.
 */
.table[data-orient="landscape"] .arena > .center { top: 62%; }
/* Upright the board sits under the clock rather than in the middle of the
   felt. A phone is not read from above. Mirrored in geometry.ts. */
.table[data-orient="portrait"] .arena > .center { top: 28%; }


/* 9 landscape. One ellipse, with the angles chosen rather than divided.

   Twelve was a stadium here, because on an ellipse of twelve the seats at 120
   and 240 degrees fall at half radius and land on the clock. Nine has three
   seats down each side, two across the top and the hero at the foot, and the
   clock has left the middle, so the shape that broke is the shape that works.
   The angles live in seatAngle() and arrive as --a. ADR 0017. */
/* Nine held sideways is not a ring. The arena is about 240 px tall, the full
   seat tile is 70 and eight of them across an 844 px arena need 896, so
   neither a stack down each side nor one row at full size fits. Eight compact
   tiles need 672 and there is room to spare. ADR 0004 reached this for twelve
   and the arithmetic has not changed. */
.table[data-format="9"][data-orient="landscape"] { --slot-w: var(--space-seat-compact); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="1"] { --x: 17%; --y: calc(100% - var(--seat-box) / 2 - 4px); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="2"] { --x: 6%; --y: 50%; }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="3"] { --x: 18%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="4"] { --x: 39%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="5"] { --x: 61%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="6"] { --x: 82%; --y: calc(var(--seat-box) / 2 + 4px); }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="7"] { --x: 94%; --y: 50%; }
.table[data-format="9"][data-orient="landscape"] .arena > .slot[data-i="8"] { --x: 83%; --y: calc(100% - var(--seat-box) / 2 - 4px); }
/* The query measures .table, so the rule lives on a descendant. The slot
   inherits from the arena. */
@container table (max-width: 1100px) {
  .table[data-orient="landscape"] .ready-count::after { content: attr(data-short); }
}

/* The compact tile wherever the screen runs out, which is a screen rule rather
   than a format one. Held sideways a phone runs out of height: 176 px of arena
   at 800x360 against a 118.8 px tile. Held upright a small phone runs out of
   width: at 360 the six seat ring has 104 px tiles either side of a board and
   its lowest pair reaches the hero's own cards. Both measured, both mirrored in
   geometry.ts as the `tight` branch. */
@media (orientation: landscape) and (max-height: 599px) {
  .table[data-orient="landscape"] { --slot-w: var(--space-seat-compact); }
  .table[data-orient="landscape"] > .arena { --seat-box: var(--seat-box-compact); }
}
@media (orientation: portrait) and (max-width: 379px) {
  .table[data-orient="portrait"] { --slot-w: var(--space-seat-compact); }
  .table[data-orient="portrait"] > .arena { --seat-box: var(--seat-box-compact); }
}

/* 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);
}
/* In landscape the centre is a row and not a stack: the clock on the left,
   the board on the right, both on one axis. The clock's middle is already the
   row's middle, so the group is centred outright and the portrait lift by
   half a clock would push it off. */
.table[data-orient="landscape"] .arena > .center {
  grid-auto-flow: column;
  align-items: center;
  gap: 16px;
  top: 50%;
  translate: -50% -50%;
}

/* 9 portrait. The hard case, and it is an ellipse now rather than a grid.

   Twelve needed a three by six grid here because eleven opponents could not be
   placed by trigonometry inside 360 px without landing on the clock. Nine can:
   three down each side at the compact slot, two across the top, the hero at
   the foot. The compact tile is the same one ADR 0004 built, and it is still
   what makes the sides pass the board at 360 px.

   --ry is 44% and not the 55% six uses. Six reaches twice as far up as down
   so its ring hangs and its span is 1.5 times --ry. Nine spans a full 2 times
   --ry, symmetric about a centred --cy. ADR 0017. */
.table[data-format="9"][data-orient="portrait"] {
  --slot-w: var(--space-seat-compact);
}
.table[data-format="9"][data-orient="portrait"] .arena {
  --rx: 32%;
  /* The ring spans a full 2 * --ry, because the hero is at +ry and the top
     pair is at -ry. Six spans 1.5 because it hangs. */
  --ry: min(48%, calc((100% - var(--ring-inset) - var(--hero-inset)) / 2));
  --cy: calc(100% - var(--hero-inset) - var(--ry));
}

/* 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;
  }
}
