/* ==========================================================================
   board.css — the chessboard, squares, pieces, highlights and themes
   ========================================================================== */

/* --------------------------------------------------------------------------
   Themes.
   Every board colour comes from these custom properties, so adding a theme
   means adding one block here — no other selector hardcodes a board colour.
   -------------------------------------------------------------------------- */

.board[data-theme="classic"],
.theme-swatch__preview[data-theme="classic"] {
  --board-light: #ecd8b6;
  --board-dark: #b07d4f;
  --board-edge: #6d4c30;
  --coord-on-light: #8a6238;
  --coord-on-dark: #ecd8b6;
}

.board[data-theme="midnight"],
.theme-swatch__preview[data-theme="midnight"] {
  --board-light: #9fb0cc;
  --board-dark: #4a5a78;
  --board-edge: #2b3550;
  --coord-on-light: #3d4a63;
  --coord-on-dark: #cdd8ea;
}

.board[data-theme="wood"],
.theme-swatch__preview[data-theme="wood"] {
  --board-light: #e8c99b;
  --board-dark: #9a6a3d;
  --board-edge: #5f4126;
  --coord-on-light: #7b5228;
  --coord-on-dark: #f0dcc0;
}

/* Highlight colours are shared across themes. */
.board {
  --hl-selected: rgba(232, 180, 76, 0.55);
  /* Strong enough to read at a glance, soft enough that a dark piece standing
     on it is still clearly dark. */
  --hl-last-move: rgba(232, 180, 76, 0.34);
  /* The ring on the square the piece left. Fully opaque and a shade deeper
     than the tint, because it is painted UNDER it: the last-move wash is 34%
     gold over the top, and a ring in the same gold at the same strength
     disappears into it. */
  --hl-last-from: rgb(214, 154, 40);
  --hl-legal: rgba(20, 22, 28, 0.32);
  --hl-legal-on-dark: rgba(245, 240, 230, 0.34);
  --hl-check: #e5594d;
}

/* Theme preview swatches in Settings */
.theme-swatch__preview {
  background: linear-gradient(
    135deg,
    var(--board-light) 0 25%,
    var(--board-dark) 25% 50%,
    var(--board-light) 50% 75%,
    var(--board-dark) 75% 100%
  );
  background-size: 16px 16px;
}

/* --------------------------------------------------------------------------
   Board container
   -------------------------------------------------------------------------- */

.board-area {
  display: flex;
  justify-content: center;
  /* The capture trays are positioned against this box, so they span exactly
     the board's height whatever the board is sized to. */
  position: relative;
}

/*
   At the top zoom the board is drawn across 94% of the canvas, so there is no
   longer any empty margin for the trays to sit in. Here alone they cost real
   width, because the alternative is a pile of captured pieces sitting on top
   of the a- and h-files.
*/
.board-area[data-zoom="max"] {
  padding-left: 20px;
  padding-right: 20px;
}

/*
   The default level leaves 32px of margin either side, half as much again as
   the middle one. A piece portrait is a picture rather than a letterform, so
   it wants every pixel it can have — and here there are pixels going spare.
*/
.board-area[data-zoom="fit"] .capture-tray {
  width: 30px;
}

.board-frame {
  width: min(94vw, 560px);
  max-width: 100%;
  /* A flex item beside the trays; without this its content sets a floor and
     the row would rather overflow than let the board give up any width. */
  min-width: 0;
  padding: 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: linear-gradient(160deg, var(--panel-soft), var(--panel));
  box-shadow: var(--shadow);
}

.board {
  display: grid;
  /*
     minmax(0, …) is essential, not cosmetic. A plain `1fr` track carries an
     automatic min-content floor, so the piece glyphs would force each square
     to their own text size and overflow the board — visibly squashing the
     bottom ranks on short/wide viewports. The zero floor lets the tracks
     divide the board evenly no matter how large the glyphs are.
  */
  grid-template-columns: repeat(8, minmax(0, 1fr));
  grid-template-rows: repeat(8, minmax(0, 1fr));
  width: 100%;
  /* The board is always square, and never stretches in either direction. */
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  /* Fast taps without blocking page scroll on touch devices. */
  touch-action: manipulation;
  user-select: none;
  /*
     Establish a size container so pieces and coordinates can scale from the
     BOARD's size rather than the viewport's. Sizing from vw is wrong whenever
     the board is not the width-limiting element — a landscape phone or a
     desktop side-by-side layout — and produced pieces far too large or small.
  */
  container-type: size;
  container-name: board;
}

/* --------------------------------------------------------------------------
   Squares
   -------------------------------------------------------------------------- */

.square {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

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

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

/* --- Coordinates: subtle, inside the edge squares --- */

.square__coord {
  position: absolute;
  /* Fallback; the container-query value below is the real one. */
  font-size: clamp(7px, 1.6vw, 11px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0.75;
}

@supports (container-type: size) {
  .square__coord {
    font-size: clamp(7px, 2.1cqmin, 12px);
  }
}

.square__coord--rank {
  top: 3px;
  left: 3px;
}

.square__coord--file {
  right: 3px;
  bottom: 3px;
}

.square--light .square__coord {
  color: var(--coord-on-light);
}

.square--dark .square__coord {
  color: var(--coord-on-dark);
}

/* --------------------------------------------------------------------------
   Pieces
   -------------------------------------------------------------------------- */

.piece {
  position: relative;
  z-index: 2;
  display: block;
  /*
     Name symbol fonts explicitly, and never an emoji font. This is the second
     half of the emoji fix (the first is the U+FE0E selector in board.js):
     if the browser resolves these glyphs through Segoe UI Emoji or Noto Color
     Emoji, they paint in the font's own colours and ignore `color` entirely,
     so white pieces come out solid black. Listing symbol fonts first keeps
     the glyphs monochrome and colourable on every platform.
  */
  font-family:
    "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2",
    "DejaVu Sans", "FreeSerif", sans-serif;
  /* Belt and braces where supported: refuse emoji presentation outright. */
  font-variant-emoji: text;
  /* Fallback for browsers without container queries. */
  font-size: clamp(20px, 7.6vw, 46px);
  line-height: 1;
  pointer-events: none;
  /*
     `backface-visibility` is the cheap half of a compositor hint: it keeps the
     glyph off the fractional-pixel path while it slides, without promoting a
     layer. The expensive half — will-change — is deliberately NOT here: see
     .piece--moving.
  */
  backface-visibility: hidden;
}

/*
   Only the piece actually in flight gets a compositor layer and a raised
   stacking order.

   will-change used to sit on `.piece` itself, which promoted all thirty-two
   pieces to their own layers permanently. That is not a stronger version of
   this — it is worse: a phone GPU pays for every one of those layers all the
   time, and the memory it costs is taken from the budget the one animation
   that is actually running needs. Granting the hint for the length of the
   move and taking it back afterwards is what the property is for.

   The raised z-index matters too. Every piece sits at z-index 2, so a piece
   crossing the board passed UNDER the ones it travelled over — a rook sliding
   down a file kept disappearing behind pawns. It should pass over them.
*/
.piece--moving {
  z-index: 5;
  will-change: transform;
}

/*
   A piece being captured, lifted out of the render so it can fade instead of
   blinking out. It sits above the last-move wash but below the arriving piece
   (z-index 2), so the capturer lands cleanly on top of it.

   Absolutely positioned, because the square's grid centring only applies to
   the real piece; this re-centres the glyph inside the full square instead.
*/
.piece--captured {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  pointer-events: none;
}

/*
   The fading ghost carries .piece--moving too, for the same compositor hint.
   It must NOT inherit that rule's raised z-index, or the piece being taken
   would fade out in front of the piece taking it. Pinned explicitly, rather
   than left to whichever of the two rules happens to be written last.
*/
.piece--captured.piece--moving {
  z-index: 1;
}

/*
   One square is 12.5% of the board, so 9cqmin ≈ 72% of a square — the
   proportion a chess piece traditionally occupies. This holds at every board
   size, on every layout.
*/
@supports (container-type: size) {
  .piece {
    font-size: 9cqmin;
  }
}

/*
   Both colours use the same solid glyph and are separated by fill + outline.
   This keeps the two sides identical in shape on every platform, and keeps
   pieces crisp at any pixel density since they are font vectors.

   White needs a genuinely heavy outline, not a hairline. Its fill sits only a
   few percent brighter than the light squares (#ecd8b6 classic, #e8c99a wood),
   so a 1px text-shadow ring left the piece almost dissolving into the board.
   Black never had the problem, because it contrasts with both square colours.

   Chained drop-shadows do the work instead of more text-shadow offsets: each
   applies to the result of the previous, so four 1px passes build an even,
   fully opaque ring — where stacked text-shadows stay translucent at the
   diagonals and let the square show through.
*/
.piece[data-color="w"] {
  color: #fbfbf8;
  text-shadow: none;
  filter:
    drop-shadow(1px 0 0 #14161d)
    drop-shadow(-1px 0 0 #14161d)
    drop-shadow(0 1px 0 #14161d)
    drop-shadow(0 -1px 0 #14161d)
    drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4));
}

/*
   A slightly dimmed outline rather than near-white: at phone sizes the ring is
   proportionally thick, and a pure white outline made dark pieces read as
   light ones when they stood on a highlighted square.
*/
.piece[data-color="b"] {
  color: #14161d;
  text-shadow:
    0 0 1px #ccd3e2,
    0 1px 0 #ccd3e2,
    1px 0 0 #ccd3e2,
    0 -1px 0 #ccd3e2,
    -1px 0 0 #ccd3e2,
    1px 1px 0 #ccd3e2,
    -1px 1px 0 #ccd3e2,
    1px -1px 0 #ccd3e2,
    -1px -1px 0 #ccd3e2,
    0 2px 3px rgba(0, 0, 0, 0.3);
}

/* Floating piece that follows the cursor while dragging (mouse/pen only). */
.piece--ghost {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 0.92;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
}

.square.is-dragging .piece {
  opacity: 0.25;
}

/* --------------------------------------------------------------------------
   Highlights
   -------------------------------------------------------------------------- */

/* Last move — both origin and destination stay lit until the next move. */
.square.is-last-move::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hl-last-move);
  pointer-events: none;
}

/*
   The square the piece came FROM: a ring, on top of the tint both ends of the
   move already get.

   Tinting both ends says a move happened here; it does not say which way. The
   ring is what makes the last move readable as a direction — empty circle
   behind, piece ahead — which is the thing you actually want to see when you
   look up and someone has moved.

   Drawn as a background-image rather than a pseudo-element on purpose: this
   square already uses ::before for the last-move tint, and ::after can be
   taken at the same time by is-legal or is-capture — the from square of the
   last move is perfectly able to be a legal destination for the next one.
   Painting into the background needs no third layer and can never collide.
*/
.square.is-last-from {
  background-image: radial-gradient(
    circle at center,
    transparent 0 27%,
    var(--hl-last-from) 27% 34%,
    transparent 34%
  );
}

/* Selected square */
.square.is-selected::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hl-selected);
  box-shadow: inset 0 0 0 3px rgba(232, 180, 76, 0.9);
  pointer-events: none;
}

/* Legal destination — a small centred dot */
.square.is-legal::after {
  content: "";
  position: absolute;
  width: 30%;
  height: 30%;
  border-radius: 50%;
  background: var(--hl-legal);
  pointer-events: none;
  z-index: 3;
}

.square--dark.is-legal::after {
  background: var(--hl-legal-on-dark);
}

/* Capture destination — a ring around the piece */
.square.is-capture::after {
  content: "";
  position: absolute;
  inset: 6%;
  border: clamp(3px, 1vw, 5px) solid var(--hl-legal);
  border-radius: 50%;
  background: transparent;
  pointer-events: none;
  z-index: 1;
}

.square--dark.is-capture::after {
  border-color: var(--hl-legal-on-dark);
}

/* King in check — unmistakably dangerous, not just a text label */
.square.is-check::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at center,
    rgba(229, 89, 77, 0.95) 0%,
    rgba(229, 89, 77, 0.7) 42%,
    rgba(229, 89, 77, 0) 76%
  );
  pointer-events: none;
  animation: check-pulse 1.2s ease-in-out infinite;
}

@keyframes check-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

/* Hover affordance on pointer devices only */
@media (hover: hover) and (pointer: fine) {
  .square:hover:not(.is-selected) {
    filter: brightness(1.08);
  }
}

.square:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
  z-index: 4;
}
