/* Table life: the club's characters at their seats, what they say, and the seal a call or a
   win earns. Strength and scale arrive as variables from dist/table-life.js, so drawing the
   board again never restarts a moment. Everything here is decorative and hidden from
   assistive technology; the announcer and the win status already speak every event. */

.seat-character {
  position: absolute;
  z-index: 6;
  display: block;
  width: 58px;
  height: 58px;
  overflow: hidden;
  pointer-events: none;
  /* The career avatars' sage: pale hair and skin keep their outline on it, where the cream paper
     let them fade into the disc. */
  background: #d2dfc6;
  border: 2px solid var(--club-teal, #1f6f66);
  border-radius: 50%;
  box-shadow: 0 2px 6px #2b241c33;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease;
}
.seat-character img {
  display: block;
  width: 100%;
  height: auto;
  /* The bust sits low in its frame; lift the face into the medallion. */
  transform: translateY(-4%) scale(1.18);
  transform-origin: 50% 30%;
  /* The portraits are loose 640×720 busts, shown whole in the career and the replay. At a seat the
     face fills the disc instead (the head about 88% of it, the eyes near the middle): `scale` and
     `translate` compose with the transform the breathing and mood animations below move. */
  scale: 1.21;
  translate: 0 -9.3%;
}
.seat-character[data-mood='joy'] {
  border-color: var(--club-mustard, #e2b654);
  box-shadow:
    0 0 0 3px #e2b65455,
    0 2px 8px #2b241c33;
}
.seat-character[data-mood='shock'] {
  border-color: var(--club-red, #b7472f);
}
.seat-character[data-mood='dismay'] {
  border-color: var(--club-muted, #716757);
  filter: saturate(0.85);
}

/* Desktop table: the face belongs to the player's name badge and is positioned from it, so it
   follows the name and never moves the rack. The side seats' rack-and-name block is turned a
   quarter, so there "before the name" is up the table edge, and the face turns back upright. */
.classic-table .seat .player-badge {
  position: relative;
}
.classic-table .seat .seat-character {
  top: 50%;
  width: 42px;
  height: 42px;
}
.classic-table .seat-top .seat-character {
  right: calc(100% + 8px);
  transform: translateY(-50%);
}
.classic-table .seat-left .seat-character {
  right: calc(100% + 8px);
  transform: translateY(-50%) rotate(-90deg);
}
.classic-table .seat-right .seat-character {
  left: calc(100% + 8px);
  transform: translateY(-50%) rotate(90deg);
}

.seat-speech {
  position: absolute;
  /* Above the seal (20): the seal is gone in a second and a half, the line is still being read. */
  z-index: 21;
  /* Sized by its words, not by the narrow badge it hangs from. */
  width: max-content;
  max-width: 200px;
  margin: 0;
  padding: 6px 10px;
  font: 600 13px/1.3 var(--club-body-font, system-ui, sans-serif);
  color: var(--club-ink, #2b241c);
  text-align: left;
  pointer-events: none;
  background: var(--club-paper, #fdf8ee);
  border: 1px solid var(--club-line, #dccfb6);
  border-radius: 12px;
  box-shadow: 0 2px 8px #2b241c26;
  /* No stray last word on a line of its own. */
  text-wrap: pretty;
  /* Each bubble carries its own strength (dist/table-life.js), so one seat's line fading never
     restyles the rest of the table. */
  opacity: var(--speech-alpha, 0);
}
/* Every character seat has a bubble in every layout; a quiet seat's is empty. */
.seat-speech:empty {
  display: none;
}
/* The tail: a small turned square on the edge nearest the speaker's portrait. By default it
   sits on the top edge near the left corner and points up; each placement below moves it. */
.seat-speech::before {
  content: '';
  position: absolute;
  top: -5px;
  left: 12px;
  width: 8px;
  height: 8px;
  background: inherit;
  border: 0 solid var(--club-line, #dccfb6);
  border-width: 1px 0 0 1px;
  transform: rotate(45deg);
}
/* Points right, from a bubble to the left of its speaker. */
.play-area > .classic-table .seat-top .player-badge > .seat-speech::before {
  top: calc(50% - 4px);
  right: -5px;
  left: auto;
  border-width: 1px 1px 0 0;
}
/* Up, from under a speaker on the right-hand side: the tail sits near the right corner. */
.table-life-right > .seat-speech::before {
  right: 12px;
  left: auto;
}

/* Desktop. A side seat's face leads the horizontal name label above its rack, and its line
   hangs just under that face from an anchor beside it: the left seat's runs inwards to the right,
   the right seat's inwards to the left, over empty felt and the seat's own concealed backs.
   Across, the line sits to the left of the face, pointing at it. */
.desktop-seat-label .speech-anchor {
  position: relative;
  flex: 0 0 0;
  align-self: stretch;
  /* Cancels the label's gap, so the name keeps its place. */
  margin-left: -7px;
}
.play-area > .classic-table .desktop-seat-label .speech-anchor .seat-speech {
  top: calc(100% + 6px);
  max-width: 200px;
  font-size: 12px;
}
.play-area > .classic-table .seat-left .desktop-seat-label .speech-anchor .seat-speech {
  left: -38px;
  border-top-left-radius: 3px;
}
.play-area > .classic-table .seat-right .desktop-seat-label .speech-anchor .seat-speech {
  right: 0;
  border-top-right-radius: 3px;
}
.play-area > .classic-table .seat-right .desktop-seat-label .speech-anchor .seat-speech::before {
  right: 12px;
  left: auto;
}
@media (min-width: 1360px) and (not ((max-width: 1400px) and (max-height: 900px) and (orientation: landscape) and (pointer: coarse))) {
  .desktop-seat-label .speech-anchor {
    margin-left: -8px;
  }
  .play-area > .classic-table .seat-left .desktop-seat-label .speech-anchor .seat-speech {
    left: -42px;
  }
}
.play-area > .classic-table .seat-top .player-badge > .seat-speech {
  top: 50%;
  right: calc(100% + 60px);
  left: auto;
  max-width: 200px;
  transform: translateY(-50%);
}

/* The seal: a vermilion chop pressed near the caller's side of the table. */
.call-stamp {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 20;
  display: grid;
  justify-items: center;
  gap: 2px;
  min-width: 108px;
  padding: 10px 16px 9px;
  color: var(--club-paper, #fdf8ee);
  text-align: center;
  pointer-events: none;
  background: var(--club-red, #b7472f);
  border: 3px double var(--club-paper, #fdf8ee);
  border-radius: 10px;
  box-shadow: 0 6px 18px #2b241c40;
  opacity: var(--stamp-alpha, 0);
  transform: translate(-50%, -50%) rotate(-4deg) scale(var(--stamp-scale, 1));
}
.call-stamp[data-seat='left'] {
  left: 24%;
}
.call-stamp[data-seat='right'] {
  left: 76%;
}
.call-stamp[data-seat='top'] {
  top: 22%;
}
.call-stamp[data-seat='bottom'] {
  top: 78%;
}
.stamp-glyph {
  font:
    400 54px/1 'DM Serif Display',
    'Noto Serif SC',
    'Songti SC',
    'Noto Sans SC',
    serif;
}
.stamp-word {
  font: 800 13px/1 var(--club-body-font, system-ui, sans-serif);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.stamp-who {
  font: 600 13px/1.2 var(--club-body-font, system-ui, sans-serif);
  opacity: 0.92;
}

/* A win is the same seal grown into a banner: who won, how many tai and with what. */
.call-stamp.call-stamp-win {
  top: 50%;
  left: 50%;
  min-width: 220px;
  padding: 14px 22px 12px;
  transform: translate(-50%, -50%) rotate(-2deg) scale(var(--stamp-scale, 1));
  background: #194c3b;
  border-color: #efd58d;
  color: #fff7df;
}
.classic-table.life-stamping[data-life-stamp='win']
  :is(.desktop-focus, .portrait-focus, .landscape-focus) {
  /* The celebration and settled result take turns, at every breakpoint. Keep geometry
     unchanged so the hand, rivers and next-hand action never jump. */
  visibility: hidden;
}
.classic-table.hand-won .seat-speech:not(.is-showdown) {
  /* The same character reaction belongs to the celebration now, not over revealed tiles. The
     one showdown line is the exception: it comes later, as that seat's own rack turns over. */
  display: none;
}
.stamp-reaction {
  grid-column: 1 / -1;
  max-width: 260px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid #efd58d66;
  color: #fff7df;
  font: 500 12px/1.35 var(--club-body-font, system-ui, sans-serif);
  overflow-wrap: anywhere;
}
.call-stamp-win .stamp-glyph {
  font-size: 72px;
}
.call-stamp-win .stamp-who {
  font:
    400 22px/1.15 'DM Serif Display',
    'Noto Sans SC',
    Georgia,
    serif;
  opacity: 1;
}
.stamp-patterns {
  max-width: 260px;
  font: 600 12px/1.3 var(--club-body-font, system-ui, sans-serif);
  opacity: 0.9;
}
.call-stamp-limit {
  background: linear-gradient(160deg, #b7472f, #8f2f1c);
  border-color: var(--club-mustard, #e2b654);
}

/* Paper confetti for a limit hand only: club colours, drifting down with the stamp's life. */
.stamp-confetti {
  position: absolute;
  inset: -40px -70px auto;
  height: 0;
}
.stamp-confetti i {
  position: absolute;
  top: 0;
  left: calc(var(--piece) * 7.4%);
  width: 8px;
  height: 12px;
  background: var(--club-mustard, #e2b654);
  border-radius: 1px;
  transform: translateY(calc(var(--life-fall, 0) * (120px + var(--piece) * 9px)))
    rotate(calc(var(--piece) * 47deg + var(--life-fall, 0) * 320deg));
}
.stamp-confetti i:nth-child(3n) {
  background: var(--club-teal, #1f6f66);
}
.stamp-confetti i:nth-child(3n + 1) {
  background: var(--club-paper, #fdf8ee);
}

/* Phone and landscape: the face is an item of the name chip, beside the wind marker and the
   name, so a seat that clips its contents can never hide it. The side chips are turned a
   quarter, so their faces turn back upright and take the top end of the chip. */
:is(.portrait-name, .landscape-name) .seat-character {
  position: static;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  box-shadow: none;
}
:is(.portrait-left .portrait-name, .landscape-seat-left .landscape-name) .seat-character {
  transform: rotate(-90deg);
}
:is(.portrait-right .portrait-name, .landscape-seat-right .landscape-name) .seat-character {
  /* This chip runs up the table edge, so its last item is the one at the top. */
  order: 3;
  transform: rotate(90deg);
}

/* Speech cannot live inside a clipped seat, so it hangs on the felt next to its speaker. */
.table-life-seat {
  position: absolute;
  z-index: 21;
  width: 0;
  height: 0;
  pointer-events: none;
}
.table-life-seat .seat-speech {
  top: 0;
  max-width: 150px;
  padding: 5px 8px;
  font-size: 12px;
}
.table-life-left .seat-speech {
  left: 0;
  border-top-left-radius: 3px;
}
.table-life-right .seat-speech {
  right: 0;
  border-top-right-radius: 3px;
}
/* Phone upright: the side faces top their rails, so each line sits just under its face, over
   that seat's own name. Across, the name row is centred, so the line rides in the row itself,
   right after the face and over that seat's own name, inside the seat. */
.portrait-felt .table-life-left {
  top: 92px;
  left: 4px;
}
.portrait-felt .table-life-right {
  top: 92px;
  right: 4px;
}
.portrait-name .speech-anchor {
  position: relative;
  flex: 0 0 0;
  align-self: stretch;
  /* Cancels the row's gap, so the name row keeps its width. */
  margin-left: -4px;
}
.portrait-name .speech-anchor .seat-speech {
  top: -1px;
  left: 9px;
  max-width: 120px;
  padding: 5px 8px;
  font-size: 12px;
  border-top-left-radius: 3px;
}
.portrait-name .speech-anchor .seat-speech::before,
.landscape-felt .table-life-top .seat-speech::before {
  top: 9px;
  left: -5px;
  border-width: 0 0 1px 1px;
}
/* A short felt (a claim open, a wrapped view switch) lifts the side names to the top. */
@container portrait-felt (max-height: 260px) {
  .portrait-felt :is(.table-life-left, .table-life-right) {
    top: 62px;
  }
}
/* Phone on its side: the side faces head the rails, so those lines hang under them; across,
   the face leads a row that runs along the top edge, and its line sits right beside it. */
.landscape-felt :is(.table-life-left, .table-life-right) {
  top: 44px;
}
.landscape-felt .table-life-left {
  left: calc(var(--rail) - 26px);
}
.landscape-felt .table-life-right {
  right: calc(var(--rail) - 26px);
}
.landscape-felt .table-life-top {
  top: 7px;
  left: calc(var(--rail) + 102px);
}
.landscape-felt .table-life-top .seat-speech {
  left: 0;
  border-top-left-radius: 3px;
}
/* The compact felt (a short screen, or your own sets showing) moves the row across inwards. */
@media (min-width: 560px) and (max-height: 380px) and (orientation: landscape) {
  .landscape-felt .table-life-top {
    left: calc(var(--rail) + 45px);
  }
}
.board:has(.own-melds) .landscape-felt .table-life-top {
  left: calc(var(--rail) + 45px);
}
:is(.landscape-centre, .portrait-centre) .call-stamp {
  min-width: 92px;
  padding: 8px 12px 7px;
}
:is(.landscape-centre, .portrait-centre) .stamp-glyph {
  font-size: 40px;
}
:is(.landscape-centre, .portrait-centre) .call-stamp-win {
  min-width: 0;
  width: min(240px, calc(100vw - 120px));
}
:is(.landscape-centre, .portrait-centre) .call-stamp-win .stamp-glyph {
  font-size: 52px;
}
:is(.landscape-centre, .portrait-centre) .call-stamp-win .stamp-who {
  font-size: 18px;
}

/* A phone on its side has a shallow felt: the seal lies flat in one line and drops the
   pattern list, which the result panel shows anyway. */
.landscape-centre .call-stamp {
  grid-auto-flow: column;
  align-items: center;
  column-gap: 8px;
  min-width: 0;
  padding: 5px 12px;
}
.landscape-centre .stamp-glyph {
  font-size: 30px;
}
.landscape-centre .call-stamp.call-stamp-win {
  min-width: 0;
  padding: 6px 14px;
  /* The compact dock overlaps the felt's lower edge. A flat seal needs neither the
     portrait tilt nor its scale overshoot in this shallow strip. */
  transform: translate(-50%, calc(-50% - 8px));
}
.landscape-centre .call-stamp-win .stamp-glyph {
  font-size: 34px;
}
.landscape-centre .call-stamp-win .stamp-who {
  font-size: 15px;
}
.landscape-centre .stamp-patterns,
.landscape-centre .stamp-reaction,
.landscape-centre .stamp-confetti {
  display: none;
}

/* Living faces. The board is drawn again on every change, so each animation starts as far in
   as the page clock says (--life-epoch) and carries on where the last board left off. */
@keyframes seat-idle {
  0%,
  100% {
    transform: translateY(-4%) scale(1.18) rotate(0deg);
  }
  30% {
    transform: translateY(-5.6%) scale(1.19) rotate(-1.6deg);
  }
  65% {
    transform: translateY(-3.1%) scale(1.18) rotate(1.4deg);
  }
}
@keyframes seat-cheer {
  0%,
  100% {
    transform: translateY(-2%) scale(1.18);
  }
  50% {
    transform: translateY(-9%) scale(1.22);
  }
}
@keyframes seat-sag {
  0%,
  100% {
    transform: translateY(-3%) scale(1.17) rotate(0deg);
  }
  50% {
    transform: translateY(-1.5%) scale(1.16) rotate(2deg);
  }
}
@keyframes seat-heat {
  0%,
  100% {
    box-shadow:
      0 0 0 2px #b7472f66,
      0 2px 6px #2b241c33;
  }
  50% {
    box-shadow:
      0 0 0 5px #b7472f2e,
      0 0 12px 2px #e2b65480;
  }
}
.seat-character img {
  animation: seat-idle 5.2s ease-in-out infinite;
  animation-delay: calc(var(--life-epoch, 0) * -1ms);
}
/* Nobody at a real table breathes in step. */
.seat-character[data-character='lim'] img {
  animation-duration: 6.4s;
}
.seat-character[data-character='rose'] img {
  animation-duration: 4.6s;
}
.seat-character[data-mood='joy'] img {
  animation-name: seat-cheer;
  animation-duration: 0.62s;
}
.seat-character[data-mood='dismay'] img {
  animation-name: seat-sag;
  animation-duration: 3.4s;
}
.seat-character[data-mood='shock'] img {
  animation: none;
}
/* A change of mood arrives with the moment's seal, so the face pops with it. `scale` is its
   own property, which leaves the quarter-turn on the side seats alone. */
.seat-character:not([data-mood='focus']) {
  scale: calc(1 + (var(--stamp-scale, 1) - 1) * 0.9);
}
/* Three sets face up: everyone can see this player is close. */
.seat-character[data-heat][data-mood='focus'] {
  border-color: var(--club-red, #b7472f);
  animation: seat-heat 1.5s ease-in-out infinite;
  animation-delay: calc(var(--life-epoch, 0) * -1ms);
}

/* The winning hand opens one tile at a time, the winning tile last. --life-ms is how far into
   the moment the table is; at rest it is large, so every tile is simply open. */
.hand-won .winning-piece {
  --reveal: clamp(0, calc((var(--life-ms, 99999) - 260 - var(--i, 0) * 65) / 170), 1);
  opacity: var(--reveal);
  scale: calc(0.7 + 0.3 * var(--reveal));
}
.hand-won .winning-piece.winning-tile {
  --reveal: clamp(0, calc((var(--life-ms, 99999) - 330 - var(--i, 0) * 65) / 220), 1);
  scale: calc(1.5 - 0.5 * var(--reveal));
}
.stamp-tai {
  display: inline-block;
  min-width: 0.7em;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--club-mustard, #e2b654);
}
/* Your own win is always a celebration. */
.call-stamp-mine {
  border-color: var(--club-mustard, #e2b654);
}

/* The closing stretch of the wall: a mustard note instead of a vermilion seal, and the tile
   count everyone can already see turns red. */
.call-stamp.call-stamp-last {
  color: var(--club-ink, #2b241c);
  background: var(--club-mustard, #e2b654);
  border-color: var(--club-ink, #2b241c);
  transform: translate(-50%, -50%) rotate(3deg) scale(var(--stamp-scale, 1));
}
@keyframes last-tiles-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}
.last-tiles :is(.focus-wall b, .landscape-wall strong),
body:has(.board.last-tiles) .header-wall-count {
  color: var(--club-red, #b7472f);
  animation: last-tiles-pulse 1.3s ease-in-out infinite;
  animation-delay: calc(var(--life-epoch, 0) * -1ms);
}

/* Tiles down. At the end of a solo hand each rack the player could not see turns face up, a
   beat apart (dist/showdown.js). --reveal climbs from 0 to 1 on each turning element only: the
   rack, its badge and, on phones, its chop. At rest, and whenever nothing writes it, it is 1. */
.showdown-rack {
  display: grid;
  flex: none;
  align-items: center;
  justify-items: start;
}
.showdown-rack > * {
  grid-area: 1 / 1;
}
.showdown-rack > .backs {
  opacity: calc(1 - var(--reveal, 1));
}
.showdown-tiles {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  line-height: 0;
  opacity: var(--reveal, 1);
  /* Tipped towards the table as the backs fade, like a rack laid down. */
  transform: perspective(260px) rotateX(calc((1 - var(--reveal, 1)) * -75deg));
  transform-origin: 50% 100%;
}
.showdown-piece {
  display: inline-grid;
  flex: none;
  line-height: 0;
}
.showdown-piece.suit-start {
  margin-left: 4px;
}
.board .seat .showdown-tiles .tile {
  --tile-w: 24px;
  --tile-h: 32px;
}
/* A side seat's rack runs along a 330px runway (see table-desktop.css); a full hand face up,
   with its suit gaps, shrinks to fit it, as the winner's does. */
.play-area > .classic-table :is(.seat-left, .seat-right) .showdown-tiles .tile.tiny {
  --tile-w: min(24px, calc((306px - 12px) / var(--showdown-count, 13)));
  --tile-h: calc(var(--tile-w) * 4 / 3);
}
/* "Ready · 4 or 7 Dots" under a ready hand's name: vermilion, or grey for no tai. A narrow
   label wraps it between its parts, never inside one. */
.showdown-badge {
  display: inline-block;
  max-width: 100%;
  padding: 2px 8px;
  font: 700 11.5px/1.3 var(--club-body-font, system-ui, sans-serif);
  color: #fff7df;
  text-align: left;
  background: var(--club-red, #b7472f);
  border-radius: 9px;
  opacity: var(--reveal, 1);
}
.showdown-badge.is-dead {
  background: #6f675b;
}
.showdown-badge > span {
  white-space: nowrap;
}
.player-name > .showdown-badge {
  display: block;
  width: fit-content;
  margin-top: 3px;
}
/* A dealer who kept the deal: the "2nd deal" tab under 莊 reaches a few pixels past the marker,
   into the name's column, so the badge under the name steps clear of it. */
.player-badge:has(.keeps-deal) > .player-name > .showdown-badge {
  margin-left: 12px;
}
/* The regular saying their showdown line is read over a neighbour's badge while it is up. */
.play-area > .classic-table .seat:has(.seat-speech.is-showdown) {
  z-index: 3;
}
/* A side seat's badge rides just above its name label, the label's full width: the rack lies
   under the label, and the face and name never move when the hand ends. */
.showdown-row {
  position: absolute;
  right: 0;
  bottom: calc(100% + 4px);
  left: 0;
  display: flex;
  justify-content: inherit;
  pointer-events: none;
}
/* Phones: a small 听 chop by a ready hand's name, pressed in as that rack turns. Tapping it, or
   the rack, opens the hand. */
.showdown-chop {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 19px;
  height: 19px;
  font:
    400 13px/1 'Noto Serif SC',
    'Songti SC',
    'Noto Sans SC',
    serif;
  color: #fff7df;
  cursor: pointer;
  background: var(--club-red, #b7472f);
  border: 1px solid #fff7df;
  border-radius: 4px;
  box-shadow: 0 1px 3px #2b241c55;
  opacity: var(--reveal, 1);
  transform: rotate(-6deg) scale(calc(1.4 - 0.4 * var(--reveal, 1)));
}
.showdown-chop.is-dead {
  background: #6f675b;
}
/* The side chips are turned a quarter; the chop turns back upright, as the face does. */
:is(.portrait-left .portrait-name, .landscape-seat-left .landscape-name) .showdown-chop {
  rotate: -90deg;
}
:is(.portrait-right .portrait-name, .landscape-seat-right .landscape-name) .showdown-chop {
  rotate: 90deg;
}
/* An upright phone's side chip holds two short lines: the name, then the face, markers and tai.
   A dealer's second line is already full (face, 東 and 莊, tai), so the chop rides on the name's
   line instead, at the end beside the face, and takes no room there: its margin gives its width
   back, and the name keeps clear of it by the same amount on both sides, so it stays centred.
   The chip keeps its two lines, and the face and name stay where they were. */
:is(.portrait-left, .portrait-right) .portrait-name > .showdown-chop {
  order: -1;
  margin-inline-start: -21px;
}
.portrait-left .portrait-name > .showdown-chop {
  order: -2;
  margin-inline: 0 -21px;
}
:is(.portrait-left, .portrait-right) .portrait-name:has(> .showdown-chop) > strong {
  padding-inline: 21px;
}
/* A phone on its side runs each side seat's chip along one line that is already longer than the
   rail, so the chop is pressed on the corner of the portrait instead of taking the name's room. */
:is(.landscape-seat-left, .landscape-seat-right) .landscape-name:has(> .showdown-chop) {
  position: relative;
}
:is(.landscape-seat-left, .landscape-seat-right) .landscape-name > .showdown-chop {
  position: absolute;
  top: 12px;
  /* Over the face, which is a flex item at z-index 6. */
  z-index: 7;
}
.landscape-seat-left .landscape-name > .showdown-chop {
  left: 15px;
}
.landscape-seat-right .landscape-name > .showdown-chop {
  right: 15px;
}
/* The hand in its dialog: small enough for a whole rack to fit a phone in two rows. */
.meld-inspection.showdown-inspection {
  flex-wrap: wrap;
  gap: 4px;
  padding: 14px 0 6px;
}
.meld-inspection.showdown-inspection .tile {
  --tile-w: 40px;
  --tile-h: 54px;
}
.showdown-names > span {
  white-space: nowrap;
}
.showdown-inspection .meld {
  display: inline-flex;
  margin: 0 5px;
}
/* A turned-over rack's backs open its hand, so they are a button; it lays out exactly as the
   backs did: no button face, padding or minimum size, and across the rack as the block did. */
button:is(.portrait-backs, .landscape-backs).is-shown {
  min-width: 0;
  min-height: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  appearance: none;
}
button.landscape-backs.is-shown {
  width: 100%;
}
/* The backs are 10 to 12 px tall, so the button's touch area reaches 17 px above and below
   them; the declared sets under it stay on top, so their own buttons keep their taps. */
button:is(.portrait-backs, .landscape-backs).is-shown {
  position: relative;
}
button:is(.portrait-backs, .landscape-backs).is-shown::after {
  position: absolute;
  inset: -17px 0;
  content: '';
}
.portrait-rack > .portrait-public,
.landscape-rack > .landscape-declared {
  position: relative;
  z-index: 1;
}
/* On a phone a turned-over rack keeps its backs (dist/portrait-table.js, dist/landscape-ui.js),
   so the turn bar's note says a tap opens it; the desktop lays the faces on the rack. The same
   conditions as the compact layouts' own sheets. */
.showdown-tap {
  display: none;
}
@media (max-width: 800px) and (orientation: portrait),
  (max-width: 559px),
  (min-width: 560px) and (max-width: 800px) and (min-height: 601px) and (orientation: landscape) and (pointer: fine),
  (min-width: 560px) and (max-width: 1100px) and (max-height: 600px) and (orientation: landscape),
  (min-width: 560px) and (max-width: 1400px) and (max-height: 900px) and (orientation: landscape) and (pointer: coarse) {
  .showdown-tap {
    display: inline;
  }
}

/* Keeps the deal. A dealer who keeps the deal hangs a small vermilion tab under 莊: "2nd deal",
   or "2nd" on a phone. A desktop side seat's label has no 莊 (its East wind says dealer), so
   there the tab is a tag after the name. */
.seat-markers > .dealer-marker.keeps-deal {
  position: relative;
  overflow: visible;
}
.deal-tab {
  position: absolute;
  top: calc(100% + 1px);
  left: 50%;
  z-index: 3;
  padding: 1px 4px 2px;
  color: var(--club-paper, #fdf8ee);
  font: 700 10px/1.15 var(--club-body-font, system-ui, sans-serif);
  letter-spacing: 0.01em;
  white-space: nowrap;
  pointer-events: none;
  background: var(--club-red, #b7472f);
  border-radius: 0 0 4px 4px;
  box-shadow: 0 1px 2px #2b241c40;
  transform: translateX(-50%);
}
.desktop-seat-label .deal-tab {
  position: static;
  padding: 2px 5px;
  border-radius: 4px;
  box-shadow: none;
  transform: none;
}
:is(.portrait-name, .landscape-name) .deal-tab {
  padding: 0 3px 1px;
  font-size: 9px;
}
:is(.portrait-name, .landscape-name) .deal-tab-word {
  display: none;
}
@media (max-width: 800px), (max-width: 1024px) and (max-height: 600px) and (pointer: coarse) {
  .player-dock .deal-tab-word {
    display: none;
  }
}
/* A phone on its side runs the right seat's chip up the table edge: the face at the top, then the
   tai and the name, and the wind and 莊 at the far end. On a phone the felt is taller than the
   window the table scrolls in (at 844x390 the top 103px of its 186px show), so that end lies under
   your rack, and a tab under 莊 with it. So up to 600px high the right chip hangs its tab from its
   top end instead, beside the face (or the tai, on a seat without one), which always shows. The
   left chip starts with its face and 莊, so its tab stays under 莊, as it does on a tablet, where
   the whole chip shows. */
@media (max-height: 600px) {
  .landscape-seat-right .landscape-name:has(.keeps-deal) {
    position: relative;
  }
  .landscape-seat-right .seat-markers > .dealer-marker.keeps-deal {
    position: static;
  }
  .landscape-seat-right .landscape-name .deal-tab {
    /* Level with the top of the chip, over the rack's 5px end padding: beside the upper half of
       the face, so a 听 chop pressed on the face's lower corner leaves it clear. */
    right: -4px;
    left: auto;
    transform: none;
  }
}
/* With the browser's bars open (380px high or less) the chip is shorter than its contents and
   runs past its top instead, face first, while 莊 shows: there the tab stays under 莊. */
@media (min-width: 560px) and (max-height: 380px) and (orientation: landscape) {
  .landscape-seat-right .seat-markers > .dealer-marker.keeps-deal {
    position: relative;
  }
  .landscape-seat-right .landscape-name .deal-tab {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
  }
}

/* The 连庄 seal for a 3rd deal in a row: the two glyphs side by side on one line, smaller than a
   one-glyph chop, over "Keeps the deal" and "Mei · 3rd deal in a row". It is wider than a
   call's seal, so it keeps its own width across the middle of the felt, and the latest discard
   in the centre steps aside while it stands (the large desktop does so for every seal); the
   discard is still in its river. */
.call-stamp.call-stamp-run {
  left: 50%;
  width: max-content;
  max-width: calc(100% - 16px);
}
.classic-table.life-stamping[data-life-stamp='run'] .desktop-focus {
  visibility: hidden;
}
.call-stamp-run .stamp-glyph {
  font-size: 42px;
  white-space: nowrap;
}
.call-stamp-run .stamp-who {
  text-wrap: balance;
}
.portrait-centre .call-stamp-run .stamp-glyph {
  font-size: 32px;
}
/* A phone on its side has a shallow, wide felt: the glyphs beside the two lines of words, on the
   dealer's side and high up as the seal across sits, nearly flat. */
.landscape-centre .call-stamp.call-stamp-run {
  top: 22%;
  grid-auto-flow: row;
  grid-template-columns: auto auto;
  row-gap: 1px;
  transform: translate(-50%, -50%) rotate(-2deg) scale(var(--stamp-scale, 1));
}
.landscape-centre .call-stamp.call-stamp-run[data-seat='left'] {
  left: 30%;
}
.landscape-centre .call-stamp.call-stamp-run[data-seat='right'] {
  left: 70%;
}
.landscape-centre .call-stamp-run .stamp-glyph {
  grid-row: 1 / 3;
  font-size: 26px;
}
.landscape-centre .call-stamp-run :is(.stamp-word, .stamp-who) {
  justify-self: start;
}
/* The large desktop lays a seal flat, the glyph beside the words: the two glyphs stack as a
   square chop in its column. */
@media (min-width: 1360px) and (not ((max-width: 1400px) and (max-height: 900px) and (orientation: landscape) and (pointer: coarse))) {
  .play-area > .classic-table .call-stamp.call-stamp-run {
    width: max-content;
    max-width: 320px;
  }
  .play-area > .classic-table .call-stamp.call-stamp-run .stamp-glyph {
    font-size: 34px;
    line-height: 1.02;
    white-space: normal;
    writing-mode: vertical-rl;
    text-orientation: upright;
    justify-self: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .showdown-tiles,
  .showdown-chop {
    transform: none;
  }
  .seat-character {
    transition: none;
  }
  .seat-character,
  .seat-character img,
  .last-tiles :is(.focus-wall b, .landscape-wall strong),
  body:has(.board.last-tiles) .header-wall-count {
    animation: none !important;
  }
  .seat-character:not([data-mood='focus']) {
    scale: 1;
  }
  .classic-table .call-stamp-win,
  .classic-table .stamp-confetti {
    display: none;
  }
}

/* 听 Ready seal: a vermilion chop in the slot where your next tile lands and, on a computer, the
   tiles you are waiting on as ghosts. Private to you, drawn only from your own tiles and the
   tiles on the table. The slot is the drawn tile's own, so the rack keeps its width when that
   tile arrives. The chop presses in once; the lit edge and everything else stand still. */
.own-tiles > .ready-seal {
  --seal-w: var(--rack-tile, 44px);
  position: relative;
  z-index: 3;
  display: grid;
  flex: 0 1 var(--seal-w);
  justify-items: center;
  align-content: center;
  gap: 2px;
  width: var(--seal-w);
  min-width: 0;
  max-width: 100%;
  min-height: 0;
  aspect-ratio: 3 / 4;
  margin: 0;
  container-type: inline-size;
  padding: 0;
  color: var(--club-ink, #2b241c);
  cursor: pointer;
  background: #fdf8ee99;
  border: 1.5px dashed #b7472f70;
  border-radius: 5px;
  box-shadow: none;
}
.own-tiles > .ready-seal:focus-visible {
  outline: 3px solid var(--club-mustard, #e2b654);
  outline-offset: 2px;
}
.ready-chop {
  display: grid;
  place-items: center;
  width: 68%;
  aspect-ratio: 1;
  color: var(--club-paper, #fdf8ee);
  font:
    400 clamp(15px, calc(var(--seal-w) * 0.44), 26px) / 1 'Noto Serif SC',
    'Songti SC',
    'Noto Sans SC',
    serif;
  background: var(--club-red, #b7472f);
  border: 2px double var(--club-paper, #fdf8ee);
  border-radius: 5px;
  box-shadow: 0 2px 4px #2b241c40;
  transform: rotate(-5deg);
}
.ready-count {
  max-width: 100%;
  overflow: hidden;
  font: 800 9px/1.1 var(--club-body-font, system-ui, sans-serif);
  color: var(--club-red, #b7472f);
  white-space: nowrap;
}
/* Ready in shape but no tai yet: the chop is outlined, not inked. Every copy showing: grey ink. */
.ready-seal[data-ready-state='noTai'] .ready-chop {
  color: var(--club-red, #b7472f);
  background: var(--club-paper, #fdf8ee);
  border: 2px solid var(--club-red, #b7472f);
  box-shadow: none;
}
.ready-seal[data-ready-state='dead'] .ready-chop {
  background: var(--club-muted, #716757);
}
.ready-seal:is([data-ready-state='noTai'], [data-ready-state='dead']) .ready-count {
  color: var(--club-muted, #716757);
}
/* A discard that completes your hand is on the table and Hu is on offer. */
.own-tiles > .ready-seal[data-lit] {
  background: #fff7df;
  border: 2px solid var(--club-mustard, #e2b654);
  box-shadow: 0 0 0 2px #e2b65455;
}
@keyframes ready-press {
  0% {
    opacity: 0;
    transform: rotate(-5deg) scale(1.6);
  }
  65% {
    opacity: 1;
    transform: rotate(-5deg) scale(0.93);
  }
  100% {
    opacity: 1;
    transform: rotate(-5deg) scale(1);
  }
}
.ready-pressing .ready-chop {
  animation: ready-press 0.42s cubic-bezier(0.3, 0.7, 0.4, 1) both;
  animation-delay: calc(var(--ready-age, 0) * -1ms);
}
.ready-ghosts {
  display: none;
}
/* Thirteen tiles and the chop across a phone: the count rides just above the slot. */
@container (max-width: 39.9px) {
  .ready-seal .ready-chop {
    width: 88%;
  }
  .ready-seal .ready-count {
    position: absolute;
    bottom: calc(100% + 3px);
    left: 50%;
    max-width: none;
    overflow: visible;
    translate: -50% 0;
  }
}
/* Phones on their side: the rack is a grid, and the drawn tile's row is the second. */
@media (min-width: 560px) and (max-width: 1100px) and (max-height: 600px) and (orientation: landscape),
  (min-width: 560px) and (max-width: 1400px) and (max-height: 900px) and (orientation: landscape) and (pointer: coarse) {
  .own-tiles > .ready-seal {
    grid-row: 2;
    width: 100%;
  }
}

/* A computer: the chop in the slot and up to four ghosts with their unseen copies. With no
   declared sets they sit where sets would, left of your flowers and tai; with sets, those are
   there, and the rack has room beside the chop instead. */
@media (min-width: 801px) and (not ((max-width: 1100px) and (max-height: 600px) and (orientation: landscape))) and (not ((max-width: 1400px) and (max-height: 900px) and (orientation: landscape) and (pointer: coarse))) {
  .play-area > .classic-table .own-tiles > .ready-seal {
    --seal-w: clamp(38px, calc(8vw - 59px), 56px);
  }
  .play-area > .classic-table .ready-count {
    display: none;
  }
  .play-area > .classic-table .ready-ghosts {
    position: absolute;
    bottom: 0;
    left: calc(100% + 10px);
    display: flex;
    align-items: flex-end;
    gap: 6px;
    pointer-events: none;
  }
  .play-area > .classic-table .own-tiles:not(:has(> .own-melds)) > .ready-seal {
    position: static;
  }
  .play-area > .classic-table .own-tiles:not(:has(> .own-melds)) .ready-ghosts {
    bottom: calc(100% + 6px);
    left: auto;
    right: calc(var(--line-pad, 0px) + var(--bonus-count, 0) * 28px + 120px);
  }
  .play-area > .classic-table .own-tiles:not(:has(> .own-melds)) .ready-ghost .tile.tiny {
    --tile-w: 28px;
    --tile-h: 37px;
  }
  .ready-ghosts-label {
    display: none;
  }
  /* Small words on the pale felt: a deeper red and the help tone, at 4.5:1 or better on the
     darkest felt behind them (the club red and the muted tone are under 4.2:1 there). */
  .play-area > .classic-table .own-tiles:not(:has(> .own-melds)) .ready-ghosts-label {
    display: block;
    align-self: center;
    margin-right: 2px;
    font: 700 11px/1.2 var(--club-body-font, system-ui, sans-serif);
    color: #8f3322;
  }
  .ready-ghost {
    display: grid;
    justify-items: center;
    gap: 2px;
  }
  /* Beside the chop they stay within the rack's own height. */
  .ready-ghost .tile.tiny {
    --tile-w: 26px;
    --tile-h: 35px;
    border-radius: 4px;
    opacity: 0.7;
    outline: 1.5px dashed #b7472f99;
    outline-offset: 1px;
  }
  .ready-ghost small {
    font: 700 10px/1.1 var(--club-body-font, system-ui, sans-serif);
    color: var(--club-ink, #2b241c);
    white-space: nowrap;
  }
  .ready-ghost.muted small {
    color: var(--club-help, #5b5145);
  }
  .ready-ghost.lit .tile.tiny {
    opacity: 1;
    outline: 2.5px solid var(--club-mustard, #e2b654);
    outline-offset: 1px;
    box-shadow: 0 0 10px 1px #e2b654aa;
  }
  .ready-ghost.lit small {
    color: #8f3322;
  }
  .ready-more {
    align-self: center;
    font: 700 11px/1 var(--club-body-font, system-ui, sans-serif);
    color: var(--club-help, #5b5145);
  }
}

/* The list behind the chop. */
.ready-intro {
  margin-top: 0;
}
.ready-list {
  display: grid;
  gap: 8px;
  margin: 12px 0;
  padding: 0;
  list-style: none;
}
.ready-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 10px 6px 6px;
  background: var(--club-paper, #fdf8ee);
  border: 1px solid var(--club-line, #dccfb6);
  border-radius: 8px;
}
.ready-list .tile.small {
  --tile-w: 36px;
  --tile-h: 48px;
  flex: 0 0 auto;
}
.ready-list li.lit {
  border: 2px solid var(--club-mustard, #e2b654);
}
.ready-list li > span {
  display: grid;
  gap: 2px;
}
.ready-list li > span > span {
  font-size: 14px;
  color: var(--club-muted, #716757);
}
.ready-footnote {
  font-size: 13px;
  color: var(--club-muted, #716757);
}
/* A phone on its side: the list keeps to one short screen. */
@media (max-height: 500px) {
  .ready-list {
    gap: 4px;
    margin: 6px 0;
  }
  .ready-list li {
    padding: 3px 8px 3px 4px;
  }
  .ready-list .tile.small {
    --tile-w: 26px;
    --tile-h: 35px;
  }
  .ready-intro,
  .ready-footnote {
    margin-block: 4px;
    font-size: 13px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ready-pressing .ready-chop {
    animation: none;
  }
}
