/*
 * Dark by default, and sized so the board stays the largest thing on screen
 * at any window size. Two people reading the same position over a phone call
 * need the pieces big and the chrome quiet.
 */

:root {
  --bg: #16150f;
  --surface: #211f18;
  --surface-raised: #2b2820;
  --border: #3a362b;
  --text: #ece6d8;
  --text-dim: #a49c88;
  --accent: #b58863;
  --accent-text: #1a1811;
  --good: #7fb069;
  --bad: #d16a5a;
  --radius: 8px;
  --gap: 0.75rem;

  /* The panel grows with the window instead of sitting at one width: on a
     large monitor there is room to spare beside a board that is bounded by
     height, and a 20rem column next to it wastes all of it. Floored so it
     never squeezes the move list, capped so it never becomes the main event.
     The board's own width subtracts this, so the two cannot disagree.

     Note the board is usually bounded by height rather than by this, so a
     wider panel normally costs no board at all — it spends space that was
     otherwise empty. On a wide but short window it does trade against the
     board, which is the price of filling a large screen. */
  --panel-width: clamp(20rem, 30vw, 44rem);

  /* The space between the columns. Fixed at 1.25rem the board and the panel
     sat welded together in the middle of a window with room to spare on both
     sides; letting it grow spends that room on separating the two things you
     are actually looking at. The board subtracts it, so widening it here can
     never push the board off the edge. */
  --layout-gap: clamp(1.25rem, 4vw, 5rem);

  /* How many of those gaps are in the row — one between board and panel, and
     a second once the terminal claims a column of its own. */
  --gap-count: 1;

  /* The terminal takes a column of its own when it is open. `--terminal-space`
     is what the board subtracts, and stays at zero while it is shut. */
  --terminal-width: clamp(20rem, 24vw, 34rem);
  --terminal-space: 0px;
}

body[data-terminal="open"] { --terminal-space: var(--terminal-width); --gap-count: 2; }

* { box-sizing: border-box; }

/*
 * The board and its panels are an instrument, not a document.
 *
 * Dragging anywhere — reaching for a piece, aiming at a move in the list —
 * used to paint headings, buttons, coordinates and move numbers blue, none
 * of which is text anybody wants on their clipboard. What you type stays
 * selectable, because that is the text that is really yours.
 */
body {
  -webkit-user-select: none;
  user-select: none;
}

input,
textarea {
  -webkit-user-select: text;
  user-select: text;
}

/* `hidden` must win over the display rules below, or a hidden row still shows. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  min-height: 100dvh;
}

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

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
  padding: 0.6rem 1rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.bar__title { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: 0.02em; }
/* Sits with the title rather than floating in the middle of the bar: the
   auto margin eats the space `space-between` would otherwise open up here. */
.bar__version {
  margin-right: auto;
  font-size: 0.7rem;
  color: var(--text-dim);
  opacity: 0.75;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.bar__meta { display: flex; align-items: center; gap: 1rem; font-size: 0.85rem; }

.status { color: var(--text-dim); }
.status::before {
  content: "●";
  margin-right: 0.4em;
  color: var(--text-dim);
}
.status[data-status="online"]::before { color: var(--good); }
.status[data-status="offline"]::before { color: var(--bad); }
.status[data-status="connecting"]::before { color: var(--accent); }

/* A quiet link out of the page, in the bar. Used by the board to reach the
   drills and by the drills to come back. */
.bar__back {
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.85rem;
  white-space: nowrap;
}
.bar__back:hover { color: var(--text); }

/* --- layout --- */

/* The board column takes whatever the board needs; the empty tracks either
   side decide where the pair sits in the window.

   Centring the *pair* left the board itself well left of centre, because the
   panel takes up the right half of it and your eye is on the board, not on
   the pair. So the leftover space is split unevenly instead — three parts
   left to one part right — which walks the board towards the middle of the
   window and leaves the slack on the far side of the panel.

   `fr` is doing the work, and it matters that it is `fr`: fr tracks divide up
   only what is *left over* once every real column has the width it asked for.
   So this can move the board but can never shrink it. When the window is
   tight, both spacers collapse to nothing and the layout is exactly what it
   was before. */
.layout {
  display: grid;
  grid-template-columns: 4fr auto var(--panel-width) 1fr;
  /* No column gap: a gap applies either side of the spacers too, and those
     two extra gaps come straight out of the space the spacers were meant to
     be spending. The one separation that is wanted is carried as a margin
     below, where it can be placed on exactly one edge. */
  column-gap: 0;
  row-gap: var(--layout-gap);
  align-items: start;
  padding: 1.25rem;
}

/* The gap that is actually wanted, on the one edge that wants it. */
.panel { margin-left: var(--layout-gap); }
.terminal { margin-right: var(--layout-gap); }

/* The two spacers. Empty on purpose — they exist to be measured. */
.layout::before,
.layout::after { content: ""; }

/* The board slides over to make room rather than being covered: a position
   you are reading should never have a window dropped on top of it. With the
   terminal open the left side is already carrying a column, so the split
   evens out. */
body[data-terminal="open"] .layout {
  grid-template-columns: 2fr var(--terminal-width) auto var(--panel-width) 1fr;
}

.board-area { display: grid; gap: 0.75rem; justify-items: center; min-width: 0; }

/* Wraps the board so the grip can sit off its corner. Sized to the board, so
   the grip is placed against the board's edge and not the column's. */
.board-frame { position: relative; width: fit-content; max-width: 100%; }

/* --- icons ---

   Every control glyph is one SVG, painted through `mask` rather than dropped
   in as an <img>. The files are solid black, which on this theme would be a
   black shape on a dark button; a mask throws the colour away and keeps only
   the alpha, so the glyph takes `currentColor` and follows the same dim/bright
   states the text glyphs used to. It works equally for the filled icons and
   the stroked ones, because a stroke has alpha too. */
/* The corner grip, in the spirit of the one on chess sites. */
.resize {
  position: absolute;
  /* Below the board's right edge, not diagonally off its corner. The board
     column is `auto`, so anything hanging off the right runs straight out of
     the column and under the panel, which is drawn after it and wins. Sitting
     on the corner was worse still — it covered two playable squares. Under
     the edge is clear of both. */
  right: 0;
  bottom: -1.95rem;
  width: 1.5rem;
  height: 1.5rem;
  /* Nothing in this column should ever be able to bury it mid-drag. */
  z-index: 2;
  padding: 0;
  border: none;
  cursor: nwse-resize;
  /* Claim the gesture so a drag on a phone resizes instead of scrolling. */
  touch-action: none;
  color: var(--text-dim);
  opacity: 0.6;
  transition: opacity 120ms ease;
}

/* Never a filled button, in any state. The shared `button` rules paint a
   surface on hover and hold it while pressed, and on something this small
   that block landed over the icon rather than behind it. */
.resize,
.resize:hover,
.resize:active,
.resize:focus-visible,
.resize[data-dragging] { background: none; }

/* The press nudge belongs on buttons you click, not on one you drag: the
   handle would drop a pixel the moment the drag began. */
.resize:active { transform: none; }

/* Arrows to the four corners — the thing being resized, not a texture. */
.resize::before {
  content: "";
  position: absolute;
  inset: 0;
  --icon: url("../icon/resize.svg");
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

.resize:hover,
.resize:focus-visible,
.resize[data-dragging] { opacity: 1; color: var(--text); }

/* Square, and bounded by the window rather than by its column: as tall as the
   viewport allows, as wide as the space left beside the panel, and no larger
   than the size asked for. With no `--board-size` set the first term drops out
   and the board simply fills the window. */
.board {
  width: min(
    var(--board-size, 100vmax),
    calc(100dvh - 11rem),
    calc(
      100vw - var(--panel-width) - var(--terminal-space)
      - (var(--layout-gap) * var(--gap-count))
      - 3rem
    )
  );
  aspect-ratio: 1;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 8px 28px rgb(0 0 0 / 0.45);
}

/* Kept to one line: if this wrapped it would change the height of everything
   below the board, and the board sizes itself around exactly that. */
.position {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.9rem;
  text-align: center;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.position[data-over="true"] { color: var(--accent); font-weight: 600; }

/* --- controls --- */

.controls { display: flex; gap: 0.4rem; }

button, select, input, textarea {
  font: inherit;
  color: var(--text);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.45rem 0.7rem;
  max-width: 100%;
}

/* --- switches ---

   A checkbox drawn as a sliding toggle. The browser's own is a blue tick from
   another operating system's palette, and next to warm greys and a brown
   accent it reads as something the page did not mean to include.

   Still a real `<input type="checkbox">` underneath: `appearance: none` takes
   the paint away and leaves the behaviour, so the label still toggles it, the
   keyboard still reaches it, space still flips it, and a screen reader still
   calls it a checkbox. A div with a click handler would have thrown all of
   that away for the same picture. */
.switch {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 2.15rem;
  height: 1.2rem;
  padding: 0;
  border-radius: 999px;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease;
}

/* The knob. Sized off the track so the two stay in proportion if the track
   is ever resized. */
.switch::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.13rem;
  width: 0.86rem;
  height: 0.86rem;
  border-radius: 50%;
  background: var(--text-dim);
  translate: 0 -50%;
  transition: translate 140ms ease, background-color 140ms ease;
}

.switch:hover { border-color: var(--text-dim); }

.switch:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.switch:checked::after {
  translate: 0.93rem -50%;
  background: var(--accent-text);
}

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

@media (prefers-reduced-motion: reduce) {
  .switch, .switch::after { transition: none; }
}

/* Fields size to the panel, not to their longest option. */
select, input, textarea { width: 100%; min-width: 0; }


button { cursor: pointer; }
button:hover { background: #38342a; }
button:active { transform: translateY(1px); }
button:focus-visible, select:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.controls button { min-width: 3rem; }

/* The five glyphs. Sized a little larger than the text arrows they replace,
   because a stroked icon reads lighter than a solid character does. */
.controls button::before {
  content: "";
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

.controls [data-action="first"] { --icon: url("../icon/backward-fast.svg"); }
.controls [data-action="prev"]  { --icon: url("../icon/previous.svg"); }
.controls [data-action="next"]  { --icon: url("../icon/next.svg"); }
.controls [data-action="last"]  { --icon: url("../icon/forward-fast.svg"); }
.controls [data-action="flip"]  { --icon: url("../icon/flip-vertical.svg"); }
.button--warn { color: var(--bad); }

.buttons { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.buttons button { flex: 1 1 8rem; }

/* --- side panel --- */

.panel {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem;
  max-height: calc(100dvh - 6rem);
  position: sticky;
  top: 1.25rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
  overflow-x: hidden;
}

.panel__block { display: grid; gap: 0.6rem; }
.panel__block--grow { min-height: 8rem; }

/* A hairline between sections: enough to group things without drawing a box
   around every one of them. */
.panel__block + .panel__block {
  border-top: 1px solid var(--border);
  padding-top: 1.1rem;
}

.panel__heading {
  margin: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-dim);
  cursor: default;
}

summary.panel__heading { cursor: pointer; padding: 0.15rem 0; border-radius: 4px; }
summary.panel__heading:hover { color: var(--text); }

/* Browsers wrap a <details> element's content in an anonymous box, so its
   children are not grid items and `gap` never reaches them. Lay the content
   out in a wrapper of our own instead of relying on the details itself. */
details.panel__block { display: block; }
.panel__body { display: grid; gap: 0.75rem; margin-top: 0.75rem; }
.panel__body--loaders { gap: 1.4rem; }

.field { display: grid; gap: 0.35rem; }

/* An input and the button that acts on it: grouped, but not crowded. */
.loader { display: grid; gap: 0.85rem; }

/* Anywhere else a button follows a field directly, give it the same air. */
.field + button { margin-top: 0.25rem; }
.field__label {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-dim);
}

/* An input and its tick. Pressing Enter or clicking away already saved these
   fields; the tick is for a thumb on a phone, where there is no Enter key
   worth reaching for and "click away" is not a gesture anyone trusts. */
.field__entry { display: flex; align-items: stretch; gap: 0.4rem; }

.field__confirm {
  flex: none;
  width: 2.5rem;
  display: grid;
  place-items: center;
  padding: 0;
  color: var(--text-dim);
  transition: color 120ms ease, background-color 120ms ease;
}

.field__confirm::before {
  content: "";
  width: 1.05rem;
  height: 1.05rem;
  --icon: url("../icon/tick.svg");
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

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

/* Held for a moment after saving. Without it a tick that saves an unchanged
   value looks like a button that does nothing. */
.field__confirm[data-confirmed] {
  color: var(--good);
  border-color: var(--good);
}
textarea { resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }


/* --- who's here --- */

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

.who__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.3rem 0.45rem;
  border-radius: 6px;
  background: rgb(255 255 255 / 0.03);
  font-size: 0.88rem;
}

.who__row--empty { color: var(--text-dim); background: none; font-size: 0.82rem; }

.who__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A generated name is a placeholder, not a claim about who someone is. */
.who__name--anon { font-style: italic; color: var(--text-dim); }
.who__you { color: var(--text-dim); font-style: normal; }

/* The reading: a number, then bars, tallest last. */
.signal { display: inline-flex; align-items: center; gap: 0.4rem; flex: none; }

.signal__ms {
  /* Tabular figures so the number does not jiggle as it updates. */
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  color: var(--text-dim);
  min-width: 3.6em;
  text-align: right;
}
.signal__unit { opacity: 0.6; }
.signal__ms--pending { opacity: 0.5; }

.signal__bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 0.85rem; }

.signal__bar {
  width: 3px;
  border-radius: 1px;
  background: var(--border);
}
.signal__bar:nth-child(1) { height: 25%; }
.signal__bar:nth-child(2) { height: 50%; }
.signal__bar:nth-child(3) { height: 75%; }
.signal__bar:nth-child(4) { height: 100%; }

.signal__bar--on { background: var(--good); }
.signal[data-signal="weak"] .signal__bar--on { background: #d6a44c; }
.signal[data-signal="poor"] .signal__bar--on { background: var(--bad); }

/* Still measuring: show the shape of the thing without claiming a reading. */
.signal[data-signal="unknown"] .signal__bars { opacity: 0.45; }

.signal[data-signal="weak"] .signal__ms { color: #d6a44c; }
.signal[data-signal="poor"] .signal__ms { color: var(--bad); }

/* --- where the other person's pointer is --- */

/* Over the board, and deaf to the pointer: this must never be the thing a
   click lands on. */
.pointers {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
}

/* The tip of the arrow sits on the point, the way a real cursor does — hence
   no centring transform. The transition covers the gap between updates so it
   glides rather than stepping. */
.pointer {
  position: absolute;
  width: 0;
  height: 0;
  transition: left 90ms linear, top 90ms linear;
}

.pointer__arrow {
  position: absolute;
  width: 1.15rem;
  height: auto;
  overflow: visible;
  fill: var(--good);
  stroke: rgb(0 0 0 / 0.55);
  stroke-width: 1;
  /* The glow, and enough of a shadow underneath to keep it off the board. */
  filter: drop-shadow(0 0 5px rgb(94 240 138 / 0.9)) drop-shadow(0 1px 2px rgb(0 0 0 / 0.5));
}

.pointer__who {
  position: absolute;
  left: 0.9rem;
  top: 1rem;
  padding: 0.05rem 0.3rem;
  border-radius: 3px;
  background: var(--good);
  color: #0c1a10;
  font-size: 0.62rem;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 0 6px rgb(94 240 138 / 0.55);
}

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

/* A thin row above and below the board, as a chess site puts them. Kept the
   same height whether or not anything has been taken, so nothing shifts the
   board up and down as the game goes on. */
.taken {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 1.35rem;
  width: 100%;
  padding: 0 0.15rem;
}

/* Pieces of a kind, overlapping the way a handful of them would. */
.taken__group { display: flex; }
.taken__group piece {
  display: block;
  width: 1.15rem;
  height: 1.15rem;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.taken__group piece + piece { margin-left: -0.55rem; }

.taken__lead {
  font-size: 0.8rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* --- move list --- */

.movelist {
  overflow-y: auto;
  scrollbar-gutter: stable;
  max-height: 16rem;
  overscroll-behavior: contain;
}
.movelist__empty { margin: 0; color: var(--text-dim); font-size: 0.85rem; }
.movelist__list { margin: 0; padding: 0; list-style: none; }

.movelist__row {
  display: grid;
  grid-template-columns: 2.2rem 1fr 1fr;
  align-items: center;
  gap: 0.25rem;
}
.movelist__row:nth-child(odd) { background: rgb(255 255 255 / 0.025); }

.movelist__number { color: var(--text-dim); font-size: 0.8rem; text-align: right; padding-right: 0.35rem; }

.movelist__move {
  background: none;
  border: none;
  border-radius: 4px;
  padding: 0.15rem 0.35rem;
  text-align: left;
  font-variant-numeric: tabular-nums;
}
.movelist__move:hover { background: var(--surface-raised); }

/* The moving piece, drawn rather than spelled. Chessground sizes pieces only
   inside `.cg-wrap`, so out here it is ours to set: a square box the height of
   the line, dropped below the baseline so the piece sits on it rather than
   floating above it. `vertical-align` is the knob if a set sits high or low —
   the sets do not agree about how much padding to leave inside their viewBox. */
.movelist__move piece {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.24em;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.movelist__move[aria-current="true"] {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* --- the chooser --- */

/* A native <select> opens a menu the operating system draws — white, in the
   system's fonts, immune to styling. This is the same control in the board's
   colours. It expands inline rather than floating: the panel scrolls, and a
   floating list would be clipped at its edge. */
.select { position: relative; display: grid; gap: 2px; }

.select__value {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  text-align: left;
}
.select__chosen { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select__arrow { flex: none; color: var(--text-dim); font-size: 0.8rem; }
.select[data-open] .select__arrow { transform: rotate(180deg); }

.select__list {
  list-style: none;
  margin: 0;
  padding: 0.2rem;
  display: grid;
  gap: 1px;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  max-height: 14rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
}

.select__option {
  display: grid;
  grid-template-columns: 1.1rem 1fr;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.4rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}
.select__option:hover { background: rgb(255 255 255 / 0.06); }

/* Where the keyboard is, which is not yet what is chosen. */
.select__option.is-cursor { background: rgb(255 255 255 / 0.1); }

.select__option.is-chosen { color: var(--accent); font-weight: 600; }
.select__tick { color: var(--accent); font-size: 0.8rem; text-align: center; }

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

/*
 * Chessground's own coordinates are 9px and positioned with fixed pixel
 * offsets — `top: -20px` against a strip whose rows are percentages — so
 * they drift out of line with the ranks as the board changes size, which is
 * exactly what a resizable board does. Replaced here with the arrangement
 * chess.com uses: big, bold, tucked into the corner of the square they
 * label, and sized as a fraction of the board so they hold their place and
 * their weight at any size.
 */
.board { container-type: inline-size; }

.cg-wrap coords {
  font-family: inherit;
  font-size: 2.6cqw;
  font-weight: 700;
  opacity: 1;
  /* The board themes are photographs and textures; a whisper of shadow keeps
     the letters legible over wood grain without making them look embossed. */
  text-shadow: 0 1px 1px rgb(0 0 0 / 0.25);
}

/* Ranks: down the left, at the top of each square, like a book's margin. */
.cg-wrap coords.ranks {
  top: 0;
  left: 0;
  width: 12.5%;
  height: 100%;
}

.cg-wrap coords.ranks coord {
  display: flex;
  align-items: flex-start;
  /* Chessground nudges each label by 39% of its own height to fake centring
     against the -20px offset above. Both go together or neither works. */
  transform: none;
  padding: 0.4cqw 0 0 0.6cqw;
}

/* Files: along the bottom, at the trailing corner of each square. */
.cg-wrap coords.files {
  bottom: 0;
  left: 0;
  width: 100%;
  height: 12.5%;
}

.cg-wrap coords.files coord {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 0 0.6cqw 0.3cqw 0;
}

/* Chessground's alternating colours assume 0.8 alpha against a square; at
   this size that reads as grey rather than as a label. */
.orientation-white .ranks :nth-child(odd),
.orientation-white .files :nth-child(even),
.orientation-black .ranks :nth-child(even),
.orientation-black .files :nth-child(odd) {
  color: rgb(60 60 60 / 0.9);
}

.orientation-white .ranks :nth-child(even),
.orientation-white .files :nth-child(odd),
.orientation-black .ranks :nth-child(odd),
.orientation-black .files :nth-child(even) {
  color: rgb(255 255 255 / 0.9);
}

/* --- games --- */

.games {
  list-style: none;
  margin: 0 0 0.6rem;
  padding: 0;
  max-height: 12rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
  /* A long name is cut short, never scrolled sideways: a horizontal bar under
     the list looks broken, and the move count belongs on screen. */
  overflow-x: hidden;
  overscroll-behavior: contain;
  display: grid;
  gap: 2px;
}

/* The row has to be allowed to shrink as well, or its own min-content width
   holds the list open no matter what the button inside it does. */
.games__row { min-width: 0; display: flex; align-items: stretch; gap: 2px; }

/* Catching a game up with chess.com: quiet until wanted, and never wider than
   the name it sits beside. */
.games__sync {
  flex: none;
  width: 2.4rem;
  padding: 0;
  display: grid;
  place-items: center;
  color: var(--text-dim);
  background: none;
  border: none;
  border-radius: 4px;
}

.games__sync::before {
  content: "";
  width: 1.05rem;
  height: 1.05rem;
  --icon: url("../icon/sync.svg");
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}
.games__sync:hover { color: var(--text); background: var(--surface-raised); }

.games__game {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  background: none;
  border: none;
  border-radius: 4px;
  padding: 0.3rem 0.45rem;
  text-align: left;
}
.games__game:hover { background: var(--surface-raised); }

.games__game.is-open {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* Long names lose their tail rather than pushing the count off the row.
   `min-width: 0` is what actually does it: without it a flex item refuses to
   shrink below its text, and the row grows instead of the name ellipsing. */
.games__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.games__moves {
  flex: none;
  font-size: 0.75rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.games__game.is-open .games__moves { color: var(--accent-text); opacity: 0.7; }

/* A pair of buttons rather than a dropdown: two choices, both worth seeing. */
.sides { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; }

.sides__pick {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.85rem;
  color: var(--text-dim);
  background: var(--surface-raised);
  border: 1px solid var(--border);
}
.sides__pick:first-child { border-radius: 4px 0 0 4px; }
.sides__pick:last-child { border-radius: 0 4px 4px 0; }
.sides__pick:hover { color: var(--text); }

.sides__pick.is-chosen {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* --- branches --- */

/* Flowing notation rather than a grid: variations nest, so there are no
   columns to line up, and the text needs to read like a book's. */
/* Reserve the scrollbar's width whether or not it is showing.
   Without this the bar appears when you reach for a move, takes width from
   the content, and the text reflows — so the move you were aiming at slides
   out from under the pointer between pressing and releasing. The click then
   lands on the panel rather than the move, and nothing happens. */
.branches {
  overflow-y: auto;
  scrollbar-gutter: stable;
  max-height: 14rem;
  overscroll-behavior: contain;
  margin-bottom: 0.6rem;
}

.branches__empty { margin: 0; color: var(--text-dim); font-size: 0.85rem; line-height: 1.5; }

.branches__text {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.9;
  word-spacing: 0.1em;
}

.branches__number { color: var(--text-dim); font-size: 0.78rem; margin-right: 0.1em; }

.branches__move {
  background: none;
  border: none;
  border-radius: 4px;
  padding: 0.1rem 0.25rem;
  font-size: 0.85rem;
  /* Sidelines are the quieter reading; the line on the board is the loud one. */
  color: var(--text-dim);
}
.branches__move.is-line { color: var(--text); }
.branches__move:hover { background: var(--surface-raised); color: var(--text); }

.branches__move.is-current {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* A note about a move, printed where a book prints it. Quieter than the
   moves, so the line still reads as a line of chess with prose in it rather
   than prose with chess in it. */
.branches__note {
  background: none;
  border: none;
  border-radius: 4px;
  padding: 0.1rem 0.2rem;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--good);
  opacity: 0.85;
  text-align: left;
}
.branches__note:hover { background: var(--surface-raised); opacity: 1; }

/* Bracketed and indented in colour, so the eye can find where a sideline
   ends without counting parentheses. */
.branches__variation {
  color: var(--text-dim);
  opacity: 0.85;
}

/* --- promotion dialog --- */

/* Sits over the board itself, now that the board has its own frame. */
.promotion {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgb(0 0 0 / 0.6);
  border-radius: var(--radius);
  z-index: 10;
}

.promotion__panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
  display: grid;
  gap: 0.6rem;
  justify-items: center;
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.5);
}
.promotion__title { margin: 0; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); }
.promotion__choices { display: flex; gap: 0.5rem; }

/* Big enough to hit without aiming — this interrupts a drag, so it wants to
   be answered at a glance rather than squinted at. */
.promotion__choice {
  display: grid;
  place-items: center;
  width: 4.5rem;
  height: 4.5rem;
  padding: 0.3rem;
}

/* Chessground sizes and positions pieces only inside `.cg-wrap`, so out here
   the size is ours to set. `contain`, not `cover`: a piece cropped to fill a
   square is a piece with its head cut off. */
.promotion__choice piece {
  display: block;
  width: 100%;
  height: 100%;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.promotion__cancel { font-size: 0.8rem; padding: 0.3rem 0.9rem; }

/* --- asking "are you sure?" --- */

/* Covers the page rather than the board: these questions come from the panel,
   and the board behind should dim with everything else. */
.dialog {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgb(0 0 0 / 0.6);
  z-index: 20;
}

.dialog__panel {
  width: min(26rem, 100%);
  display: grid;
  gap: 0.5rem;
  padding: 1.15rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.5);
}

.dialog__question { margin: 0; font-size: 1rem; font-weight: 600; }
.dialog__detail { margin: 0; font-size: 0.85rem; line-height: 1.5; color: var(--text-dim); }

/* The way out on the left, the thing you asked for on the right. */
.dialog__answers {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.4rem;
}
.dialog__answers button { padding: 0.4rem 0.9rem; }

/* --- the terminal --- */

/*
 * Green phosphor, not a desktop window. The palette is its own — the rest of
 * the board is warm and tan, and this deliberately is not: it should read as
 * a different kind of thing sitting next to the game.
 */
.terminal {
  --term-bg: #040705;
  --term-text: #5ef08a;
  --term-bright: #c2ffd8;
  --term-dim: #2f7048;
  --term-edge: #14361f;

  display: flex;
  flex-direction: column;
  min-width: 0;
  position: sticky;
  top: 1.25rem;
  max-height: calc(100dvh - 6rem);
  overflow: hidden;
  background: var(--term-bg);
  border: 1px solid var(--term-edge);
  border-radius: var(--radius);
  color: var(--term-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
  line-height: 1.55;
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(94 240 138 / 0.06),
              inset 0 0 80px rgb(94 240 138 / 0.04);
}

/* Scanlines, faint enough to be felt rather than seen. */
.terminal::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(to bottom, rgb(0 0 0 / 0.28) 0 1px, transparent 1px 3px);
  opacity: 0.5;
}

.terminal__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  padding: 0.4rem 0.6rem;
  background: rgb(94 240 138 / 0.05);
  border-bottom: 1px solid var(--term-edge);
}

/* Alive, and quietly saying so. */
.terminal__pulse {
  width: 0.45rem;
  height: 0.45rem;
  flex: none;
  border-radius: 50%;
  background: var(--term-text);
  box-shadow: 0 0 6px var(--term-text);
  animation: terminal-pulse 2.4s ease-in-out infinite;
}

@keyframes terminal-pulse { 50% { opacity: 0.25; } }

.terminal__title {
  flex: 1;
  color: var(--term-dim);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
}

.terminal__close {
  flex: none;
  padding: 0 0.35rem;
  background: none;
  border: none;
  color: var(--term-dim);
  font-size: 0.8rem;
  line-height: 1;
}
.terminal__close:hover { color: var(--term-bright); background: none; }

.terminal__output {
  flex: 1;
  min-height: 6rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
  padding: 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  /* The bloom a phosphor screen has, at a strength that stays readable. */
  text-shadow: 0 0 8px rgb(94 240 138 / 0.35);
}

.terminal__line {
  margin: 0;
  display: flex;
  gap: 0.45rem;
  white-space: pre-wrap;
  word-break: break-word;
}

.terminal__line--out { color: var(--term-text); }
.terminal__line--echo { color: var(--term-bright); }
.terminal__line--note { color: var(--term-dim); text-shadow: none; }
.terminal__line--bad { color: #ff6b6b; text-shadow: 0 0 8px rgb(255 107 107 / 0.35); }

.terminal__sigil { flex: none; color: var(--term-text); }

/* What could come next, from the first letter onwards. Suggestions only —
   clicking one writes it into the line, it never runs anything. */
.terminal__hints {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  padding: 0 0.7rem 0.4rem;
  min-height: 0;
}
.terminal__hints:empty { display: none; }

.terminal__hint {
  padding: 0.1rem 0.4rem;
  font: inherit;
  font-size: 0.76rem;
  color: var(--term-dim);
  background: rgb(94 240 138 / 0.07);
  border: 1px solid var(--term-edge);
  border-radius: 3px;
}
.terminal__hint:hover {
  color: var(--term-bright);
  background: rgb(94 240 138 / 0.16);
}

/* Not a suggestion, a count: there are more, keep typing. */
.terminal__hint--more { background: none; border-color: transparent; opacity: 0.7; }

/* Not a suggestion either, but the shape of what to type. */
.terminal__hint--shape {
  background: none;
  border-style: dashed;
  font-style: italic;
  opacity: 0.8;
}

.terminal__prompt {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  flex: none;
  padding: 0.55rem 0.7rem;
  border-top: 1px solid var(--term-edge);
  background: rgb(94 240 138 / 0.04);
}

.terminal__input {
  flex: 1;
  min-width: 0;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  color: var(--term-bright);
  font: inherit;
  caret-color: var(--term-text);
  text-shadow: 0 0 8px rgb(94 240 138 / 0.35);
}
.terminal__input:focus-visible { outline: none; box-shadow: none; }

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

/* --- narrow screens --- */

@media (max-width: 56rem) {
  .layout { grid-template-columns: minmax(0, 1fr); padding: 0.75rem; }
  /* Nothing to balance in a single column; they would be two empty rows. */
  .layout::before,
  .layout::after { display: none; }
  .panel { margin-left: 0; }
  .terminal { margin-right: 0; }
  /* Stacked, so the page scrolls: bound by width only, not by height. */
  .board { width: min(var(--board-size, 100vmax), calc(100vw - 1.5rem)); }
  .panel { position: static; max-height: none; }

  /* Stacked with everything else, and above the board it belongs to. */
  body[data-terminal="open"] .layout { grid-template-columns: minmax(0, 1fr); }
  .terminal { position: static; max-height: 60dvh; order: -1; }
}

/* --- the engine ------------------------------------------------------------

   Locked until somebody types `engine on`. Locked rather than hidden: a
   feature you cannot find is worse than one you cannot use yet, and the lock
   is where the two words that unlock it are written down. */

.engine[data-locked="true"] .panel__heading { color: var(--text-dim); }
.engine[data-locked="true"] .panel__heading::after {
  content: "locked";
  margin-left: 0.5rem;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.6;
}

.engine__note {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--text-dim);
}
.engine__note--bad { color: var(--bad); }

/* The bar: White from the left, the way every evaluation graph ever drawn has
   done it, so it reads the same whichever way the board is turned. */
.evalbar {
  position: relative;
  height: 0.65rem;
  border-radius: 3px;
  overflow: hidden;
  background: #17150f;
  box-shadow: inset 0 0 0 1px var(--border);
}
.evalbar__white {
  display: block;
  height: 100%;
  width: 50%;
  background: linear-gradient(90deg, #f4f1ea, #ddd7c9);
  /* Long enough to be followed by eye, and eased at both ends so a deep search
     changing its mind reads as the bar moving rather than as a flicker. */
  transition: width 400ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .evalbar__white { transition: none; }
}
/* Level, so the eye has something to judge the fill against. */
.evalbar__mark {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: rgb(181 136 99 / 0.75);
}

/* One line of thinking: the score, then the moves it expects. */
.engine__lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.55rem;
}
.engine__line {
  display: grid;
  grid-template-columns: 3.6rem minmax(0, 1fr);
  gap: 0.6rem;
  align-items: start;
  font-size: 0.82rem;
  line-height: 1.6;
}

/* The evaluation, painted as the side it favours. A pale chip on this dark
   panel reads as White ahead without anybody having to work out what the sign
   means, and a dark chip with a hairline says Black. */
.engine__score {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  text-align: center;
  padding: 0.1rem 0.25rem;
  border-radius: 3px;
  white-space: nowrap;
}
.engine__score[data-side="white"] { background: #efece4; color: #16150f; }
.engine__score[data-side="black"] { background: #100f0c; color: #efece4; box-shadow: inset 0 0 0 1px #3a352a; }
.engine__score[data-side="even"] { background: var(--surface-raised); color: var(--text); }

/* Three rows at most, ending in an ellipsis rather than mid-move: a variation
   is as long as it likes, and the useful part is the front of it. */
.engine__pv {
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.engine__move { white-space: nowrap; }
.engine__movenumber {
  font-style: normal;
  color: var(--text-dim);
  margin-right: 0.15rem;
  font-variant-numeric: tabular-nums;
}
.engine__pv piece {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.24em;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.engine__track {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-raised);
  overflow: hidden;
}
.engine__bar { display: block; height: 100%; background: var(--accent); transition: width 120ms linear; }

.engine__sides { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.engine__side {
  display: grid;
  gap: 0.35rem;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius);
  background: var(--surface-raised);
}
/* Whose accuracy it is, said in the colour as well as in the word. */
.engine__side[data-side="white"] { box-shadow: inset 3px 0 0 #efece4; }
.engine__side[data-side="black"] { box-shadow: inset 3px 0 0 #100f0c; }
.engine__who { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-dim); }
.engine__accuracy { font-size: 1.4rem; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.1; }
.engine__counts { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.engine__count { display: inline-flex; align-items: baseline; gap: 0.15rem; font-size: 0.72rem; }
.engine__count b { color: var(--text); }

/* Kept apart from the live lines above it, because one changes several times
   a second and the other not at all. */
.engine__report:not(:empty) {
  display: grid;
  gap: 0.6rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border);
}
.engine__reportname { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-dim); }

/* What a move was worth, in the move list and in the summary.

   Best and excellent share a star and are told apart by colour alone — blue
   for the engine's own move, green for one just as good by another route.
   Everything else carries its own symbol, and every mark carries its name as
   a tooltip, which is what anybody who cannot separate the two colours has to
   fall back on. */
.verdict {
  margin-left: 0.25rem;
  font-size: 0.75em;
  font-weight: 700;
  vertical-align: 0.1em;
}
.verdict--best { color: #5da9e9; }
.verdict--excellent { color: #6cc48c; }
.verdict--good { color: var(--good); }
.verdict--inaccuracy { color: #e4b363; }
.verdict--mistake { color: #e08b4a; }
.verdict--blunder { color: var(--bad); }

/* Roomier than the other panels: this one holds numbers that are read rather
   than controls that are clicked, and they need air to be legible. */
.panel__body--engine { gap: 0.9rem; }
.engine__live { display: grid; gap: 0.6rem; }

/* --- the engine's settings box --- */

.config { width: min(30rem, 100%); gap: 0.9rem; }
.config__group { display: grid; gap: 0.7rem; }
.config__heading {
  margin: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-dim);
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--border);
}
.config__field { display: grid; gap: 0.35rem; }
.config__label { font-size: 0.85rem; }
.config__note { font-size: 0.74rem; line-height: 1.5; color: var(--text-dim); }
.config__picks { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.config__pick {
  flex: 1 1 auto;
  min-width: 3rem;
  padding: 0.35rem 0.6rem;
  font-size: 0.85rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.config__pick--on {
  color: var(--accent-text);
  background: var(--accent);
  border-color: var(--accent);
}

/* Typed rather than picked, for a depth that is not one of the two offered. */
.config__number {
  width: 100%;
  padding: 0.4rem 0.6rem;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
