/* board.css — styling for the DOM-rendered boards (web/js/boards/).
 *
 * This file is the argument for the whole "C decides / JS draws" split.
 *
 * On the canvas path, a single cell colour makes this journey: solo.c's
 * game_colours() derives it from a light reference background pushed in from
 * CSS; a selection wash is blended toward a hardcoded float triple in
 * frontend.c (sel_accent, SEL_WASH); the result crosses into JS, where
 * softenPrimary() clamps its saturation against a 12-stop hue table and
 * flipLightness() inverts it for dark mode. To retune the selection you edit C
 * and rebuild the wasm — and in dark mode you cannot retune it at all, because
 * dark is a derivation of light.
 *
 * Here it is a class and a custom property. Dark mode is authored, not
 * derived. Nothing is rebuilt.
 */

.pv-board {
  background: var(--board-paper);
  font-family: var(--sans);
}

/* ---- Fifteen -------------------------------------------------------------
 * The transition IS the animation. Upstream repainted the whole board every
 * frame, lerping each tile between its old and new cell (c = animtime /
 * ANIM_TIME). --engine-anim is set from C's own ANIM_TIME so the timing is
 * identical; the browser does the tweening.
 *
 * It works only because each tile is a DOM node keyed by TILE NUMBER, so
 * "tile 7 moved" is expressible. Keyed by cell, a move would just be two cells
 * changing contents, which cannot be tweened.
 */
.pv-board.fifteen .tile,
.pv-board.fifteen .block {
  transition: transform var(--engine-anim, 130ms) linear;
}

/* A jump that is not a move — new game, restart, preset change, a resumed
   save — must not animate, or every tile flies in from its old position. */
.pv-board.fifteen.no-anim .tile,
.pv-board.fifteen.no-anim .block {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .pv-board.fifteen .tile,
  .pv-board.fifteen .block { transition: none; }
}

/* The house aesthetic (see Cube, Solo, and the rest of the collection): a flat
   line grid. Paper cells, a thin ink stroke, hard corners. Each face fills its
   cell exactly, so neighbouring strokes coincide and read as one grid. */
.pv-board .tile-face {
  fill: var(--board-paper);
  stroke: var(--board-rule);
  stroke-width: var(--engine-line-width, 1);
}

.pv-board .tile-num {
  fill: var(--board-ink);
  font-size: 22px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
  /* An SVG <text> does not inherit the page's font stack the way a <span>
     does, so it is named explicitly. */
  font-family: var(--sans);
}

/* The gap, drawn as the solid block you slide (upstream draws a hole). In C
   this was draw_cube(): ~50 lines of cabinet projection. Three polygons.
   Flat fills with the same ink outline the grid uses — no gradients, no
   shadow: the shading IS the three faces. */
.pv-board .block-face,
.pv-board .block-top,
.pv-board .block-side {
  stroke: var(--board-rule);
  stroke-width: var(--engine-line-width, 1);
  stroke-linejoin: round;
}
.pv-board .block-face { fill: var(--board-block); }
.pv-board .block-top { fill: var(--board-block-top); }
.pv-board .block-side { fill: var(--board-block-side); }

/* ---- Solo ----------------------------------------------------------------
 * Every rule below replaces a colour decision that used to live in C.
 *
 * The selection is the clearest case. In C it is pv_selected_colour(): a 45%
 * blend of the cell's own face toward a hardcoded float triple (sel_accent),
 * computed at game_colours() time and baked into the palette — so a shaded
 * cell keeps its identity under the wash. Here that blend IS the cascade:
 * `.cell.sel` sets a fill, `.cell.xdiag.sel` composes, and `color-mix()`
 * handles the one case (selected AND errored) that genuinely needs a blend.
 * Retuning it is a token edit, not a wasm rebuild — and dark mode gets its own
 * authored values instead of a lightness inversion of the light ones.
 */
.pv-board .cell { fill: var(--board-paper); }
.pv-board .cell.xdiag { fill: color-mix(in srgb, var(--board-paper) 92%, var(--ink)); }
.pv-board .cell.sel { fill: var(--board-sel); }
.pv-board .cell.err { fill: var(--board-err); }
.pv-board .cell.err-cage { fill: var(--board-err); }

/* Selected AND errored: neither signal may hide the other, so it is a blend
   rather than a precedence rule. The keyboard cursor parking on a bad cell is
   constant, so this state is common, not a corner case. */
.pv-board .cell.sel.err,
.pv-board .cell.sel.err-cage {
  fill: color-mix(in srgb, var(--board-sel) 55%, var(--board-err));
}

/* A pencil-mode highlight is a different intent from an ink one, and looked
   identical on the canvas path. */
.pv-board .cell.sel-pencil {
  fill: color-mix(in srgb, var(--board-sel) 55%, var(--board-paper));
}

.pv-board .rule {
  stroke: var(--board-line);
  stroke-width: var(--engine-line-width, 1);
}
/* The block boundary. Read from whichblock[], so jigsaw Solo's irregular
   blocks get their borders for free. */
.pv-board .rule.heavy,
.pv-board .frame {
  stroke: var(--board-rule);
  stroke-width: calc(var(--engine-line-width, 1) * 2.5);
  fill: none;
}

.pv-board .digit {
  fill: var(--board-user);        /* a digit YOU entered */
  font-family: var(--sans);
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
}
.pv-board .digit.given {
  fill: var(--board-given);       /* a clue: not yours, and not erasable */
  font-weight: 700;
}

.pv-board .pencil {
  fill: var(--board-pencil);
  font-family: var(--sans);
  text-anchor: middle;
  dominant-baseline: hanging;
  letter-spacing: -0.02em;
}

/* Killer cages: a dashed inset boundary + the cage sum. */
.pv-board .cage {
  stroke: var(--board-cage, var(--muted));
  stroke-width: 1;
  stroke-dasharray: 3 2;
}
.pv-board .cage-sum {
  fill: var(--board-cage, var(--muted));
  font-family: var(--sans);
  font-weight: 600;
  dominant-baseline: hanging;
}
