/*
 * The blindfold trainer.
 *
 * The design has one idea: the board is a void, and solving it is what turns the
 * void into a board. So the squares carry no colour at all while the user is
 * blind — just enough of a grid to aim at — and gain their light and dark only on
 * the reveal. That is also why the pieces can be Cburnett's: black pieces are
 * black-on-black against a void and only become legible once real squares are
 * under them.
 *
 * Colour tokens are redefined per theme and components only ever read the tokens,
 * so the two themes cannot drift. The board's own square colours are the
 * deliberate exception: a chessboard is a chessboard, and it keeps its wood in
 * both themes.
 */

:root {
  --ink: #0e141a;
  --slate: #19232e;
  --raised: #212e3b;
  --line: #33424f;
  --chalk: #e9e5da;
  --chalk-dim: #9aa6b0;
  --chalk-faint: #5d6c79;
  --amber: #d9a65b;
  --teal: #4fa9a3;
  --rose: #c97a6d;
  --void: #141d25;
  --void-line: #2b3a47;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Comfortable touch target on a coarse pointer (~44px). One value so the
   * min-height and the square icon buttons in the coarse-pointer block agree. */
  --tap-target: 2.75rem;

  /* The revealed board. Not themed — see the note above. */
  --square-light: #d7c9ad;
  --square-dark: #8f7a5c;
  --square-played: #d9a65b;

  /*
   * Files and ranks per side. Must equal `constants::BOARD_SIDE`, which is what
   * Rust lays the same pieces out with — one fact, and it used to be a `12.5%`
   * here against a computed percentage there, so only one of them would have
   * adapted. `nth-child` cannot take a `var()`, so the two selectors below spell
   * it out and say why.
   */
  --board-side: 8;

  /*
   * Text on an amber ground. Outside the theme blocks deliberately: the primary
   * button and the arrow badges stay amber in both themes, so their text has to
   * stay dark in both. This is the file's second exception to "components only
   * read tokens", and it is one on purpose rather than a missed token.
   */
  --on-amber: #0e141a;

  /*
   * The armed-mic red. Outside the theme blocks like `--on-amber`: a record button
   * reads as "recording" by being the same bright red in both themes, so it does not
   * follow the palette. Its text is white in both.
   */
  --rec: #e5484d;
}

@media (prefers-color-scheme: light) {
  :root {
    --ink: #ebe8e0;
    --slate: #f7f5ef;
    --raised: #ffffff;
    --line: #d2cdc1;
    --chalk: #1a232d;
    --chalk-dim: #56626e;
    --chalk-faint: #8d98a3;
    --amber: #9c6712;
    --teal: #25706c;
    --rose: #a34b3a;
    --void: #e2ded3;
    --void-line: #c8c2b3;
  }
}

:root[data-theme="dark"] {
  --ink: #0e141a;
  --slate: #19232e;
  --raised: #212e3b;
  --line: #33424f;
  --chalk: #e9e5da;
  --chalk-dim: #9aa6b0;
  --chalk-faint: #5d6c79;
  --amber: #d9a65b;
  --teal: #4fa9a3;
  --rose: #c97a6d;
  --void: #141d25;
  --void-line: #2b3a47;
}

:root[data-theme="light"] {
  --ink: #ebe8e0;
  --slate: #f7f5ef;
  --raised: #ffffff;
  --line: #d2cdc1;
  --chalk: #1a232d;
  --chalk-dim: #56626e;
  --chalk-faint: #8d98a3;
  --amber: #9c6712;
  --teal: #25706c;
  --rose: #a34b3a;
  --void: #e2ded3;
  --void-line: #c8c2b3;
}

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

body {
  margin: 0;
  background: var(--ink);
  color: var(--chalk);
  font-family: var(--sans);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.app {
  max-width: 1060px;
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3.5rem) clamp(1rem, 3vw, 2rem) 4rem;
}

/* ---- masthead ---------------------------------------------------------- */

.masthead {
  margin-bottom: 2rem;
}

.masthead__eyebrow {
  margin: 0 0 0.8rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--chalk-faint);
}

.masthead__title {
  margin: 0 0 0.8rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.masthead__lede {
  margin: 0;
  max-width: 62ch;
  color: var(--chalk-dim);
}

/* ---- header row (status + view controls) ------------------------------- */

/* One row across the top: the rating/pool on the left, the board's view controls on
 * the right. The divider under the whole row lives here, not on `.statusbar`, so it
 * spans both. */
.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--line);
}

.statusbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  /* Fills the header row so the view controls are pushed to its right end. */
  flex: 1;
}

.elo {
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--chalk-dim);
}

.elo strong {
  margin-left: 0.15rem;
  color: var(--amber);
  font-weight: 600;
  font-size: 1.3rem;
}

/* The change from the last scored puzzle, chess.com's "+8". */
.elo__delta {
  margin-left: 0.5rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 700;
}

.elo__delta--up {
  color: var(--teal);
}

.elo__delta--down {
  color: var(--rose);
}

/* ---- layout ------------------------------------------------------------ */

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

.layout__panels {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

/* A passthrough on desktop: the board is laid out as a direct child of
 * `.layout__board`, full width. On a phone the frame becomes a size container that
 * sizes the board to fit (see the mobile block). */
.board-frame {
  display: contents;
}

@media (max-width: 840px) {
  /* A fixed, full-height app shell: the page itself never scrolls. The core loop —
   * read the pinned roster, draw on the board, submit — fits one screen, and the
   * board flexes to fill the height left between the roster on top and the controls
   * below. Only the roster and the drawn-line list scroll, each inside its own capped
   * region; the page never does. */
  html,
  body {
    height: 100%;
    overflow: hidden;
  }

  .app {
    height: 100dvh;
    max-width: none;
    margin: 0;
    padding: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  /* Marketing copy and footer are noise on a phone that is here to solve — dropping
   * them is what frees the vertical room for a full-size board. */
  .masthead,
  .colophon {
    display: none;
  }

  .topbar {
    margin: 0;
    padding-bottom: 0.4rem;
  }

  /* The layout fills the space under the rating bar and stacks in one column: the
   * roster hoisted to the top, the board in the middle taking the slack, the line
   * below. `display: contents` drops the panel wrapper so its two regions become flex
   * siblings of the board that `order` can place. */
  .layout {
    display: flex;
    flex-direction: column;
    /* Reset the desktop grid's `align-items: start`: in this column it would leave
     * each region at its content width instead of filling the phone's width. */
    align-items: stretch;
    flex: 1;
    min-height: 0;
    gap: 0.5rem;
  }

  .layout__panels {
    display: contents;
  }

  .layout__roster {
    order: 1;
    flex: 0 1 auto;
    min-height: 0;
    max-height: 30%;
    overflow-y: auto;
  }

  .layout__board {
    order: 2;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }

  .layout__line {
    order: 3;
    /* Size to its (now compact) content while editing so the board keeps the slack;
     * the post-reveal move list may grow, capped, then scrolls. */
    flex: 0 0 auto;
    min-height: 0;
    max-height: 45%;
  }

  /* The board fills the slack between roster and line while staying square. Its frame
   * is a size container, so the board takes the smaller of the frame's width and
   * height (`cqmin`) — shrinking on a short screen instead of forcing a page scroll. */
  .board-frame {
    display: grid;
    place-items: center;
    flex: 1 1 auto;
    min-height: 0;
    container-type: size;
  }

  .board-frame .board {
    width: 100cqmin;
    height: 100cqmin;
  }

  /* The puzzle id under the board is a bug-report aid, not worth vertical space here. */
  .layout__board .facts {
    display: none;
  }

  /* Keep the controls and verdict on screen while the move list scrolls inside the
   * panel — so Submit and the reveal stepper never need a page scroll. */
  .layout__line .panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    padding: 0.6rem 0.8rem;
  }

  /* The panel title and the drawn-arrow list are redundant on a phone — the arrows are
   * already numbered on the board — so drop them to hand their height to the board. The
   * post-reveal move list (`.movelist`) stays: it is how the solution is stepped. */
  .layout__line .panel__title,
  .layout__line .line {
    display: none;
  }

  .layout__line .movelist {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
  }

  .layout__line .controls {
    margin: 0;
  }

  .layout__line .verdict {
    margin: 0.35rem 0 0;
  }

  /* The roster sits at the top of the fixed shell — always on screen without a pin —
   * and is compacted: the "Roster" label is redundant when the roster *is* what is on
   * screen, and each side's pieces flow into a wrapping row rather than a tall column
   * of one entry per line, so it stays a thin strip over a full-size board. */
  .layout__roster .panel {
    padding: 0.55rem 0.75rem;
  }

  .layout__roster .panel__title {
    display: none;
  }

  .layout__roster .panel__lede {
    margin-bottom: 0.35rem;
  }

  .layout__roster .panel__tomove {
    font-size: 0.9rem;
  }

  .layout__roster .side {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.1rem 0.5rem;
  }

  .layout__roster .side + .side {
    margin-top: 0.3rem;
  }

  /* The side name is a compact lead-in *inline* with its wrapping piece row (not on
   * its own line), to save two lines of height; only the castling note keeps its own
   * line, since it is a sentence. */
  .layout__roster .side__name {
    margin: 0 0.3rem 0 0;
  }

  .layout__roster .side__castling {
    width: 100%;
    margin: 0.15rem 0 0;
  }

  .layout__roster .entry {
    padding: 0;
    gap: 0.25rem;
  }

  .layout__roster .entry__piece {
    width: 1.1rem;
    height: 1.1rem;
  }
}

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

.board {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border: 1px solid var(--line);
  background: var(--void);
  cursor: crosshair;
  overflow: hidden;
  /* Or the browser pans the page instead of delivering the drag. */
  touch-action: none;
  user-select: none;
}

.board__squares {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--board-side), 1fr);
  grid-template-rows: repeat(var(--board-side), 1fr);
}

/*
 * Arrows and pieces sit over the squares and must never eat a pointer event: a
 * drag across an arrow already drawn has to reach the square underneath it.
 */
.board__arrows,
.board__pieces {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.board__arrows {
  width: 100%;
  height: 100%;
  color: var(--amber);
  transition: opacity 0.5s ease;
}

/*
 * The line stays up through the reveal — seeing your own arrows sitting on the
 * position you could not see is the payoff — but it steps back so the pieces
 * moving under it are what the eye follows. The panel still lists it in full.
 */
.board--revealed .board__arrows {
  opacity: 0.3;
}

/* The void. Squares are all one colour, with a hairline grid to aim along. */
.square {
  position: relative;
  border-right: 1px solid var(--void-line);
  border-bottom: 1px solid var(--void-line);
  background: transparent;
  /*
   * Short, because this also governs a drag highlight being *removed*: at 0.5s
   * the last squares you touched sit there glowing long after the drag, which
   * reads as a stuck highlight rather than a fade.
   */
  transition: background-color 0.16s ease;
}

/* The last square of each row, and the whole last row. `nth-child` takes no
 * `var()`, so these two are the one place `--board-side` is spelled out: `8n` is
 * every eighth, and `n + 57` is `--board-side * (--board-side - 1) + 1`. */
.square:nth-child(8n) {
  border-right: none;
}

.square:nth-child(n + 57) {
  border-bottom: none;
}

/* ...and this is the reveal: the grid becomes a chessboard. */
.board--revealed .square {
  border-color: transparent;
  /* The reveal is the payoff and swells; the value that wins is the one in
   * effect after the class change, so this governs the void-to-board fade. */
  transition: background-color 0.5s ease;
}

.board--revealed .square--light {
  background: var(--square-light);
}

.board--revealed .square--dark {
  background: var(--square-dark);
}

.board--revealed .square--played {
  background: var(--square-played);
}

.square--from,
.square--to {
  background: color-mix(in srgb, var(--amber) 18%, transparent);
  transition: none;
}

/* The square under the pointer when not mid-drag — a light touch so it reads as
 * "you are here" without competing with the drag's own from/to highlight. */
.square--hover {
  background: color-mix(in srgb, var(--amber) 9%, transparent);
  transition: none;
}

.coord {
  position: absolute;
  font-family: var(--mono);
  font-size: 0.52rem;
  color: var(--chalk-faint);
  pointer-events: none;
}

.board--revealed .coord {
  color: color-mix(in srgb, #000 55%, transparent);
}

.coord--file {
  right: 3px;
  bottom: 2px;
}

.coord--rank {
  top: 2px;
  left: 3px;
}

/* ---- arrows ------------------------------------------------------------ */

.arrow line {
  stroke: currentColor;
}

/* The head is filled like the shaft is stroked: currentColor, resolved from the
 * arrow group's per-move `color`. Inline in the group (not a shared <marker>) so it
 * actually inherits that colour — see constants::ARROW_HEAD_LENGTH. */
.arrow__head {
  fill: currentColor;
}

.arrow--ghost {
  opacity: 0.45;
}

.arrow__badge {
  fill: currentColor;
}

/* Size and baseline come from `constants`, not from here: they are tuned against
 * `ARROW_BADGE_RADIUS`, which is Rust's.
 *
 * White with a dark outline rather than a single contrasting colour: the badge is
 * now one of eight arrow colours (`ARROW_COLORS`), so no one fill reads on all of
 * them. The stroke is painted under the fill (`paint-order`) so it thickens the
 * glyph's edge without eating into it. */
.arrow__number {
  fill: #fff;
  stroke: rgba(0, 0, 0, 0.55);
  stroke-width: 3px;
  paint-order: stroke;
  font-family: var(--mono);
  font-weight: 700;
  text-anchor: middle;
}

/* ---- pieces ------------------------------------------------------------ */

.piece {
  position: absolute;
  width: calc(100% / var(--board-side));
  height: calc(100% / var(--board-side));
}

/* Pieces fill the square. The board itself fades in on the reveal (the squares
 * gaining their colour); the pieces do not pop, because that pop would fire again
 * on every step through the mate, which reads as flicker rather than payoff. */
.piece svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ---- under the board --------------------------------------------------- */

/* Just the puzzle id now, kept small and muted for bug reports. */
.facts {
  margin: 0.4rem 0 0;
  font-size: 0.7rem;
  color: var(--chalk-faint);
}

/* ---- panels ------------------------------------------------------------ */

.panel {
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  background: var(--slate);
}

.panel__title {
  margin: 0 0 0.8rem;
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--chalk-faint);
}

/* The "to play" lede shares its row with the speak button: text on the left, the
 * 🔊 pushed to the right. Inline here rather than beside the title because the title
 * is hidden on a phone, which would otherwise strand the button alone on its own row.
 * The row carries the lede's bottom margin so the button does not sit over a gap. */
.panel__lede {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}

.panel__speak {
  flex: none;
}

.panel__tomove {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--amber);
}

.panel__extra {
  margin: 0.7rem 0 0;
  font-size: 0.8rem;
  color: var(--chalk-dim);
  font-style: italic;
}

.side + .side {
  margin-top: 0.9rem;
}

.side__name {
  margin: 0 0 0.3rem;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--chalk-faint);
}

.side__castling {
  margin: 0.35rem 0 0;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--chalk-dim);
}

.entry {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.1rem 0;
}

.entry__piece {
  display: block;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
}

.entry__piece svg {
  display: block;
  width: 100%;
  height: 100%;
}

/*
 * Cburnett's black pieces are black-on-transparent, which is invisible against a
 * dark panel. A drop-shadow rim reads as an outline at this size without
 * recolouring the artwork — the roster's knight has to be the same knight the
 * board reveals, or the user is learning two icon sets.
 */
.entry__piece--black svg {
  filter: drop-shadow(0 0 1px var(--chalk-dim)) drop-shadow(0 0 1px var(--chalk-dim));
}

.entry__squares {
  font-size: 0.85rem;
  letter-spacing: 0.02em;
}

/* ---- the line ---------------------------------------------------------- */

.line {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.line__empty {
  font-size: 0.82rem;
  font-style: italic;
  color: var(--chalk-faint);
}

.line__step {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.line__number {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--amber);
  color: var(--on-amber);
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 700;
}

.line__move {
  font-family: var(--mono);
  font-size: 0.84rem;
}

.line__arrow {
  color: var(--chalk-faint);
  padding: 0 0.2rem;
}

/* The per-move promotion control, shown only for a last-rank move. Pushed to the
 * right end of the step; defaults to the "—" (no promotion) button pressed, so a
 * rook lift to the last rank reads as the plain move it is. */
.line__promote {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: auto;
}

.line__promote-choice {
  display: grid;
  place-items: center;
  width: 1.35rem;
  height: 1.35rem;
  padding: 2px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--chalk-dim);
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s;
}

.line__promote-choice:hover {
  background: color-mix(in srgb, var(--amber) 18%, transparent);
}

/* The chosen piece (or "no promotion") sits pressed, so the current state is
 * legible without opening anything. */
.line__promote-choice--on {
  border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 22%, transparent);
}

.line__promote-choice svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ---- the solution move list (post-reveal analysis) --------------------- */

/* Shown in place of "Your line" once the board is revealed: the mate's plies in
 * SAN, numbered by move, each clickable to jump the board. Scrolls within itself so
 * a long line never pushes the controls off-screen. */
.movelist {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin: 0;
  padding: 0;
  max-height: 11rem;
  overflow-y: auto;
  list-style: none;
}

.movelist__row {
  display: grid;
  grid-template-columns: 1.8rem 1fr 1fr;
  align-items: center;
  gap: 0.3rem;
}

.movelist__number {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--chalk-faint);
  text-align: right;
}

.movelist__cell {
  min-width: 0;
  padding: 0.28rem 0.4rem;
  font-size: 0.84rem;
  text-align: left;
}

/* A side that did not move in this row (a line opening on Black, or ending on
 * White) leaves an empty cell so the two columns stay aligned. */
.movelist__cell--empty {
  visibility: hidden;
}

.movelist__ply {
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
  color: var(--chalk);
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}

.movelist__ply:hover {
  color: var(--amber);
  background: color-mix(in srgb, var(--amber) 12%, transparent);
}

/* The move the board is currently showing. */
.movelist__ply--on {
  border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 22%, transparent);
  color: var(--amber);
}

/* ---- controls ---------------------------------------------------------- */

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.9rem;
}

/* The record control — a compact circular button sitting inline with the
 * editing/reveal buttons (a `--icon` made round), rather than a labelled bar on its
 * own row. A mic glyph when idle; while armed, a red disc showing the silence
 * countdown, ringed by a pulse the way a recording app signals it is live. */
.button--mic {
  border-radius: 50%;
  font-size: 0.95rem;
}

/* Bright red while listening, in both themes (see `--rec`), so "recording" reads at a
 * glance the way a record button is expected to. */
.button--recording,
.button--recording:hover:not(:disabled) {
  background: var(--rec);
  border-color: var(--rec);
  color: #fff;
}

.button--mic.button--recording {
  animation: rec-ring 1.4s ease-out infinite;
}

/* An expanding, fading ring — the "live" pulse. On the box-shadow, not the button's
 * opacity, so the countdown digit inside stays fully legible. */
@keyframes rec-ring {
  0% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--rec) 55%, transparent);
  }
  70% {
    box-shadow: 0 0 0 0.55rem color-mix(in srgb, var(--rec) 0%, transparent);
  }
  100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--rec) 0%, transparent);
  }
}

/* Reveal navigation, pushed left so "Next puzzle" sits at the right end. */
.stepper {
  display: flex;
  gap: 0.3rem;
  margin-right: auto;
}

.button--step {
  padding: 0.4rem 0.7rem;
  font-size: 0.85rem;
  line-height: 1;
}

.button {
  font: inherit;
  font-size: 0.83rem;
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--raised);
  color: var(--chalk);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}

.button:hover:not(:disabled) {
  border-color: var(--amber);
  color: var(--amber);
}

.button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.button--primary {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--on-amber);
  font-weight: 600;
}

.button--primary:hover:not(:disabled) {
  filter: brightness(1.12);
  color: var(--on-amber);
}

/* A square control that carries a glyph rather than a word — the flip toggle and
 * the settings gear. */
.button--icon {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
}

/* A pressed/active toggle button — the flip while the board is flipped — in the
 * same amber the other `--on` states use (`settings__option--on`, etc.). */
.button--on {
  border-color: var(--amber);
  color: var(--amber);
}

/* "Give up" concedes the puzzle: pushed to the right end, away from the editing
 * buttons, and toned down so it never competes with Submit. Rose on hover — the
 * same muted red the refutation verdict uses — because it costs the puzzle. */
.button--give-up {
  margin-left: auto;
  color: var(--chalk-dim);
}

.button--give-up:hover:not(:disabled) {
  border-color: var(--rose);
  color: var(--rose);
}

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* ---- board bar (view controls) ---------------------------------------- */

/* Flip and settings, sitting in the header row (`.topbar`) beside the rating. */
.boardbar {
  display: flex;
  gap: 0.4rem;
  flex: 0 0 auto;
}

/* The settings gear anchors its dropdown to itself, which then floats over the
 * board rather than shoving it down when opened. */
.settings {
  position: relative;
}

.settings__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 5;
  min-width: 10rem;
  padding: 0.5rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--slate);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.settings__heading {
  margin: 0 0 0.4rem;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--chalk-faint);
}

/* Space each group off the one above — the menu stacks several (point of view, input,
 * output). The first heading keeps its flush top. */
.settings__heading:not(:first-child) {
  margin-top: 0.7rem;
}

.settings__option {
  display: block;
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 2px;
  background: transparent;
  color: var(--chalk);
  font: inherit;
  font-size: 0.83rem;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}

.settings__option:hover {
  color: var(--amber);
}

/* The chosen POV sits pressed, so the current preference reads at a glance. */
.settings__option--on {
  border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 15%, transparent);
  color: var(--amber);
}

/* ---- verdict ----------------------------------------------------------- */

.verdict {
  margin: 0.7rem 0 0;
  min-height: 1.3rem;
  font-size: 0.86rem;
}

.verdict--ok {
  color: var(--teal);
}

.verdict--no {
  color: var(--rose);
}

.verdict--hmm {
  color: var(--chalk-dim);
}

.verdict__defense {
  display: block;
  margin-top: 0.35rem;
  color: var(--chalk-dim);
  font-size: 0.8rem;
}

/* ---- colophon ---------------------------------------------------------- */

.colophon {
  max-width: 72ch;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  color: var(--chalk-faint);
  font-size: 0.83rem;
}

.colophon p {
  margin: 0 0 0.8rem;
}

.colophon a {
  color: var(--chalk-dim);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation-duration: 0.01ms !important;
  }
}

/* On a touch device the controls were laid out for a mouse, so the small glyph and
 * piece buttons are fiddly to tap. Enlarge every interactive control to a
 * comfortable target (~44px) where the pointer is coarse. */
@media (pointer: coarse) {
  .button,
  .button--step,
  .movelist__ply {
    min-height: var(--tap-target);
  }

  .button--icon,
  .line__promote-choice {
    width: var(--tap-target);
    height: var(--tap-target);
  }
}
