/* Playfield, minoes, previews, overlays and game-specific panels.
   Palette variables are owned by theme.css; fallbacks match the design brief. */

/* ------------------------------------------------------------------ board */

/* The frame is only a sizing wrapper. The well's left wall, right wall and
   floor are drawn by .board::before so the top stays open and the buffer
   rows above the walls have no background. The frame takes the height the
   page layout allows (--well-h); its width adds back the 3.6-wall strip the
   wall padding eats, so the 10 cell columns and 23 cell rows inside end up
   exactly square. */
.board-frame {
  --well-wall: 3px;
  position: relative;
  width: calc((var(--well-h, 82vh) + var(--well-wall) * 3.6) * 10 / 23);
  height: var(--well-h, 82vh);
  max-width: 100%;
  padding: 0;
}

/* The well's walls take the page's colour (the player's side of a PvCPU
   race is blue, the CPU's red: layout.css sets --mode per side). */
.board {
  --board-gap: 0px;
  --well-wall: 3px;
  --well-edge: color-mix(in srgb, var(--mode, #8b8b8b) 55%, #2a2b30);
  --well-glow: color-mix(in srgb, var(--mode, #8b8b8b) 40%, transparent);
  position: relative;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(var(--board-cols, 10), minmax(0, 1fr));
  grid-template-rows: repeat(var(--board-rows, 23), minmax(0, 1fr));
  gap: var(--board-gap);
  width: 100%;
  height: 100%;
  aspect-ratio: var(--board-cols, 10) / var(--board-rows, 23);
  /* The well background is a negative-z pseudo element behind the cells. */
  isolation: isolate;
  /* Lets the countdown scale with the board instead of the viewport. */
  container-type: size;
  /* The walls and floor sit outside the cell columns. There is no top edge:
     the 3 buffer rows above the visible well stay open. */
  padding: 0 var(--well-wall) var(--well-wall);
  user-select: none;
  touch-action: none;
}

/* ---------------------------------------------------------------- countdown */

.countdown-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  pointer-events: none;
  color: #fff;
  font-size: clamp(3rem, 22vmin, 8rem);
  font-size: clamp(2.5rem, 26cqh, 9rem);
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.04em;
  text-shadow:
    0 0 24px rgba(255, 255, 255, 0.45),
    0 8px 28px rgba(0, 0, 0, 0.8);
}

.countdown-overlay[data-state="go"] {
  color: var(--accent-strong, #7dd3fc);
  text-shadow:
    0 0 30px var(--mode-glow, rgba(56, 189, 248, 0.6)),
    0 8px 28px rgba(0, 0, 0, 0.8);
  font-size: clamp(2.2rem, 20cqh, 7rem);
  letter-spacing: 0.12em;
}

.countdown-overlay.hidden {
  display: none;
}

/* Background and walls of the 20 visible rows only. The height is the board
   content height times 20/23, plus the floor strip in the bottom padding. */
.board::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  box-sizing: border-box;
  height: calc((100% - var(--well-wall)) * 20 / 23 + var(--well-wall));
  z-index: -1;
  border: var(--well-wall) solid var(--well-edge);
  border-top: 0;
  border-radius: 0 0 0.55rem 0.55rem;
  background: linear-gradient(180deg, rgba(9, 9, 11, 0.82), rgba(7, 7, 9, 0.97));
  /* The walls glow in the page's colour and the well casts a shadow on the
     page; inside, the floor and walls shade the edges of the grid. All
     static: nothing here changes while a game runs. */
  box-shadow:
    inset 0 -14px 30px -14px rgba(0, 0, 0, 0.85),
    inset 0 0 0 1px rgba(255, 255, 255, 0.05),
    0 0 0 1px rgba(0, 0, 0, 0.8),
    0 0 26px -2px var(--well-glow),
    0 26px 50px -16px rgba(0, 0, 0, 0.9);
  pointer-events: none;
}

/* A bright bevel along the top of each wall, where the glass catches the
   light. */
.board::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc((100% - var(--well-wall)) * 3 / 23);
  z-index: -1;
  height: 2px;
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--mode, #8b8b8b) 70%, #fff) 0 var(--well-wall),
      transparent var(--well-wall) calc(100% - var(--well-wall)),
      color-mix(in srgb, var(--mode, #8b8b8b) 70%, #fff) calc(100% - var(--well-wall)));
  pointer-events: none;
}

.cell {
  min-width: 0;
  min-height: 0;
  border-radius: 0;
  background-color: rgba(139, 139, 139, 0.05);
  /* right/bottom only: with the board gap at 0 a four-sided inset line
     would double up to 2 px where cells meet */
  box-shadow: inset -1px -1px 0 0 rgba(139, 139, 139, 0.11);
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

/* Buffer rows above the well: no cell fill and no grid line. Mino/ghost rules
   below still give occupied cells their tile. */
.cell.buffer {
  background-color: transparent;
  box-shadow: none;
}

/* Minoes are the 40 px tiles sliced from Archive/public/images/sprites40px.png.
   Each tile fills its 40x40 cell to the edge with rounded corners and a 1 px
   dark rim, so with the board gap at 0 adjacent tiles meet in a 2 px seam
   exactly as the sheet's I_mino/J_mino strips do. The tiles are smooth
   gradients, not pixel art, so the browser's default (bilinear) scaling to
   the fluid cell size is the right choice; image-rendering: pixelated would
   only add ringing. The transparent corners show the board through, so the
   filled-cell background colour and inset grid line are switched off. */

.cell.mino-I, .cell.mino-J, .cell.mino-L, .cell.mino-O,
.cell.mino-S, .cell.mino-T, .cell.mino-Z, .cell.mino-G,
.cell.mino-G1, .cell.mino-G2, .cell.mino-G3,
.mini-cell.mino-I, .mini-cell.mino-J, .mini-cell.mino-L, .mini-cell.mino-O,
.mini-cell.mino-S, .mini-cell.mino-T, .mini-cell.mino-Z {
  border-radius: 0;
  background-color: transparent;
  box-shadow: none;
}

.cell.mino-I, .mini-cell.mino-I { background-image: url("/img/mino/I.png"); }
.cell.mino-J, .mini-cell.mino-J { background-image: url("/img/mino/J.png"); }
.cell.mino-L, .mini-cell.mino-L { background-image: url("/img/mino/L.png"); }
.cell.mino-O, .mini-cell.mino-O { background-image: url("/img/mino/O.png"); }
.cell.mino-S, .mini-cell.mino-S { background-image: url("/img/mino/S.png"); }
.cell.mino-T, .mini-cell.mino-T { background-image: url("/img/mino/T.png"); }
.cell.mino-Z, .mini-cell.mino-Z { background-image: url("/img/mino/Z.png"); }

/* Garbage: the T tile desaturated (magick T.png -modulate 78,0), so it keeps
   the same glass, rim and gloss as the coloured tiles. */
.cell.mino-G { background-image: url("/img/mino/G.png"); }

/* Pre-placed blocks (combo walls) in three shades, so the pieces they are
   laid out as show: the garbage tile at 60%, 100% and 145% brightness
   (magick G.png -modulate 60). */
.cell.mino-G1 { background-image: url("/img/mino/G1.png"); }
.cell.mino-G2 { background-image: url("/img/mino/G2.png"); }
.cell.mino-G3 { background-image: url("/img/mino/G3.png"); }

/* Ghost: a 2 px outline in the tile's face colour on the same rounded
   silhouette as the PNG tiles, with a 12% fill and a faint gloss band so it
   reads as a hollow version of the tile it previews. The SVG carries the
   colour; no box-shadow, so the outline follows the rounded corners. */
.cell.ghost-I,
.cell.ghost-J,
.cell.ghost-L,
.cell.ghost-O,
.cell.ghost-S,
.cell.ghost-T,
.cell.ghost-Z {
  border-radius: 0;
  background-color: transparent;
  box-shadow: none;
  opacity: 0.8;
}

.cell.ghost-I { background-image: url("/img/mino/ghost-I.svg"); }
.cell.ghost-J { background-image: url("/img/mino/ghost-J.svg"); }
.cell.ghost-L { background-image: url("/img/mino/ghost-L.svg"); }
.cell.ghost-O { background-image: url("/img/mino/ghost-O.svg"); }
.cell.ghost-S { background-image: url("/img/mino/ghost-S.svg"); }
.cell.ghost-T { background-image: url("/img/mino/ghost-T.svg"); }
.cell.ghost-Z { background-image: url("/img/mino/ghost-Z.svg"); }

/* The coach's ghost (renderer.js coachGhost): a dotted outline of the
   whole piece over whatever the cells show, so it reads apart from the
   ghost's solid rim and stays visible while the piece in play moves onto
   it. Each cell draws only its sides on the outline (edge-t, edge-r,
   edge-b, edge-l). The colour is a light cut of the tile's, so J's dark
   blue still shows on the well. */
.cell.coach {
  --coach-line: max(2px, calc(var(--cell, 30px) * 0.1));
  position: relative;
}

.cell.coach::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border: 0 dotted var(--coach-c, #fff);
  pointer-events: none;
}

.cell.coach.edge-t::after { border-top-width: var(--coach-line); }
.cell.coach.edge-r::after { border-right-width: var(--coach-line); }
.cell.coach.edge-b::after { border-bottom-width: var(--coach-line); }
.cell.coach.edge-l::after { border-left-width: var(--coach-line); }

.cell.coach-I { --coach-c: color-mix(in srgb, var(--mino-I) 70%, #fff); }
.cell.coach-J { --coach-c: color-mix(in srgb, var(--mino-J) 70%, #fff); }
.cell.coach-L { --coach-c: color-mix(in srgb, var(--mino-L) 70%, #fff); }
.cell.coach-O { --coach-c: color-mix(in srgb, var(--mino-O) 70%, #fff); }
.cell.coach-S { --coach-c: color-mix(in srgb, var(--mino-S) 70%, #fff); }
.cell.coach-T { --coach-c: color-mix(in srgb, var(--mino-T) 70%, #fff); }
.cell.coach-Z { --coach-c: color-mix(in srgb, var(--mino-Z) 70%, #fff); }

/* The coach's stop (renderer.js `coachHold`): the pieces a rewind would
   take back pulse from their own colour half way to the undo flash's
   white and back, every 2 s. The renderer sets --stop-pulse-peak from the
   flash's own filter (whiteFilter), and --stop-pulse-still, a quarter of
   the way, for reduced motion. Behind the pause menu the pulse holds
   still. */
.cell.stop-pulse {
  animation: stop-pulse 2s ease-in-out infinite;
}

@keyframes stop-pulse {
  50% {
    filter: var(--stop-pulse-peak);
  }
}

body:has(#pause-modal:not(.hidden)) .cell.stop-pulse {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .cell.stop-pulse {
    animation: none;
    filter: var(--stop-pulse-still);
  }
}

/* --------------------------------------------------------- line clear flash */

.clear-overlay {
  position: absolute;
  top: 0;
  right: var(--well-wall);
  bottom: var(--well-wall);
  left: var(--well-wall);
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(var(--board-cols, 10), minmax(0, 1fr));
  grid-template-rows: repeat(var(--board-rows, 23), minmax(0, 1fr));
  gap: var(--board-gap, 2px);
  pointer-events: none;
}

.clear-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(var(--board-cols, 10), minmax(0, 1fr));
  gap: var(--board-gap, 2px);
  background: rgba(230, 230, 230, 0.12);
  box-shadow: 0 0 18px rgba(255, 255, 255, 0.22);
  animation: clear-flash 280ms ease-out forwards;
}

.clear-row .cell {
  filter: brightness(2.1) saturate(0.4);
  box-shadow:
    0 0 12px rgba(255, 255, 255, 0.65),
    inset 0 0 0 1px rgba(255, 255, 255, 0.6);
}

@keyframes clear-flash {
  0% {
    opacity: 1;
    transform: scaleY(1);
  }
  55% {
    opacity: 0.85;
    transform: scaleY(0.85);
  }
  100% {
    opacity: 0;
    transform: scaleY(0.1);
  }
}

/* ------------------------------------------------------ perfect clear flight */

/* The blocks a perfect clear sends flying (Renderer.animatePerfectClear):
   a layer over the whole page, above the top bar and under the modals, so
   a block can pass the viewer anywhere on screen without making the page
   scroll. The script sets the perspective and puts each block on its cell;
   the block is a .cell with its tile, so it looks like the one it was. */
.pc-flight {
  position: fixed;
  inset: 0;
  z-index: 40;
  overflow: hidden;
  pointer-events: none;
}

.pc-block {
  position: absolute;
  will-change: transform, filter, opacity;
}

/* With reduced motion, a soft flash over the cleared rows instead. */
.pc-flash {
  grid-column: 1 / -1;
  background: rgba(255, 255, 255, 0.3);
  box-shadow: 0 0 24px 6px rgba(255, 255, 255, 0.3);
}

/* ------------------------------------------------------------- finish line */

/* Sprint finish line and lines-left count (ui/finishline.js): a canvas in
   the well that reaches out past the next queue when the count has to go
   beyond it. The script sets its size and top. Inside the board it sits
   just above the well background (::before) and under the cells, so the
   stack hides it and empty cells let it through; the line-clear flash and
   countdown stay above it. The queue panel's contents sit above the board,
   so the line crosses the panel's background but runs under its pieces. */
.finish-line {
  position: absolute;
  left: 0;
  z-index: -1;
  pointer-events: none;
}

.next-panel > * {
  position: relative;
  z-index: 1;
}

/* Previews scale with the well (--cell is one well column) so hold and next
   keep their proportion to the board instead of shrinking as it grows. Each
   piece is drawn at its own rotation-0 bounding box and centred in a fixed
   4x2-mino slot; the slot has no background or gaps, so only the tiles show.
   Hold and the first next piece use one board cell per mino, the rest 75%. */
.preview-hold,
.preview-next {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 0.35rem;
}

/* One queue mino (3/4 of a board cell) between queue pieces, so they read
   as separate pieces rather than one tower. */
.preview-next {
  gap: calc(var(--cell, 1rem) * 0.75);
}

.mini-slot {
  --mini: var(--cell, 1rem);
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--mini) * 4);
  height: calc(var(--mini) * 2);
  max-width: 100%;
}

.mini-slot--queue {
  --mini: calc(var(--cell, 1rem) * 0.75);
}

.mini-piece {
  display: grid;
  grid-template-columns: repeat(var(--pw, 3), minmax(0, 1fr));
  grid-template-rows: repeat(var(--ph, 2), minmax(0, 1fr));
  width: calc(var(--mini) * var(--pw, 3));
  height: calc(var(--mini) * var(--ph, 2));
}

.mini-cell {
  min-width: 0;
  min-height: 0;
  background-color: transparent;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

.preview-empty {
  display: grid;
  place-items: center;
  width: calc(var(--cell, 1rem) * 4);
  height: calc(var(--cell, 1rem) * 2);
  max-width: 100%;
  border: 1px dashed rgba(143, 144, 154, 0.3);
  border-radius: 0.4rem;
  background: rgba(0, 0, 0, 0.25);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.45);
  color: var(--muted, #8b8b8b);
  font-size: 1.2rem;
}

.hold-panel .panel-title,
.next-panel .panel-title {
  margin-bottom: 0.4rem;
}

/* ----------------------------------------------------------- garbage meter */

.garbage-meter {
  position: relative;
  display: flex;
  align-items: flex-end;
  width: 0.6rem;
  min-height: 4rem;
  overflow: hidden;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.3));
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.7),
    inset 2px 0 4px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.05);
}

.garbage-meter.is-active {
  box-shadow:
    inset 0 0 0 1px rgba(248, 113, 113, 0.5),
    0 0 14px rgba(248, 113, 113, 0.28);
}

.garbage-fill {
  width: 100%;
  height: 0%;
  border-radius: 999px;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, #ff7b85, var(--mino-Z, #be1e2d));
  box-shadow: 0 0 10px rgba(255, 60, 70, 0.6);
  transition: height 0.14s ease-out;
}

.garbage-meter::after {
  content: attr(data-count);
  position: absolute;
  bottom: 0.15rem;
  left: 50%;
  transform: translateX(-50%);
  color: #fff;
  font-size: var(--text-min);
  font-weight: 800;
  line-height: 1;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.garbage-meter[data-count="0"]::after {
  content: "";
}

.board-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  justify-content: center;
  max-width: 100%;
}

.board-row .garbage-meter {
  align-self: stretch;
  height: auto;
}

/* -------------------------------------------------------------- PvCPU view */

.side-stage .panel {
  /* Horizontal padding stays small: each flank has to fit a four-mino slot
     beside the well. */
  padding: 0.55rem 0.35rem;
}

.side-stage .preview-hold {
  gap: 0.3rem;
}

.pvcpu-layout .stat-grid {
  gap: 0.35rem 0.6rem;
}

.pvcpu-layout .stat-value {
  font-size: 0.92rem;
}

.pvcpu-layout .stat-wide .stat-value {
  font-size: 1rem;
}

.pvcpu-layout .stats-panel {
  width: 100%;
  /* Narrow flank panels cannot hold two stat columns; the query below drops
     to one so labels like "Finesse" are not clipped. */
  container-type: inline-size;
}

@container (max-width: 130px) {
  .pvcpu-layout .stat-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.pvcpu-layout .side-stage .panel-title {
  font-size: var(--text-min);
  letter-spacing: 0.12em;
}

/* CPU-Humanish's strategy (ui/strategy.js): a label over a pill in the
   bot's stats panel, side by side when the panel is wide enough. The pill
   is one line at a fixed height and never animates, so a new label moves
   nothing. Each major strategy takes a colour from the piece palette. */
.strategy-row {
  display: grid;
  justify-items: start;
  gap: 0.2rem;
  margin-top: 0.45rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--line);
}

@container (min-width: 11rem) {
  .strategy-row {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 0.5rem;
  }
}

.strategy-pill {
  --pill-c: #8f909a;
  display: inline-block;
  max-width: 100%;
  min-width: 4.5em;
  overflow: hidden;
  padding: 0.1rem 0.6rem;
  border: 1px solid color-mix(in srgb, var(--pill-c) 70%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--pill-c) 24%, rgba(0, 0, 0, 0.35));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
  color: color-mix(in srgb, var(--pill-c) 40%, #fff);
  font-size: var(--text-min);
  font-weight: 800;
  letter-spacing: 0.04em;
  line-height: 1.4;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.strategy-pill[data-strategy="none"] { --pill-c: #4a4c54; color: var(--muted); }
.strategy-pill[data-strategy="opener"] { --pill-c: var(--mino-O); }
.strategy-pill[data-strategy="upstack"] { --pill-c: var(--mino-I); }
.strategy-pill[data-strategy="downstack"] { --pill-c: color-mix(in srgb, var(--mino-J) 70%, var(--mino-I)); }
.strategy-pill[data-strategy="dig"] { --pill-c: color-mix(in srgb, var(--mino-L) 45%, #8f909a); }
.strategy-pill[data-strategy="tspin"] { --pill-c: var(--mino-T); }
.strategy-pill[data-strategy="pc"] { --pill-c: color-mix(in srgb, var(--mino-S) 50%, var(--mino-I)); }
.strategy-pill[data-strategy="combo"] { --pill-c: var(--mino-L); }
.strategy-pill[data-strategy="spike"] { --pill-c: var(--mino-Z); }
.strategy-pill[data-strategy="survive"] { --pill-c: color-mix(in srgb, var(--mino-Z) 50%, var(--mino-T)); }
.strategy-pill[data-strategy="recover"] { --pill-c: var(--mino-S); }
.strategy-pill[data-strategy="flustered"] { --pill-c: color-mix(in srgb, var(--mino-Z) 55%, #ff9ccf); }

/* ------------------------------------------------------------- result panel */

.result-card {
  width: min(34rem, 100%);
}

.result-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.45rem 0.6rem;
  margin: 0.9rem 0;
}

.result-stat {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: 0.45rem;
  background: rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 0 rgba(255, 255, 255, 0.05);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
}

.result-stat span {
  color: var(--muted, #8b8b8b);
}

.result-stat strong {
  font-weight: 700;
}

.result-status {
  min-height: 1.2rem;
  margin: 0;
  color: var(--muted, #8b8b8b);
  font-size: var(--text-min);
}

.result-status.is-error {
  color: #f87171;
}

.result-status.is-ok {
  color: #4ade80;
}

/* ------------------------------------------------------------ replay view */

/* The replay page also carries a heading and playback controls, so its panels
   are a little tighter than the sprint page's to leave the well its height. */
.page-replay .panel {
  padding: 0.7rem 0.8rem;
}

/* The PvCPU replay flanks keep the live race's compact padding so their
   four-mino preview slots fit the width the layout reserves for them. */
.page-replay .side-stage .panel {
  padding: 0.55rem 0.35rem;
}

.page-replay .stage-side {
  gap: 0.55rem;
}

.replay-layout .stat-grid {
  gap: 0.45rem 0.7rem;
}

.replay-layout .speed-buttons {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-height: 800px) {
  /* The compact heading below is shorter than the base estimate. */
  .page-replay .page-main {
    --chrome-h: 15.7rem;
  }

  .page-replay .panel {
    padding: 0.5rem 0.65rem;
  }

  .page-replay .panel-title {
    margin-bottom: 0.4rem;
  }

  .page-replay .stage-side {
    gap: 0.4rem;
  }

  .page-replay .stat-grid {
    gap: 0.25rem 0.5rem;
  }

  .page-replay .stat-value {
    font-size: 0.92rem;
  }

  .page-replay .stat-wide .stat-value {
    font-size: 1.05rem;
  }

  .page-replay .replay-controls {
    gap: 0.4rem;
  }

  .page-replay .page-heading {
    margin-bottom: 0.5rem;
  }

  .page-replay .page-heading h1,
  .page-replay .replay-title {
    font-size: 1.35rem;
  }
}

.replay-title {
  margin: 0;
  font-size: clamp(1.3rem, 3vw, 1.8rem);
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.replay-meta {
  margin: 0.2rem 0 0;
  color: var(--muted, #8b8b8b);
  font-size: var(--text-min);
}

.replay-debug a {
  color: inherit;
  text-decoration: underline;
}

.replay-debug input {
  margin: 0 0.15rem 0 0;
  vertical-align: -0.1em;
}

/* --------------------------------------------------------------- zen view */

/* The combo and B2B counters (Zen and Coach): two big numbers, each with a
   small label, floating under the left column's panels beside the well.
   They are out of the flow and keep their places while hidden, so nothing
   moves when one shows or hides. The combo is in the L's orange, the B2B
   in the I's cyan. zenpage.js sets the classes: is-on zooms a count in,
   is-out zooms it on while it blurs and fades out, and is-pop on the
   number marks a new value. Each grows from its right edge, away from the
   well. */
.stage-side:has(> .zen-counters) {
  position: relative;
}

.zen-counters {
  position: absolute;
  top: 100%;
  right: 0;
  display: grid;
  justify-items: end;
  gap: calc(var(--cell, 1.6rem) * 0.3);
  width: max-content;
  margin-top: calc(var(--cell, 1.6rem) * 0.6);
  pointer-events: none;
}

.zen-counter {
  --counter-c: var(--mino-L);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  visibility: hidden;
  opacity: 0;
  transform-origin: 100% 50%;
}

.zen-b2b {
  --counter-c: var(--mino-I);
}

.zen-counter-label {
  margin-right: -0.16em;
  color: color-mix(in srgb, var(--counter-c) 45%, #fff);
  font-size: clamp(var(--text-min), calc(var(--cell, 1.6rem) * 0.42), 1.2rem);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* The number in the tile's colour, lit from above as the tiles are, with
   a glow of the same colour. Its line is --counter-h high, empty or not,
   so the B2B below never moves. */
.zen-counter-value {
  --counter-h: calc(var(--cell, 1.6rem) * 3.4);
  height: var(--counter-h);
  background: linear-gradient(
    color-mix(in srgb, var(--counter-c) 40%, #fff) 15%,
    var(--counter-c) 65%,
    color-mix(in srgb, var(--counter-c) 80%, #000)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 calc(var(--cell, 1.6rem) * 0.25)
    color-mix(in srgb, var(--counter-c) 55%, transparent));
  font-size: var(--counter-h);
  font-variant-numeric: tabular-nums;
  font-weight: 900;
  line-height: var(--counter-h);
  white-space: nowrap;
  transform-origin: 100% 50%;
}

.zen-counter-value.is-long {
  font-size: calc(var(--counter-h) * 0.7);
}

.zen-counter.is-on {
  visibility: visible;
  opacity: 1;
  animation: counter-in 0.18s ease-out;
}

/* The zoom out ends where a hidden counter starts. */
.zen-counter.is-out {
  animation: counter-out 0.3s ease-out;
}

/* Zen's B2B count goes the same way over 3 s (B2B_FADE_MS in ui/zen.js)
   once the next piece locks, and stays gone until zenpage.js hides it. */
.zen-counter.is-fading {
  animation: counter-out 3s linear forwards;
}

.zen-counter-value.is-pop {
  animation: counter-pop 0.15s ease-out;
}

@keyframes counter-in {
  from {
    opacity: 0;
    transform: scale(0.25);
  }
}

@keyframes counter-out {
  from {
    visibility: visible;
    opacity: 1;
  }

  to {
    visibility: hidden;
    opacity: 0;
    transform: scale(1.6);
    filter: blur(calc(var(--cell, 1.6rem) * 0.3));
  }
}

@keyframes counter-pop {
  from {
    transform: scale(1.2);
  }
}

@keyframes counter-fade-in {
  from {
    opacity: 0;
  }
}

@keyframes counter-fade-out {
  from {
    visibility: visible;
    opacity: 1;
  }

  to {
    visibility: hidden;
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .zen-counter.is-on {
    animation-name: counter-fade-in;
  }

  .zen-counter.is-out,
  .zen-counter.is-fading {
    animation-name: counter-fade-out;
  }

  .zen-counter-value.is-pop {
    animation: none;
  }
}

/* --------------------------------------------------------------- responsive */

@media (max-width: 640px) {
  .board-frame {
    max-width: 100%;
  }

  .result-stats {
    grid-template-columns: minmax(0, 1fr);
  }
}
