/* Page structure, typography, navigation, panels and responsive layout.
   The palette, the glass buttons and panels and the motion live in
   theme.css, which every page loads first; this file lays the pages out and
   colours the page-level furniture from --mode. */

:root {
  /* Height of the sticky .topbar. Page layouts that size the well from the
     viewport subtract this; keep it in step with .topbar's padding and font. */
  --topbar-h: 3.6rem;
  /* .board's wall/floor strip: 3 px left, right and bottom. */
  --well-wall: 3px;
}

h1, h2, h3 {
  line-height: 1.15;
}

/* ---------------------------------------------------------------- top bar */

/* Opaque, so it costs nothing to scroll under; the glow line along its foot
   is the page's colour. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1.4rem;
  height: var(--topbar-h);
  padding: 0 clamp(1rem, 3vw, 2rem);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, #15161a, #0e0f12);
  box-shadow:
    0 1px 0 color-mix(in srgb, var(--mode) 28%, transparent),
    0 10px 30px -10px rgba(0, 0, 0, 0.8);
  view-transition-name: topbar;
}

/* Wordmark: the mark (img/mark.svg) and the name, with the page's name in
   its colour beside it. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--text);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  text-decoration: none;
  white-space: nowrap;
}

.brand:hover {
  text-decoration: none;
  color: #fff;
}

.brand-mark {
  width: 1.6rem;
  height: 1.6rem;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7));
}

.brand-sub {
  margin-left: 0.1rem;
  padding: 0.12rem 0.5rem;
  border: 1px solid var(--mode-line);
  border-radius: 999px;
  background: var(--mode-faint);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  color: var(--accent);
  font-size: var(--text-min);
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* With every link, the brand and the account the bar needs about 1100 px.
   Narrower, down to 640 px where it wraps, the links scroll sideways in
   their own strip, not the page. The padding keeps the active link's glow
   inside that strip. */
.topnav {
  display: flex;
  gap: 0.2rem;
  min-width: 0;
  margin-block: -0.5rem;
  padding-block: 0.5rem;
  overflow-x: auto;
  scrollbar-width: none;
}

.topnav a {
  flex: none;
  padding: 0.35rem 0.7rem;
  border: 1px solid transparent;
  border-radius: 0.45rem;
  color: var(--muted);
  font-size: var(--text-min);
  font-weight: 600;
  transition: color 0.12s ease, background 0.12s ease;
}

.topnav a:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
  text-decoration: none;
}

.topnav a.is-active {
  border-color: var(--mode-line);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 55%),
    var(--mode-faint);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 4px 10px -4px var(--mode-glow);
  color: #fff;
  text-decoration: none;
}

.account {
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.75rem;
  margin-left: auto;
  font-size: var(--text-min);
}

.account-name {
  color: var(--text);
  font-weight: 700;
}

.account-link {
  color: var(--muted);
}

.account-link:hover {
  color: var(--text);
}

/* -------------------------------------------------------------- game pages */

/* Board stage: hold and run stats on the left, the well in the centre, the
   next queue on the right. Used by the sprint and replay pages; the PvCPU
   boards use .side-stage with the same idea at a smaller scale.

   The wells are sized height-first: each page layout sets --well-h from the
   viewport (top bar and page padding removed) and --well-w from the board's
   10:23 aspect. --cell is one well column, which the previews scale with so
   they keep their proportion to the board. */
.stage {
  /* A preview slot is four minoes wide, so both flanking panels need room
     for 4 * --cell plus their own padding. */
  --side-w: max(10rem, calc(var(--cell, 1.4rem) * 4 + 2.125rem));
  --next-w: max(8rem, calc(var(--cell, 1.4rem) * 4 + 2.125rem));
  --cell: calc((var(--well-w, 28rem) - 2 * var(--well-wall)) / 10);
  --stage-gap: clamp(0.7rem, 1.8vw, 1.4rem);
  display: grid;
  grid-template-columns: minmax(0, var(--side-w)) auto minmax(0, var(--next-w));
  gap: var(--stage-gap);
  align-items: start;
  justify-content: center;
}

/* Sprint stages keep room right of the next queue for the finish line's
   lines-left count (ui/finishline.js), which moves out past the queue when
   the line is level with it. The left column takes the same room when the
   page is wide enough, so the well stays centred. On a narrow page the room
   is whatever the page width leaves beside the panels and the well, and the
   stage shifts left into the page padding instead of shrinking the well. */
@media (min-width: 761px) {
  .stage--sprint {
    --count-room: clamp(
      0px,
      100vw - 2 * var(--pad-x, 0.25rem) - var(--side-w) - var(--well-w) - var(--next-w)
        - 2 * var(--stage-gap),
      var(--cell) * 1.8
    );
    /* The well's column is fixed: an auto one would shrink the well. */
    grid-template-columns:
      minmax(var(--side-w), calc(var(--side-w) + var(--count-room)))
      var(--well-w)
      calc(var(--next-w) + var(--count-room));
  }

  .stage--sprint > .stage-side:first-child {
    justify-self: end;
    width: 100%;
    max-width: var(--side-w);
  }

  .stage--sprint > .stage-side:last-child {
    justify-self: start;
    width: 100%;
    max-width: var(--next-w);
  }
}

.game-layout {
  --pad-y: clamp(0.5rem, 1.4vh, 1.1rem);
  /* 23 rows of board (20 visible + 3 above) fill the height left by the top
     bar, the page padding and a small bottom margin. The width cap turns the
     space left beside the four-mino preview slots into a board height. */
  --well-h: min(
    calc(100vh - var(--topbar-h) - 2 * var(--pad-y) - 0.5rem),
    calc((100vw - 4.95rem) * 1.155)
  );
  --well-w: calc((var(--well-h) + var(--well-wall) * 3.6) * 10 / 23);
  /* Wide enough at 2560x1440 for the count room on both sides of the stage
     (.stage--sprint), so the well stays centred. */
  --pad-x: clamp(0.75rem, 3vw, 2rem);
  width: min(1600px, 100%);
  margin: 0 auto;
  padding: var(--pad-y) var(--pad-x);
}

.stage-board {
  display: flex;
  justify-content: center;
  min-width: 0;
  animation: fade-in 0.5s ease-out backwards;
}

/* The flanking columns start at the top of the well, below the three open
   rows drawn above it: 3 of the board's 23 rows (its height less the floor
   strip). */
.stage-side {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 0;
  margin-top: calc((var(--well-h) - var(--well-wall)) * 3 / 23);
}

/* Panels rise in one after another when a page opens. */
.stage-side > .panel,
.versus-panel > .panel {
  animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}

.stage-side > .panel:nth-child(2),
.versus-panel > .panel { animation-delay: 70ms; }
.stage-side > .panel:nth-child(3) { animation-delay: 140ms; }
.stage-side > .panel:nth-child(4) { animation-delay: 210ms; }

/* --------------------------------------------------------------- HUD text */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.55rem 0.8rem;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.stat-wide {
  grid-column: 1 / -1;
}

.stat-label {
  color: var(--muted);
  font-size: var(--text-min);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.stat-value {
  font-size: 1.02rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.stat-wide .stat-value {
  font-size: 1.3rem;
}

/* Time and PPS lead every stats panel, one per row at the same large size,
   scaled with the well (--cell is one well column). No shadow or glow: the
   time changes every frame, and a blur on it would be rasterised each time. */
.stat-hero {
  grid-column: 1 / -1;
}

.stat-grid .stat-hero .stat-value {
  font-size: clamp(1.3rem, calc(var(--cell, 1.6rem) * 0.85), 2.8rem);
  font-weight: 800;
  line-height: 1.05;
  color: #fff;
}

/* Sprint: lines left comes next, still large, and turns red once the
   finish line shows (10 or fewer). */
.stat-grid .stat-goal .stat-value {
  font-size: clamp(1.2rem, calc(var(--cell, 1.6rem) * 0.7), 2.2rem);
  font-weight: 800;
  line-height: 1.05;
}

.stat-goal .stat-value.is-final {
  color: #ff4a40;
  text-shadow: 0 0 0.35em rgba(255, 30, 24, 0.6);
}

/* Decimals, point included (hud.js): half size, raised so their tops line
   up with the whole digits' (at half size, 1cap is the difference between
   the two cap heights), in a darker shade of the number's colour. */
.stat-frac {
  font-size: max(0.5em, var(--text-min));
  vertical-align: 1cap;
  color: color-mix(in srgb, currentColor 58%, #000);
}

/* ------------------------------------------------------------- PvCPU race */

/* Two player columns (hold+stats | well | next) around a centre panel, all
   sized from the same height so both wells stay identical. The centre panel
   is the first thing to give way on narrower screens (see the media queries). */
.pvcpu-layout {
  --pad-y: clamp(0.4rem, 1.2vh, 0.9rem);
  --pad-x: clamp(0.5rem, 1.5vw, 1.25rem);
  --well-gap: clamp(0.35rem, 0.8vw, 0.7rem);
  --col-gap: clamp(0.5rem, 1vw, 1.1rem);
  /* Each flank holds a four-mino preview slot, so its width tracks --cell
     (the side-stage panel adds 0.825rem of padding and border). */
  --side-w: calc(var(--cell, 1.4rem) * 4 + 0.825rem);
  --next-w: calc(var(--cell, 1.4rem) * 4 + 0.825rem);
  --centre-w: clamp(9rem, 12vw, 12rem);
  /* Cap from the width too, so a tall narrow window shrinks the wells rather
     than pushing the centre panel off-screen. Four preview slots grow with
     the cell, so the cap is a width-left-over slope, not a fixed budget. */
  --well-h-cap: calc(
    100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--chrome-h, 0px) - 0.5rem
  );
  --well-h: min(
    var(--well-h-cap),
    calc((100vw - 11rem) * 0.5336)
  );
  --well-w: calc((var(--well-h) + var(--well-wall) * 3.6) * 10 / 23);
  --cell: calc((var(--well-w) - 2 * var(--well-wall)) / 10);
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--centre-w) minmax(0, 1fr);
  gap: var(--col-gap);
  align-items: start;
  width: 100%;
  margin: 0 auto;
  padding: var(--pad-y) var(--pad-x);
}

/* Above 1600px the flanking panels no longer grow faster than the viewport,
   so the width cap is the exact four-slot slope. */
@media (min-width: 1600px) {
  .pvcpu-layout {
    --well-h: min(
      var(--well-h-cap),
      calc((100vw - 25.8rem) * 0.6389)
    );
  }
}

.side-stage {
  display: grid;
  grid-template-columns: minmax(0, var(--side-w)) auto minmax(0, var(--next-w));
  gap: var(--well-gap);
  align-items: start;
  justify-content: center;
  width: 100%;
}

/* The player's side is always blue (I) and the CPU's always red (Z), on
   the live race and on a PvCPU replay alike. */
.player-side,
.cpu-side {
  --accent: color-mix(in srgb, var(--mode) 62%, #fff);
  --accent-strong: color-mix(in srgb, var(--mode) 40%, #fff);
  --mode-glow: color-mix(in srgb, var(--mode) 55%, transparent);
  --mode-faint: color-mix(in srgb, var(--mode) 16%, transparent);
  --mode-line: color-mix(in srgb, var(--mode) 45%, rgba(255, 255, 255, 0.12));
}

.player-side { --mode: var(--mino-I); }
.cpu-side { --mode: var(--mino-Z); }

.versus-panel {
  position: sticky;
  top: 4.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.versus-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.versus-title span {
  color: var(--accent);
}

.race {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.race-row {
  display: grid;
  /* A long bot name is cut short instead of pushing the count out. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.25rem;
  min-width: 0;
  padding: 0.35rem 0.45rem;
  border-left: 3px solid var(--mino-Z);
  border-radius: 0 0.4rem 0.4rem 0;
  background: rgba(255, 255, 255, 0.04);
}

.race-row:first-child {
  border-left-color: var(--mino-I);
}

.race-label {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--muted);
  font-size: var(--text-min);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.race-label span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.race-label strong {
  margin-left: auto;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.race-detail {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
}

.controls-form {
  display: grid;
  gap: 0.6rem;
}

/* CPU-Humanish settings (ui/humanish.js): rank and personality, the
   personality's blurb and the Advanced sliders. */
.humanish-settings {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.humanish-about {
  margin: 0;
}

.humanish-about.is-error {
  color: #f87171;
}

/* The rank picker: three to a row, weakest first, edge to edge across the
   centre column, with Full Strength across the bottom. The colours are
   TETR.IO's (rankColors in ch.tetr.io's base.js), with the ink that reads
   best on each. The pick's LED is lit. */
.rank-picker {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-inline: calc(-1rem - 1px);
}

.rank-button {
  --rank: #777;
  --rank-ink: #0b0b0d;
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  height: 2.3rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: hidden;
  background: var(--rank);
  color: var(--rank-ink);
  font: 700 clamp(16pt, 1.5vw, 18pt) / 1 var(--font-mono);
  cursor: pointer;
}

.rank-button:hover {
  filter: brightness(1.12);
}

.rank-button:focus-visible {
  z-index: 1;
  outline: 2px solid #fff;
  outline-offset: -2px;
}

/* Only the letters are centred; the + or - hangs off their right. */
.rank-name {
  position: relative;
}

.rank-mod {
  position: absolute;
  left: 100%;
}

.rank-led {
  position: absolute;
  top: 50%;
  left: 0.2rem;
  width: 0.35rem;
  height: 0.35rem;
  margin-top: -0.175rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  box-shadow:
    inset 0 1px 1px rgba(0, 0, 0, 0.6),
    0 1px 0 rgba(255, 255, 255, 0.3);
}

.rank-button[aria-pressed="true"] .rank-led {
  background: radial-gradient(circle at 40% 35%, #fff 0 35%,
    color-mix(in srgb, var(--rank) 30%, #fff) 75%);
  box-shadow:
    0 0 0.2rem 0.05rem #fff,
    0 0 0.6rem 0.15rem color-mix(in srgb, var(--rank) 40%, #fff);
}

.rank-button[data-rank="D"]  { --rank: #907591; --rank-ink: #fff; }
.rank-button[data-rank="D+"] { --rank: #8e6091; --rank-ink: #fff; }
.rank-button[data-rank="C-"] { --rank: #79558c; --rank-ink: #fff; }
.rank-button[data-rank="C"]  { --rank: #733e8f; --rank-ink: #fff; }
.rank-button[data-rank="C+"] { --rank: #552883; --rank-ink: #fff; }
.rank-button[data-rank="B-"] { --rank: #5650c7; --rank-ink: #fff; }
.rank-button[data-rank="B"]  { --rank: #4f64c9; --rank-ink: #fff; }
.rank-button[data-rank="B+"] { --rank: #4f99c0; --rank-ink: #fff; }
.rank-button[data-rank="A-"] { --rank: #3bb687; }
.rank-button[data-rank="A"]  { --rank: #46ad51; }
.rank-button[data-rank="A+"] { --rank: #1fa834; }
.rank-button[data-rank="S-"] { --rank: #b2972b; }
.rank-button[data-rank="S"]  { --rank: #e0a71b; }
.rank-button[data-rank="S+"] { --rank: #d8af0e; }
.rank-button[data-rank="SS"] { --rank: #db8b1f; }
.rank-button[data-rank="U"]  { --rank: #ff3813; }
.rank-button[data-rank="X"]  { --rank: #ff45ff; }
.rank-button[data-rank="X+"] { --rank: #a763ea; }

.rank-button[data-rank="full"] {
  --rank: #fff;
  grid-column: 1 / -1;
  background: linear-gradient(90deg, #ff4d4d, #ff9f1c, #ffe14d, #4cd964,
    #3bb2ff, #7a6bff, #e15cff);
  font: 800 var(--text-min) / 1 var(--font-ui);
  letter-spacing: 0.04em;
}

.humanish-advanced > summary {
  color: var(--muted);
  font-size: var(--text-min);
  font-weight: 700;
  cursor: pointer;
}

.humanish-advanced[open] > summary {
  margin-bottom: 0.5rem;
  color: var(--text);
}

.slider-list {
  display: grid;
  gap: 0.55rem;
}

.slider-field {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
  color: var(--muted);
  font-size: var(--text-min);
  font-weight: 600;
}

.slider-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0 0.5rem;
}

.slider-head output {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.slider-field input[type="range"] {
  width: 100%;
  margin: 0;
  accent-color: var(--mode);
}

.slider-field input[type="range"]:disabled {
  opacity: 0.4;
}

.slider-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.9rem;
}

.slider-check {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 400;
}

.slider-check input {
  margin: 0;
  accent-color: var(--mode);
}

/* -------------------------------------------------------------- home page */

.home-main {
  display: grid;
  gap: clamp(1.4rem, 3vw, 2.4rem);
  width: min(1120px, 100%);
  margin: 0 auto;
  padding: clamp(1.4rem, 4vw, 3rem) clamp(1rem, 3vw, 2rem) 4rem;
}

.home-main > * {
  animation: rise 0.55s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}

.home-main > :nth-child(2) { animation-delay: 90ms; }
.home-main > :nth-child(3) { animation-delay: 180ms; }

.hero {
  display: grid;
  gap: 0.8rem;
  max-width: 56rem;
}

/* The big wordmark: the mark beside the name. */
.hero-brand {
  display: flex;
  align-items: center;
  gap: clamp(0.8rem, 2vw, 1.3rem);
}

.hero-mark {
  width: clamp(3.2rem, 8vw, 5.2rem);
  height: auto;
  flex: none;
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.7));
}

.hero h1 {
  margin: 0;
  font-size: clamp(2.4rem, 7vw, 4.6rem);
  font-weight: 900;
  letter-spacing: -0.035em;
  line-height: 1;
  /* The gradient only paints inside the box: room for the j's descender. */
  padding-bottom: 0.2em;
  margin-bottom: -0.2em;
  background: linear-gradient(180deg, #fff 30%, #a9abb6);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-tag {
  margin: 0;
  color: var(--text);
  font-size: clamp(1.1rem, 2.4vw, 1.45rem);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.hero-tag span {
  color: var(--accent);
}

.hero p {
  max-width: 44rem;
  margin: 0;
  color: var(--muted);
  font-size: clamp(0.95rem, 2vw, 1.08rem);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.4rem;
}

.home-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

.home-grid > * {
  width: 100%;
}

/* Seven modes: Sprint leads as a tall card on the left, the other six
   fill two rows of three beside it. Each card is its mode's colour. */
.mode-cards {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto;
  gap: 0.8rem;
}

.mode-card {
  --mode: var(--mino-I);
  --accent: color-mix(in srgb, var(--mode) 62%, #fff);
  --mode-glow: color-mix(in srgb, var(--mode) 55%, transparent);
  --mode-faint: color-mix(in srgb, var(--mode) 16%, transparent);
  --mode-line: color-mix(in srgb, var(--mode) 45%, rgba(255, 255, 255, 0.12));
  position: relative;
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: 0.4rem;
  overflow: hidden;
  padding: 1.1rem 1.1rem 1rem;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-top-color: var(--mode-line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015) 40%, rgba(255, 255, 255, 0) 40.5%),
    radial-gradient(120% 90% at 100% 0%, var(--mode-faint), transparent 55%),
    linear-gradient(180deg, #1c1d22, #131417);
  box-shadow: var(--shadow-panel);
  color: var(--text);
  text-decoration: none;
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}

/* A bar of the mode's colour along the left edge, glowing. */
.mode-card::before {
  content: "";
  position: absolute;
  top: 0.8rem;
  bottom: 0.8rem;
  left: 0;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--mode);
  box-shadow: 0 0 12px var(--mode-glow);
}

.mode-card:hover {
  transform: translateY(-3px);
  border-color: var(--mode-line);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 0 0 1px rgba(0, 0, 0, 0.55),
    0 30px 50px -18px rgba(0, 0, 0, 0.9),
    0 24px 40px -20px var(--mode-glow);
  text-decoration: none;
}

.mode-card--sprint { --mode: var(--mino-I); grid-row: 1 / 3; }
.mode-card--pvcpu { --mode: var(--mino-Z); }
.mode-card--cpuvcpu { --mode: var(--mino-O); }
.mode-card--zen { --mode: var(--mino-S); }
.mode-card--coach { --mode: var(--mino-J); }
.mode-card--combo { --mode: var(--mino-L); }
.mode-card--replays { --mode: var(--mino-T); }

.mode-card h3 {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
  color: #fff;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.mode-card h3 small {
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--mode-line);
  border-radius: 999px;
  background: var(--mode-faint);
  color: var(--accent);
  font-size: var(--text-min);
  letter-spacing: 0.12em;
}

.mode-card p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-min);
}

.mode-card--sprint p {
  font-size: 0.9rem;
}

/* The mode's piece, drawn from the game's own tiles (img/mino), floating at
   the card's bottom right under the text. */
.mode-glyph {
  --g: 1.4rem;
  justify-self: end;
  align-self: end;
  margin-top: 0.5rem;
  width: calc(var(--g) * 4);
  height: calc(var(--g) * 2);
  background-repeat: no-repeat;
  background-size: var(--g) var(--g);
  filter: drop-shadow(0 8px 10px rgba(0, 0, 0, 0.7));
  opacity: 0.95;
  transition: transform 0.2s ease;
  pointer-events: none;
}

.mode-card:hover .mode-glyph {
  transform: translateY(-3px) rotate(-3deg);
}

.mode-card--sprint .mode-glyph {
  --g: 2.4rem;
}

.mode-glyph--I {
  background-image: url("/img/mino/I.png"), url("/img/mino/I.png"),
    url("/img/mino/I.png"), url("/img/mino/I.png");
  background-position: 0 var(--g), var(--g) var(--g), calc(var(--g) * 2) var(--g),
    calc(var(--g) * 3) var(--g);
}

.mode-glyph--Z {
  background-image: url("/img/mino/Z.png"), url("/img/mino/Z.png"),
    url("/img/mino/Z.png"), url("/img/mino/Z.png");
  background-position: var(--g) 0, calc(var(--g) * 2) 0, calc(var(--g) * 2) var(--g),
    calc(var(--g) * 3) var(--g);
}

.mode-glyph--O {
  background-image: url("/img/mino/O.png"), url("/img/mino/O.png"),
    url("/img/mino/O.png"), url("/img/mino/O.png");
  background-position: var(--g) 0, calc(var(--g) * 2) 0, var(--g) var(--g),
    calc(var(--g) * 2) var(--g);
}

.mode-glyph--S {
  background-image: url("/img/mino/S.png"), url("/img/mino/S.png"),
    url("/img/mino/S.png"), url("/img/mino/S.png");
  background-position: calc(var(--g) * 2) 0, calc(var(--g) * 3) 0, var(--g) var(--g),
    calc(var(--g) * 2) var(--g);
}

.mode-glyph--J {
  background-image: url("/img/mino/J.png"), url("/img/mino/J.png"),
    url("/img/mino/J.png"), url("/img/mino/J.png");
  background-position: var(--g) 0, var(--g) var(--g), calc(var(--g) * 2) var(--g),
    calc(var(--g) * 3) var(--g);
}

.mode-glyph--L {
  background-image: url("/img/mino/L.png"), url("/img/mino/L.png"),
    url("/img/mino/L.png"), url("/img/mino/L.png");
  background-position: calc(var(--g) * 3) 0, var(--g) var(--g), calc(var(--g) * 2) var(--g),
    calc(var(--g) * 3) var(--g);
}

.mode-glyph--T {
  background-image: url("/img/mino/T.png"), url("/img/mino/T.png"),
    url("/img/mino/T.png"), url("/img/mino/T.png");
  background-position: calc(var(--g) * 2) 0, var(--g) var(--g), calc(var(--g) * 2) var(--g),
    calc(var(--g) * 3) var(--g);
}

.tabs {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.85rem;
  padding: 0.25rem;
  border-radius: 0.6rem;
  background: rgba(0, 0, 0, 0.45);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5), 0 1px 0 rgba(255, 255, 255, 0.05);
}

.tab {
  flex: 1;
  padding: 0.45rem 0.6rem;
  border: 1px solid transparent;
  border-radius: 0.45rem;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-min);
  font-weight: 700;
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease;
}

.tab:hover {
  color: var(--text);
}

.tab.is-active {
  border-color: var(--mode-line);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 55%),
    var(--mode-faint);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
  color: #fff;
}

.auth-form {
  display: grid;
  gap: 0.75rem;
}

.account-panel {
  max-width: 26rem;
}

.controls-panel {
  max-width: 48rem;
  margin-bottom: 1rem;
}

.controls-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.controls-panel .hint {
  margin-top: 0;
}

.controls-group {
  margin: 1rem 0 0.3rem;
  color: var(--accent);
  font-size: var(--text-min);
  font-weight: 800;
}

.controls-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(3, 8.5rem);
  gap: 0.4rem;
  align-items: center;
  padding: 0.25rem 0;
  border-top: 1px solid var(--line);
  font-size: var(--text-min);
}

.key-slot {
  position: relative;
  display: flex;
}

/* A key with its name on it, as .key-cap is in a line of text. */
.key-slot-key {
  flex: 1;
  min-width: 0;
  min-height: 2rem;
  padding: 0.2rem 1.5rem;
  border: 1px solid #5a5b64;
  border-bottom-width: 2px;
  border-radius: 0.35rem;
  background: linear-gradient(180deg, #3d3e46, #2a2b31);
  color: #f2f2f6;
  font: inherit;
  font-weight: 700;
  overflow-wrap: anywhere;
  cursor: pointer;
}

.key-slot-key:hover {
  filter: brightness(1.2);
}

.key-slot-key.is-empty {
  border-style: dashed;
  border-bottom-width: 1px;
  background: none;
  color: var(--muted);
  font-weight: 400;
}

.key-slot-key.is-waiting {
  border-color: var(--accent);
  background: var(--mode-faint);
  color: var(--accent-strong);
}

.key-slot-clear {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 1.5rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1.1rem;
  font-weight: 700;
  cursor: pointer;
}

.key-slot-clear:hover {
  color: #f87171;
}

.key-slot-clear[hidden] {
  display: none;
}

.controls-status {
  margin-top: 0.6rem;
}

@media (max-width: 42rem) {
  .controls-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .controls-label {
    grid-column: 1 / -1;
  }

  .key-slot-key {
    padding-inline: 0.3rem 1.4rem;
  }

  .key-slot-key.is-empty,
  .key-slot-key.is-waiting {
    padding-inline: 0.3rem;
  }
}

.handling-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.4rem;
}

.handling-row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) 11rem;
  column-gap: 0.75rem;
  align-items: center;
  padding: 0.4rem 0;
  border-top: 1px solid var(--line);
  font-size: var(--text-min);
}

.handling-name {
  font-weight: 700;
}

.handling-slider {
  width: 100%;
  accent-color: var(--accent);
}

.handling-value {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.2rem 0.5rem;
  border-radius: 0.3rem;
  background: rgba(0, 0, 0, 0.25);
}

.handling-detail,
.handling-about {
  color: var(--muted);
}

.handling-number {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.handling-about {
  grid-column: 2 / -1;
  margin: 0.1rem 0 0;
}

.handling-switches {
  display: grid;
  gap: 0.35rem;
  padding: 0.6rem 0;
  border-top: 1px solid var(--line);
}

.handling-switch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-min);
  cursor: pointer;
}

.handling-switch input {
  accent-color: var(--accent);
}

.handling-buffer {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.3rem;
  margin: 0;
  padding: 0.5rem 0 0.6rem;
  border: 0;
  border-top: 1px solid var(--line);
  font-size: var(--text-min);
}

.handling-buffer legend {
  float: left;
  grid-column: 1 / -1;
  padding: 0;
  font-weight: 700;
}

.handling-mode {
  cursor: pointer;
}

.handling-mode input {
  position: absolute;
  opacity: 0;
}

.handling-mode span {
  display: block;
  padding: 0.3rem;
  border: 1px solid #5a5b64;
  border-radius: 0.3rem;
  text-align: center;
}

.handling-mode:hover span {
  filter: brightness(1.2);
}

.handling-mode input:checked + span {
  border-color: var(--accent);
  background: var(--mode-faint);
  color: var(--accent-strong);
  font-weight: 700;
}

.handling-mode input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

@media (max-width: 42rem) {
  .handling-row {
    grid-template-columns: 3rem minmax(0, 1fr);
  }

  .handling-value,
  .handling-about {
    grid-column: 1 / -1;
  }
}

.form-status {
  min-height: 1.2rem;
  margin: 0;
  color: var(--muted);
  font-size: var(--text-min);
}

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

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

.signed-in-name {
  margin: 0 0 0.3rem;
  font-size: 1.6rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.leaderboard-list {
  display: grid;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.leaderboard-row {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr) auto auto;
  gap: 0.7rem;
  align-items: center;
  padding: 0.5rem 0.6rem;
  border-radius: 0.5rem;
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
}

a.leaderboard-row {
  border: 1px solid transparent;
  color: var(--text);
  text-decoration: none;
  transition: background 0.12s ease, border-color 0.12s ease;
}

a.leaderboard-row:hover {
  border-color: var(--mode-line);
  background: var(--mode-faint);
  text-decoration: none;
}

.leaderboard-rank {
  color: var(--muted);
  font-weight: 800;
}

/* The podium: gold, silver and bronze from the palette (O, the light wall
   tile and L). */
.leaderboard-list > :nth-child(1) .leaderboard-rank { color: var(--mino-O); text-shadow: 0 0 8px rgba(220, 191, 0, 0.5); }
.leaderboard-list > :nth-child(2) .leaderboard-rank { color: #c9d2e0; }
.leaderboard-list > :nth-child(3) .leaderboard-rank { color: var(--mino-L); }

.leaderboard-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.leaderboard-time {
  color: var(--accent);
  font-weight: 700;
}

.leaderboard-pieces {
  color: var(--muted);
}

/* --------------------------------------------------------- replay pages */

.page-main {
  width: min(1080px, 100%);
  margin: 0 auto;
  padding: clamp(1.2rem, 3vw, 2.2rem);
}

.page-main > * {
  animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}

.page-main > :nth-child(2) { animation-delay: 70ms; }
.page-main > :nth-child(3) { animation-delay: 140ms; }

/* The replay page has a heading and a status line above the stage, so the
   well height budget also removes that chrome. */
.page-replay .stage {
  /* The replay panels use less horizontal padding than the sprint page's. */
  --side-w: max(10rem, calc(var(--cell, 1.4rem) * 4 + 1.725rem));
  --next-w: max(8rem, calc(var(--cell, 1.4rem) * 4 + 1.725rem));
}

.page-replay .page-main {
  --pad-y: clamp(0.4rem, 1.2vh, 1rem);
  /* The heading, the status line and the full-width timeline. */
  --chrome-h: 17.25rem;
  --well-h: min(
    calc(100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--chrome-h) - 0.25rem),
    calc((100vw - 4.1rem) * 1.155)
  );
  --well-w: calc((var(--well-h) + var(--well-wall) * 3.6) * 10 / 23);
  --cell: calc((var(--well-w) - 2 * var(--well-wall)) / 10);
  width: min(1400px, 100%);
  padding: var(--pad-y) clamp(0.9rem, 3vw, 2rem);
}

/* A PvCPU replay with both boards uses the live PvCPU grid inside the page
   (its well height also removes the heading, through --chrome-h). */
.page-replay.is-vs .page-main {
  width: 100%;
}

/* The heading's status line starts on its own row when the heading is the
   normal top-of-page block. */
.replay-head .status-line {
  flex: 1 0 100%;
}

.replay-shell,
.replay-content {
  min-width: 0;
}

/* A solo replay in landscape: the whole heading, run facts, actions, debug
   line and status move into a Coach-style side panel, so the well gets the
   height. PvCPU replays keep the heading at the top (their race is a
   different layout), and portrait keeps it there too. */
@media (orientation: landscape) and (min-width: 1000px) {
  .page-replay:not(.is-vs):not(.is-pvcpu) .page-main {
    --chrome-h: 10.5rem;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-shell {
    display: grid;
    grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0.7rem;
    min-width: 0;
    margin: 0;
    padding: 0.9rem 1rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-top-color: var(--mode-line);
    border-radius: var(--radius-md);
    background:
      linear-gradient(180deg,
        rgba(255, 255, 255, 0.045) 0%,
        rgba(255, 255, 255, 0.015) 38%,
        rgba(255, 255, 255, 0) 38.5%),
      linear-gradient(180deg, #1d1e23 0%, #141518 100%);
    box-shadow: var(--shadow-panel);
    position: sticky;
    top: calc(var(--topbar-h) + 0.5rem);
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head > div {
    min-width: 0;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .replay-title {
    font-size: clamp(1.1rem, 1.6vw, 1.45rem);
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .replay-meta,
  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .status-line {
    font-size: var(--text-min);
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .replay-debug {
    overflow-wrap: anywhere;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .control-row {
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .control-row .btn {
    width: 100%;
    text-align: center;
  }

  .page-replay:not(.is-vs):not(.is-pvcpu) .replay-head .status-line {
    flex: 0 0 auto;
    margin: 0;
  }
}

.page-replay .pvcpu-layout {
  width: 100%;
  padding: 0;
}

.vs-result {
  color: var(--text);
  font-weight: 700;
}

.page-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem;
  margin-bottom: 1rem;
}

.page-heading h1 {
  margin: 0;
  font-size: clamp(1.4rem, 3vw, 2rem);
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The columns belong to the list, and each row is a subgrid of it, so they
   line up from row to row: mode, date, time, then pieces and faults as a
   count and a unit each. */
.replay-list {
  display: grid;
  grid-template-columns: minmax(9rem, 1.4fr) repeat(6, auto);
  gap: 0.45rem 0.8rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.replay-list > li {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

.replay-list > li > p {
  grid-column: 1 / -1;
}

.replay-row {
  --row-c: var(--mino-I);
  position: relative;
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  padding: 0.7rem 0.9rem 0.7rem 1.1rem;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 0.6rem;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0) 50%),
    linear-gradient(180deg, #1a1b20, #141518);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 10px 20px -14px rgba(0, 0, 0, 0.9);
  color: var(--text);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease;
}

/* The row's mode as a coloured bar on its left edge (replays.js sets
   data-mode). */
.replay-row::before {
  content: "";
  position: absolute;
  top: 0.55rem;
  bottom: 0.55rem;
  left: 0;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--row-c);
  box-shadow: 0 0 10px color-mix(in srgb, var(--row-c) 60%, transparent);
}

.replay-row[data-mode="pvcpu"] {
  --row-c: var(--mino-Z);
}

.replay-row:hover {
  transform: translateY(-1px);
  border-color: var(--mode-line);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 16px 28px -14px rgba(0, 0, 0, 0.9),
    0 12px 24px -16px var(--mode-glow);
  text-decoration: none;
}

.replay-mode {
  color: #fff;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.replay-date,
.replay-pieces,
.replay-faults,
.replay-unit {
  color: var(--muted);
}

.replay-pieces,
.replay-faults {
  justify-self: end;
}

/* Pull the unit back across most of the column gap, to a word space from
   its count. */
.replay-unit {
  margin-left: -0.5rem;
}

.replay-time {
  color: var(--accent);
  font-weight: 700;
}

/* The time's leading zeros and fraction, in the darker shade the HUD gives
   its decimals (.stat-frac). */
.replay-shade {
  color: color-mix(in srgb, currentColor 58%, #000);
}

.replay-open {
  justify-self: end;
}

.replay-controls {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

/* The graph and playback controls sit below the well, in one strip. The
   graph is the scrub control, so there is no separate slider. */
.replay-under {
  display: grid;
  gap: 0.45rem;
  margin-top: 0.7rem;
  padding: 0.55rem 0.7rem;
}

.replay-under-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

.replay-under .replay-controls {
  flex: 1 1 auto;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.8rem;
}

.replay-under .speed-buttons {
  grid-template-columns: repeat(4, auto);
}

.replay-under .replay-clock {
  margin-left: auto;
  white-space: nowrap;
}

.control-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

.speed-buttons {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* The speed graph above the scrub bar: PPS over time, with its playhead. */
.speedgraph-host {
  position: relative;
  display: block;
  width: 100%;
  min-height: 64px;
  border: 1px solid var(--mode-line);
  border-radius: 6px;
  background: color-mix(in srgb, var(--mode) 6%, rgba(0, 0, 0, 0.18));
  overflow: hidden;
  cursor: ew-resize;
  touch-action: none;
}

.speedgraph-host:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.speedgraph-hit {
  stroke: transparent;
  stroke-width: 26;
  pointer-events: stroke;
  cursor: ew-resize;
}

.speedgraph-svg {
  display: block;
  width: 100%;
}

.speedgraph-grid {
  stroke: currentColor;
  stroke-width: 1;
  opacity: 0.12;
}

.speedgraph-tick,
.speedgraph-axis {
  fill: var(--muted);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
}

.speedgraph-axis {
  opacity: 0.7;
}

.speedgraph-area {
  fill: color-mix(in srgb, var(--mode) 20%, transparent);
}

.speedgraph-line {
  fill: none;
  stroke: var(--accent-strong);
  stroke-width: 1.6;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.speedgraph-baseline {
  stroke: var(--mode-line);
  stroke-width: 1;
}

.speedgraph-progress {
  fill: color-mix(in srgb, var(--mode) 8%, transparent);
}

.speedgraph-playhead {
  fill: none;
  stroke: #fff;
  stroke-width: 1.6;
  opacity: 0.9;
  stroke-linecap: square;
  vector-effect: non-scaling-stroke;
}

.speedgraph-dot {
  fill: var(--accent-strong);
  stroke: #0b0d12;
  stroke-width: 1;
}

.speedgraph-readout {
  position: absolute;
  top: 3px;
  right: 6px;
  color: var(--muted);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.replay-clock {
  color: var(--muted);
  font-size: var(--text-min);
  font-variant-numeric: tabular-nums;
}

.status-line {
  min-height: 1.2rem;
  margin: 0;
  color: var(--muted);
  font-size: var(--text-min);
}

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

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

/* Below the width where two flanked wells plus the centre panel fit, the
   centre panel moves to a compact strip above the race. */
@media (max-width: 1240px) {
  .pvcpu-layout {
    --strip-h: 7.5rem;
    --well-h-cap: calc(
      100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--strip-h) - var(--col-gap)
      - var(--chrome-h, 0px)
    );
    /* Two wells, no centre column: the width left after four preview slots
       turns into board height. */
    --well-h: min(
      var(--well-h-cap),
      calc((100vw - 7rem) * 0.5929)
    );
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .versus-panel {
    position: static;
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .versus-panel .panel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 1rem;
    padding: 0.45rem 0.7rem;
  }

  .versus-panel .race-detail {
    font-size: var(--text-min);
  }

  .versus-panel .field {
    gap: 0.2rem;
    font-size: var(--text-min);
  }

  .versus-panel .versus-title,
  .versus-panel .muted,
  .versus-panel .race,
  .versus-panel .status-line,
  .versus-panel .hint {
    margin: 0;
  }

  .versus-panel .muted {
    flex: 1 1 16rem;
    font-size: var(--text-min);
  }

  .versus-panel .race {
    flex: 1 1 16rem;
    flex-direction: row;
    gap: 1rem;
  }

  .versus-panel .race-row {
    flex: 1 1 8rem;
    gap: 0.15rem;
  }

  .versus-panel .field select {
    padding: 0.4rem 0.5rem;
  }

  .versus-panel .status-line {
    flex: 1 1 100%;
    min-height: 0;
  }

  .versus-panel .controls-form {
    flex: 1 1 auto;
    grid-auto-flow: column;
    grid-auto-columns: minmax(7rem, 1fr);
    align-items: end;
    gap: 0.35rem;
  }

  /* CPU-Humanish's pickers join the strip's row; its blurb and the
     Advanced sliders, when open, take a row of their own. */
  .versus-panel .humanish-settings {
    display: contents;
  }

  /* The rank picker takes a row of its own: the ranks in one line where it
     fits, wrapping where it doesn't. */
  .versus-panel .rank-field {
    flex: 1 1 100%;
  }

  .versus-panel .rank-picker {
    display: flex;
    flex-wrap: wrap;
    margin-inline: 0;
  }

  .versus-panel .rank-button {
    flex: 1 0 3rem;
    height: 2rem;
  }

  .versus-panel .rank-button[data-rank="full"] {
    flex: 3 0 8rem;
  }

  .versus-panel .humanish-about {
    flex: 1 1 12rem;
    font-size: var(--text-min);
  }

  .versus-panel .humanish-advanced {
    flex: 0 1 auto;
  }

  .versus-panel .humanish-advanced[open] {
    flex: 1 1 100%;
  }

  .versus-panel .slider-list {
    grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
    gap: 0.4rem 1rem;
  }

  /* A PvCPU replay's strip holds the race panel; the wells give up its
     height. The playback controls sit below the whole layout. */
  .page-replay .pvcpu-layout {
    --strip-h: 11.5rem;
  }

}

@media (max-width: 900px) {
  .stage {
    --stage-gap: 0.7rem;
  }

  /* No room for two flanked wells: stack the player columns. Each well still
     takes the full height left under the top bar and the centre strip. */
  .pvcpu-layout {
    /* The stacked layout scrolls vertically, so the well can take the slack
       under the strip; 7rem keeps the first board inside the viewport. The
       row still has to fit one board between two four-mino preview slots. */
    --strip-h: 7.1rem;
    --well-h-cap: calc(
      100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--strip-h) - var(--col-gap)
      - var(--chrome-h, 0px)
    );
    --well-h: min(
      var(--well-h-cap),
      calc((100vw - 4.2rem) * 1.219)
    );
    grid-template-columns: minmax(0, 1fr);
  }

  .player-side {
    grid-row: 2;
  }

  .cpu-side {
    grid-row: 3;
  }

  .home-grid {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }

  /* Two columns: Sprint spans the first row. */
  .mode-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .mode-card--sprint {
    grid-row: auto;
    grid-column: 1 / -1;
  }
}

@media (min-width: 901px) {
  .page-play .hint {
    white-space: nowrap;
  }
}

/* Too narrow to flank the well: the board leads, hold/stats and the next
   queue sit side by side underneath it. */
@media (max-width: 760px) {
  /* Reflowed layouts are width-bound: the board spans the page, so take the
     height cap from the page width instead of the (now negative) desktop
     furniture allowance. */
  .game-layout,
  .page-replay .page-main {
    --well-h: min(
      calc(100vh - var(--topbar-h) - 2 * var(--pad-y) - 0.5rem),
      calc((100vw - 1.5rem) * 2.3)
    );
  }

  .pvcpu-layout {
    /* The board row also carries the garbage meter beside the well. */
    --well-h: min(
      calc(100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--strip-h) - var(--col-gap)
        - var(--chrome-h, 0px)),
      calc((100vw - 3.5rem) * 2.3)
    );
  }

  .stage,
  .side-stage {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.7rem;
  }

  .stage-board {
    grid-column: 1 / -1;
    order: -1;
    width: 100%;
  }

  .stage-side {
    width: 100%;
    margin-top: 0;
  }
}

@media (max-width: 640px) {
  .topbar {
    flex-wrap: wrap;
    gap: 0.6rem;
    height: auto;
    min-height: var(--topbar-h);
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
  }

  .topnav {
    order: 3;
    flex-wrap: wrap;
    width: 100%;
    justify-content: center;
  }

  .account {
    margin-left: auto;
  }

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

  .replay-list {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 0.7rem;
  }

  .replay-date,
  .replay-pieces,
  .replay-faults,
  .replay-unit {
    display: none;
  }
}

/* Tighter links on a phone. What still does not fit wraps instead of
   scrolling the page sideways. */
@media (max-width: 460px) {
  .topnav {
    gap: 0.1rem;
  }

  .topnav a {
    padding: 0.35rem;
  }
}

/* Portrait screens (1440x2560 and the like). A single well is as wide as
   the page lets it be, which leaves height to spare: the stage sits in the
   middle of the height rather than under the top bar with the spare height
   below it. PvCPU stacks the two players, yours on top, with the centre
   panel beside them, so each well takes half the height instead of a
   third of the width. */
@media (min-width: 901px) and (max-aspect-ratio: 4/5) {
  .game-layout {
    min-height: calc(100vh - var(--topbar-h));
    align-content: center;
  }

  /* The replay's heading stays with its board. */
  .page-replay .page-main {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: calc(100vh - var(--topbar-h));
  }

  .pvcpu-layout {
    --centre-w: clamp(12rem, 18vw, 18rem);
    --well-h-cap: calc(
      (100vh - var(--topbar-h) - 2 * var(--pad-y) - var(--col-gap) - var(--chrome-h, 0px)
        - 0.5rem) / 2
    );
    /* One board between two four-mino preview slots, beside the centre
       panel. */
    --well-h: min(
      var(--well-h-cap),
      calc((100vw - 4.2rem - var(--centre-w) - var(--col-gap)) * 1.219)
    );
    grid-template-columns: minmax(0, 1fr) var(--centre-w);
  }

  .player-side {
    grid-column: 1;
    grid-row: 1;
  }

  .cpu-side {
    grid-column: 1;
    grid-row: 2;
  }

  .versus-panel {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
}

/* PvCPU: the match starts here, after the bot is picked. */
.start-button {
  width: 100%;
}
