/* Lightsquare. Every rule here follows RULES.md: pieces move by transform
   only, places are fractions of the board's width, the squares are one
   static shape, nothing animates at rest.

   Looks are CSS variables. Set a public one (--ls-light, --ls-wk, ...) on
   the board or on anything above it, up to the page's root, and every board
   below follows. The private ones (--_light, ...) hold "the public value or
   the default" and are what the rules below read. */

.ls-board {
  --_light: var(--ls-light, #ecdab9);
  --_dark: var(--ls-dark, #c5a076);
  --_ms: var(--ls-move-ms, 180ms);
  --_ease: var(--ls-ease, cubic-bezier(0.2, 0.7, 0.25, 1));
  --_last: var(--ls-last, rgba(255, 235, 0, 0.5));
  --_select: var(--ls-select, rgba(245, 158, 11, 0.2));
  --_select-edge: var(--ls-select-edge, rgba(245, 158, 11, 0.9));
  --_check: var(--ls-check, rgba(220, 38, 38, 0.78));
  --_target: var(--ls-target, rgba(0, 0, 0, 0.2));
  --_hover: var(--ls-hover, var(--_target));
  --_premove: var(--ls-premove, rgba(90, 90, 90, 0.45));
  position: relative;
  /* The board is as wide as its container, less what is left over when that
     width is shared out in whole pixels among eight squares (7 pixels at
     most). With a square a whole number of pixels wide, the squares, the
     marks and the pieces meet on the same pixel; at other widths a thin
     line of the bare square showed beside a mark. The max-width holds when
     an app's own rule gives the element all of the width. Browsers without
     round() keep the full width. */
  width: 100%;
  width: round(down, 100%, 8px);
  max-width: round(down, 100%, 8px);
  aspect-ratio: 1;
  /* The light squares, over an optional picture. The dark squares are one
     shape on top (.ls-squares). */
  background: var(--_light) var(--ls-board-image, none) center / 100% 100% no-repeat;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  /* Children are placed and sized by the board's width (cqw). An app's
     stylesheet can ask for the board's size by name:
     @container lightsquare (max-width: 460px) { ... } */
  container: lightsquare / inline-size;
  contain: layout style inline-size;
  isolation: isolate;
}

/* Only a board the user can move pieces on takes the touch for itself;
   a board to look at lets the page scroll. */
.ls-board.ls-interactive {
  touch-action: none;
}

.ls-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* No shape-rendering: crispEdges here. It moved a square's edge to the next
   whole pixel where the marks and pieces, placed by a transform, stayed
   put: the line beside the mark again, wherever a square is not a whole
   number of pixels (a zoomed page, many Android phones). */
/* The dark squares are a layer of their own, drawn once and never again.
   Without it, Safari (26.1, macOS) redrew them with every change of a mark
   or shape above, and missed a strip a few pixels wide along the right and
   bottom edge of the area it redrew: thin light lines through the dark
   squares beside a circle or arrow, until the next move (seen on
   kibitzo.com, 2026-11-13). Measured in the founder's Safari with real
   right-clicks drawing a circle on h5: a gap of 2,646 pixels after every
   circle as it was, none in 16 runs with this layer. */
.ls-squares {
  width: 100%;
  height: 100%;
  fill: var(--_dark);
  will-change: transform;
}

/* Bottom to top. A dragged piece is lifted above everything with its layer. */
.ls-marks { z-index: 1; }
.ls-pieces { z-index: 2; }
.ls-coords { z-index: 3; }
.ls-shapes { z-index: 4; }
.ls-over { z-index: 5; }
.ls-overlay { z-index: 6; }
.ls-pieces.ls-lift { z-index: 7; }
.ls-promotion { z-index: 8; }

/* One square in size, top-left of the board; the script's transform puts
   each on its square. */
.ls-piece,
.ls-mark,
.ls-coord {
  position: absolute;
  left: 0;
  top: 0;
  width: 12.5%;
  height: 12.5%;
}

/* A piece set is twelve variables, --ls-wk to --ls-bp, each a CSS image.
   A piece-set stylesheet sets them on a class (.ls-set-<name>); put that
   class on the board or anywhere above it. */
.ls-piece {
  background: center / var(--ls-piece-size, 100%) var(--ls-piece-size, 100%) no-repeat;
}

.ls-wk { background-image: var(--ls-wk); }
.ls-wq { background-image: var(--ls-wq); }
.ls-wr { background-image: var(--ls-wr); }
.ls-wb { background-image: var(--ls-wb); }
.ls-wn { background-image: var(--ls-wn); }
.ls-wp { background-image: var(--ls-wp); }
.ls-bk { background-image: var(--ls-bk); }
.ls-bq { background-image: var(--ls-bq); }
.ls-br { background-image: var(--ls-br); }
.ls-bb { background-image: var(--ls-bb); }
.ls-bn { background-image: var(--ls-bn); }
.ls-bp { background-image: var(--ls-bp); }

/* A move. The class and the new transform arrive in the same write, so the
   browser starts the slide by itself. The class is dropped when the slide
   ends: a piece at rest has no transition. */
.ls-piece.ls-slide {
  transition: transform var(--_ms) var(--_ease);
}

/* The pieces of the latest move pass over the others. */
.ls-piece.ls-top {
  z-index: 1;
}

/* A captured piece. Removed from the page when the fade ends, or by the
   next change at the latest. */
.ls-piece.ls-gone {
  opacity: 0;
  transition: opacity var(--_ms) linear;
}

/* Under the pointer. The only time a piece asks for its own GPU layer. */
.ls-piece.ls-dragging {
  z-index: 2;
  will-change: transform;
}

.ls-mark-last {
  background: var(--_last);
}

.ls-mark-premove {
  background: var(--_premove);
}

/* The piece in hand: a soft tint inside a clear frame. */
.ls-mark-select {
  background: var(--_select);
  box-shadow: inset 0 0 0 max(2px, 0.5cqw) var(--_select-edge);
}

.ls-mark-check {
  background: radial-gradient(circle, var(--_check) 0 30%, transparent 70%);
}

/* Where the piece in hand may go: a dot on an empty square. Its width is
   --ls-dot-size, a share of the square (default 0.3). */
.ls-mark-dot {
  background: radial-gradient(circle closest-side, var(--_target) 0 calc(var(--ls-dot-size, 0.3) * 100% - 2%), transparent calc(var(--ls-dot-size, 0.3) * 100%));
}

/* A piece it may take: the four corners of its square, so that nothing is
   drawn over the piece itself. */
.ls-mark-take {
  background:
    linear-gradient(135deg, var(--_target) 0 11%, transparent 11.6%),
    linear-gradient(225deg, var(--_target) 0 11%, transparent 11.6%),
    linear-gradient(315deg, var(--_target) 0 11%, transparent 11.6%),
    linear-gradient(45deg, var(--_target) 0 11%, transparent 11.6%);
}

/* The square the piece would go to now: all of it in the shade of the
   dots. Under a dragged piece the script says which square that is
   (ls-mark-hover). Under a mouse that only points, the browser says it:
   the two marks above take the pointer for that, and no script runs. */
.ls-mark-hover {
  background: var(--_hover);
}

@media (hover: hover) and (pointer: fine) {
  .ls-mark-dot,
  .ls-mark-take {
    pointer-events: auto;
    cursor: var(--ls-piece-cursor, pointer);
  }
  .ls-mark-dot:hover,
  .ls-mark-take:hover {
    background: var(--_hover);
  }

  /* A hand over a piece that can be picked up, and a closed one while it
     is carried. Only those pieces take the pointer: a piece that cannot be
     moved lets it through to the mark on its square. */
  .ls-white-moves .ls-piece[class*=" ls-w"],
  .ls-black-moves .ls-piece[class*=" ls-b"] {
    pointer-events: auto;
    cursor: var(--ls-piece-cursor, pointer);
  }
  .ls-board:has(.ls-dragging) {
    cursor: grabbing;
  }
}

/* A finger covers the square it is on: show a halo around it instead. */
@media (pointer: coarse) {
  .ls-mark-hover {
    background: none;
  }
  .ls-mark-hover::after {
    content: "";
    position: absolute;
    inset: -55%;
    border-radius: 50%;
    background: var(--_hover);
  }
}

/* Rank numbers in the top-left of the left column, file letters in the
   bottom-right of the bottom row: small, quiet, there when you look for
   them. They sit over the pieces, so a piece never hides its square's
   name, and they grow with the board: about 10 px on a phone, 12 px at
   most. Each takes the colour of the other squares, a touch deeper. */
.ls-coord {
  box-sizing: border-box;
  display: flex;
  padding: 0.6cqw 0.9cqw;
  font-family: var(--ls-coord-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
  font-size: var(--ls-coord-size, clamp(7px, calc(2.4cqw + 1px), 12px));
  font-weight: var(--ls-coord-weight, 600);
  line-height: 1;
  letter-spacing: 0.02em;
}

.ls-coord-file {
  align-items: flex-end;
  justify-content: flex-end;
}

.ls-coord-light {
  color: var(--_dark);
  color: var(--ls-coord-on-light, color-mix(in srgb, var(--_dark) 93%, #000));
}

.ls-coord-dark {
  color: var(--_light);
  color: var(--ls-coord-on-dark, color-mix(in srgb, var(--_light) 95%, #fff));
}

/* Module "shapes": arrows and circles, in board units (the layer's viewBox
   is 8 by 8). A shape is drawn once. It grows out of its tail as it
   appears; an arrow still being aimed is slimmer and lighter. The script
   gives each shape its colour as --_shape: the public --ls-shape or
   --ls-shape-<name>, or the built-in tone. */
/* Clipped to the board. Every shape lies inside it anyway; with the layer
   allowed to draw outside its box, Safari (26.1, macOS) did not repaint
   all of a shape's edge when the shape changed, and thin lines stayed on
   the board at an arrow's tip and tail until the next move repainted it
   (seen on kibitzo.com and on the demo, 2026-11-12; measured in the
   founder's Safari: leftover pixels after 9 of 15 clean switches as it
   was, after none of 29 with the layer clipped). */
.ls-shapes {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.ls-arrow,
.ls-circle {
  transition: opacity 140ms linear, transform 180ms var(--_ease);
}

/* An arrow's colour grows from faint at its tail, so the piece there stays
   readable, to strong at its head. */
.ls-arrow stop {
  stop-color: var(--_shape);
  stop-opacity: var(--ls-head-opacity, 0.82);
}

.ls-arrow stop:first-child {
  stop-opacity: var(--ls-tail-opacity, 0.14);
}

.ls-arrow-preview {
  opacity: 0.65;
  transition: none;
}

/* A label on an arrow ("1", "2") is set into its head: upright, in a light
   tint of the arrow's own colour, with a soft, see-through edge in a
   deeper tone of it. No hard outline: it should read as part of the arrow.
   The script sets its size to fit. */
.ls-arrow text {
  fill: #fff;
  fill: var(--ls-label-text, color-mix(in srgb, var(--_shape) 12%, #fff));
  stroke: #000;
  stroke: var(--ls-label-rim, color-mix(in srgb, var(--_shape) 72%, #000));
  stroke-opacity: 0.4;
  stroke-width: 0.045;
  stroke-linejoin: round;
  paint-order: stroke;
  font-family: ui-rounded, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-anchor: middle;
}

.ls-circle {
  fill: none;
  stroke: var(--_shape);
  stroke-width: var(--ls-circle-stroke, 0.05);
  opacity: var(--ls-head-opacity, 0.82);
}

@starting-style {
  .ls-arrow:not(.ls-arrow-preview),
  .ls-circle {
    opacity: 0;
    transform: scale(0.8);
  }
}

/* Module "promotion": a panel carved from the board, one square wide, on
   the promotion square and the squares behind it. The layer around it is
   clear and catches a press beside the panel. */
.ls-promotion {
  pointer-events: auto;
}

.ls-picker {
  position: absolute;
  left: 0;
  top: 0;
  width: 12.5%;
  height: calc(var(--ls-picks, 4) * 12.5%);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 0.8cqw;
  background: var(--_light);
  background: color-mix(in srgb, var(--_light) 94%, #fff);
  box-shadow: 0 0 0 1px rgba(120, 85, 45, 0.35), 0 0.9cqw 2.8cqw rgba(40, 25, 10, 0.45);
  transition: opacity 140ms linear;
}

/* From the bottom edge the first choice is at the bottom. */
.ls-picker-up {
  flex-direction: column-reverse;
}

.ls-choice {
  flex: 1;
  margin: 0;
  padding: 0;
  border: 0;
  /* Longhands: the picture comes from the piece's own class (.ls-wq, ...). */
  background-color: transparent;
  background-position: center;
  background-size: 88% 88%;
  background-repeat: no-repeat;
  cursor: pointer;
}

.ls-choice:hover,
.ls-choice:focus-visible {
  background-color: rgba(245, 158, 11, 0.2);
  outline: none;
}

@starting-style {
  .ls-picker {
    opacity: 0;
  }
}

/* Module "overlay": a box the size of a square; what is put in it can be pressed. */
.ls-anchor {
  position: absolute;
  left: 0;
  top: 0;
  width: 12.5%;
  height: 12.5%;
}

.ls-anchor > * {
  pointer-events: auto;
}

/* Module "a11y": text for screen readers only, and the keyboard cursor. */
.ls-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The keyboard cursor: four corner brackets in the colour of the selection
   frame, so it belongs to the board and still cannot be taken for a
   selected square. It exists only while the keyboard is in use. */
.ls-cursor {
  --_c: var(--ls-cursor, var(--_select-edge));
  --_w: max(2px, 0.6cqw);
  position: absolute;
  left: 0;
  top: 0;
  z-index: 6;
  width: 12.5%;
  height: 12.5%;
  background:
    linear-gradient(var(--_c) 0 0) left top / 30% var(--_w),
    linear-gradient(var(--_c) 0 0) left top / var(--_w) 30%,
    linear-gradient(var(--_c) 0 0) right top / 30% var(--_w),
    linear-gradient(var(--_c) 0 0) right top / var(--_w) 30%,
    linear-gradient(var(--_c) 0 0) left bottom / 30% var(--_w),
    linear-gradient(var(--_c) 0 0) left bottom / var(--_w) 30%,
    linear-gradient(var(--_c) 0 0) right bottom / 30% var(--_w),
    linear-gradient(var(--_c) 0 0) right bottom / var(--_w) 30%;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* The board shows its own cursor, so it needs no focus ring around it. */
.ls-board:focus {
  outline: none;
}

@media (prefers-reduced-motion: reduce) {
  .ls-board {
    --_ms: 0ms;
  }
  .ls-arrow,
  .ls-circle {
    transition: none;
  }
}
