/* The board's own look: squares, pieces, the marks board.js draws on them, and the promotion
   picker. Everything here is addressed by a class board.js writes, and nothing here knows what page
   the board is on, the trainer, the analysis board and the tour all get the same board because they
   all load this file.

   It used to live in the middle of style.css, which was fine while the only two pages with a board
   both loaded that stylesheet. The tour is on the landing page, and landing.css is a separate poster
   palette that deliberately shares nothing with the trainer's, so the choice was to load a whole
   tool UI's stylesheet for its board rules, or to put the board's rules where the board is. This is
   the second.

   The palette is read through `var(..., fallback)` throughout, so a page that has not defined the
   trainer's variables still gets a board that looks right. `--board-size` is the one thing a caller
   must set: it is the board's edge, and every page has a different budget for it. */

/* Nothing in here is text to be selected, and on a touch screen the attempt to select it is what a
   slightly slow drag looks like: press a piece, hesitate, and the board comes up blue with the
   file and rank letters highlighted, or iOS puts a Copy/Look Up bubble over the position.
 *
 * All four properties are needed and none is redundant. `user-select` is the standard and is what
 * desktop Chrome and Firefox read; `-webkit-user-select` is what Safari, including every browser on
 * iOS, actually reads. `-webkit-touch-callout` is a separate mechanism again, the long-press menu,
 * which fires whether or not selection is allowed. And it goes on `.board-wrap` rather than on
 * `.board`, because the coordinates, the marks and the promotion picker are children of the wrapper
 * and were the parts being highlighted. */
.board-wrap {
  position: relative;
  width: var(--board-size);
  height: var(--board-size);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

.board {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  border-radius: 4px;
  overflow: hidden;
  box-shadow: var(--shadow-board, 0 8px 24px rgba(0, 0, 0, 0.5));
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

.square {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Set by `applyBoardTheme` in prefs.js, on `<html>`. The fallbacks are the brown board, so a page
   that somehow runs no JavaScript still gets a chessboard rather than two transparent grids, and
   so the colours here and the defaults there cannot silently disagree.

   Deliberately not theme tokens: a chessboard is not part of the app's palette and must not invert
   when somebody switches to light mode. */
.square.light { background: var(--sq-light, #f0d9b5); }
.square.dark { background: var(--sq-dark, #b58863); }

.square.selected::before,
.square.last-move::before {
  content: "";
  position: absolute;
  inset: 0;
}

.square.last-move::before { background: rgba(155, 199, 0, 0.41); }
.square.selected::before { background: rgba(20, 85, 30, 0.5); }
.square.drag-over::before { background: rgba(20, 85, 30, 0.3); }

.user-highlight {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* The hint: the piece that should move, lit and lifted off the square.

   This was a solid yellow block drawn on the square, borrowed from the mark a right-click makes, on
   the reasoning that anyone who has used the board already knows what that mark means. True, and
   still the wrong object: it says "this square", and a hint is about a *piece*. It also sat under
   the piece, so on a light square the piece was the least visible thing inside its own hint.

   Two properties do the work, and they are chosen so that neither depends on the board's colours,
   because there are six themes and the hint has to read on all of them:

     * the **lift**, which is motion, and motion is the same on `#f0d9b5` as on `#b58863`
     * a **dark edge and a warm bloom** together, so the edge carries it on the pale themes and the
       bloom carries it on the dark ones

   Defined here rather than in a page's own stylesheet because every board on the site gets it: the
   trainer, the analysis board, the rush, and the run on the front page. */
.piece.hint { animation: piece-hint 1.9s ease-in-out infinite; }

@keyframes piece-hint {
  0%, 100% {
    transform: translateZ(0) translateY(0) scale(1);
    filter:
      drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45))
      drop-shadow(0 0 4px rgba(255, 244, 214, 0.35));
  }
  50% {
    transform: translateZ(0) translateY(-11%) scale(1.06);
    filter:
      drop-shadow(0 0 3px rgba(58, 36, 4, 0.6))
      drop-shadow(0 10px 13px rgba(0, 0, 0, 0.55))
      drop-shadow(0 0 16px rgba(255, 246, 220, 1))
      drop-shadow(0 0 34px rgba(255, 206, 92, 0.75));
  }
}

/* `translateZ(0)` is repeated in both keyframes because `.piece` above sets it as a compositing
   hint, and an animation that sets `transform` replaces the property rather than adding to it. */
@media (prefers-reduced-motion: reduce) {
  .piece.hint {
    animation: none;
    filter: drop-shadow(0 0 3px rgba(58, 36, 4, 0.75)) drop-shadow(0 0 12px rgba(255, 246, 220, 1));
  }
}

.arrows-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 40;
}

.square.check::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(255, 0, 0, 0.9) 0%, rgba(255, 0, 0, 0.55) 25%, rgba(255, 0, 0, 0) 89%);
}

.square .move-dot {
  position: absolute;
  width: 32%;
  height: 32%;
  border-radius: 50%;
  background: rgba(20, 20, 20, 0.4);
  pointer-events: none;
  z-index: 2;
}

.square .capture-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: inset 0 0 0 5px rgba(20, 20, 20, 0.4);
  pointer-events: none;
  z-index: 2;
}

/* Marks live in their own layer above the board rather than inside a square: the board clips its
   children to round its corners, which would slice a badge on an edge square in half. Position and
   size come from render(), in percentages of the board. */
.glyph-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 45;
}

/* The ?!/?/?? mark, overlapping the top-right corner of the square that was moved to. Sized small
   with a light ring so it stays legible on both square colours without dominating the piece. */
.move-glyph {
  position: absolute;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* Duotone: a darker ring of the same hue around a lighter face, so each mark reads as one colour
     at a glance while still standing off the square underneath. */
  border: 1.5px solid var(--glyph-ring);
  background: var(--glyph-face);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: min(1.9vmin, 15px);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.5px;
  text-indent: -0.5px; /* re-centre once the negative tracking is applied */
  color: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
  /* Small text on a coloured disc aliases badly without these. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
  pointer-events: none;
  z-index: 30;
}

/* Only the render that first shows the mark gets the pop, see setMoveGlyph(). */
.move-glyph.pop {
  animation: glyph-pop 150ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes glyph-pop {
  from { transform: scale(0.4); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* Mistake marks: yellow ?! , orange ? , red ?? */
/* White text throughout, so the yellow face is darkened enough to carry it. */
.move-glyph.inaccuracy { --glyph-face: #e3b32a; --glyph-ring: #a8800c; }
.move-glyph.mistake { --glyph-face: #f0912c; --glyph-ring: #b96410; }
.move-glyph.blunder { --glyph-face: #de4c47; --glyph-ring: #a32723; }

/* The miss: a mate let slip out of a position that is still winning. A lighter red than the
   blunder, and the same distance from it as the light green thumb is from the green star, because
   it stands in the same relation: the right family of answer, one grade down. */
.move-glyph.miss { --glyph-face: #ea8d88; --glyph-ring: #bb5b56; }

/* Ratings once there's no single best move left: green ★ , light green 👍 , dull green ✓ */
.move-glyph.best { --glyph-face: #3fae5f; --glyph-ring: #1e7a3b; }
.move-glyph.good { --glyph-face: #8ed07c; --glyph-ring: #5da54c; }

/* Size comes from glyphShape(), per shape: a star needs more room than a tick. */
.move-glyph svg {
  display: block;
  fill: #fff;
  shape-rendering: geometricPrecision;
}
.move-glyph.ok { --glyph-face: #6f9a72; --glyph-ring: #4b7150; }

.coord {
  position: absolute;
  font-size: 11px;
  font-weight: 600;
  pointer-events: none;
  z-index: 3;
}

.coord.file { bottom: 2px; right: 4px; }
.coord.rank { top: 2px; left: 4px; }

.square.light .coord { color: var(--sq-dark, #b58863); }
.square.dark .coord { color: var(--sq-light, #f0d9b5); }

.piece {
  position: absolute;
  inset: 3%;
  z-index: 5;
  cursor: grab;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  transform: translateZ(0);
}

.piece.dragging {
  position: fixed;
  z-index: 100;
  pointer-events: none;
  cursor: grabbing;
  filter: drop-shadow(0 6px 10px rgba(0,0,0,.5));
}

.square.drag-hidden > .piece { visibility: hidden; }

/* --- Promotion picker ------------------------------------------------------------------------ */

.promo-picker {
  position: absolute;
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: var(--surface, #171341);
  border-radius: 6px;
  box-shadow: var(--shadow-pop, 0 8px 24px rgba(0, 0, 0, 0.6));
  overflow: hidden;
}

.promo-picker.hidden { display: none; }

/* One square, because that is what it is sitting on.
 *
 * This was `min(9vmin, 68px)`, which is a size taken from the *viewport* while the picker's position
 * is taken from the *board*: `openPromotionPicker` places it at `col * cellSize`. The two only agree
 * at one board size and disagree everywhere else, and on this page they disagree badly, `--board-size`
 * on `.page` runs up to 900px, so a square is 112px and the piece being offered was drawn at 68,
 * about three fifths of the pawn that was being promoted, in a column narrower than the file it
 * belonged to.
 *
 * `--board-size` is the caller's one required variable and the picker is inside `.board-wrap`, so it
 * inherits it and the option now tracks the square through every breakpoint and both boards for
 * free. The fallback matches board.css's convention of never depending on a variable being set.
 *
 * The cap it replaces was doing one useful thing, keeping a usable tap target on a small screen,
 * and giving that up is deliberate rather than overlooked. On the narrowest layout an option is now
 * about 36px on the analysis board and about 45px on the trainer, which is exactly a square, and a
 * square is already the target for every other thing you do to this board: you pick a piece up by
 * tapping one. An option that is comfortably tappable but wider than the file it belongs to is the
 * bug being fixed, not a compromise, so the two cannot both be had by making this bigger. */
.promo-option {
  width: calc(var(--board-size, 480px) / 8);
  height: calc(var(--board-size, 480px) / 8);
  cursor: pointer;
  background-color: var(--sq-light, #f0d9b5);
  background-repeat: no-repeat;
  background-position: center;
  /* 94%, matching `.piece`'s `inset: 3%`, so the queen you pick is the size of the queen that
     lands on the board. At 80% it was a second, smaller piece next to the one it replaces. */
  background-size: 94%;
}

.promo-option:hover { background: var(--sq-dark, #b58863); }
