@charset "UTF-8";
/* ==========================================================================
   Bible Games — the board layer  (shared/board/board.css)
   --------------------------------------------------------------------------
   The parts of a BOARD GAME page that are the same whatever the board is: the
   frame the board sits in, the turn indicator, the captured tray, the control
   row, the move list, the rules sheet.

   It sits on shell.css exactly as quiz.css and grid.css do, and it adds a
   layer rather than replacing anything: the topbar, the Back link, the buttons
   and the end-of-round card all still come from the shell.

   HOW A PAGE LOADS IT

     <link rel="stylesheet" href="/biblegames/shared/theme.css?v=...">
     <link rel="stylesheet" href="/biblegames/shared/shell.css?v=...">
     <link rel="stylesheet" href="/biblegames/shared/board/board.css?v=...">
     <link rel="stylesheet" href="styles.css?v=...">

   In that order. Tokens, then the shell, then the board's shape, then the
   game's own identity — each free to override the one before it.

   --------------------------------------------------------------------------
   WHAT IS HERE AND WHAT IS NOT           (the Shared.md §5 test, applied)

   "Could a game reasonably want it different?"

     * The frame, the turn indicator, the control row, the move list, the
       rules sheet — NO. Every board game wants the same ones, and seventy-five
       copies is how they drift apart.  ->  here
     * The board's own colours, and its piece artwork — YES, obviously. Go
       stones and Mancala seeds are not the same object.  ->  each game's
       styles.css, by overriding the --board-* tokens in section 1.

   --------------------------------------------------------------------------
   THE LAYOUT CONTRACT (Standards.md §5)

   A game page must NEVER scroll, and nothing may ever scroll horizontally.
   The shell is a fixed-height flex column; this file adds two rows either side
   of .stage and puts the board INSIDE .stage, which is the one elastic row.

   A board game adds two things a quiz never had — a move list and a rules
   sheet — and neither is a row. Both are PANELS over the stage, opened by a
   button, because a row for either would take height from the board on every
   screen in order to be useful on none. A panel may scroll inside itself; the
   page still may not.

   The @media (max-height: 299px) exception is inherited from shell.css and
   extended here, and stays in step with MIN_NO_SCROLL_HEIGHT in
   BibleGames_Preview.py.

   --------------------------------------------------------------------------
   Contents
     1.  Tokens this layer adds
     2.  The board row, and how a board is sized
     3.  The board itself — cells, lines, pieces
     4.  Legal moves, the last move, focus
     5.  The turn indicator
     6.  The captured tray
     7.  The control row
     8.  Panels — the move list and the rules sheet
     9.  Responsive
     10. Reduced motion, forced colours, more contrast, print
   ========================================================================== */

/* -------------------------------------------------------------------------- */
/* 1. Tokens this layer adds                                                  */
/* -------------------------------------------------------------------------- */

/*
   A game overrides these in its own styles.css and changes nothing else. That
   is the whole customisation surface for a board's appearance, and it is
   deliberately small: seventy-five games each inventing their own class names
   is seventy-five games that cannot be restyled at once.
*/
:root {
  /* The board surface */
  --board-bg: rgba(18, 12, 42, 0.72);
  --board-edge: var(--panel-edge);
  --board-cell: rgba(255, 240, 205, 0.05);
  --board-cell-alt: rgba(255, 240, 205, 0.12);
  --board-line: rgba(255, 224, 160, 0.42);
  --board-line-width: 2;

  /* The two players. A game renames the colours; it does not rename the
     SHAPES, which come from sprites.js and are the signal that survives a
     colour-blind eye, a photocopy and forced colours. */
  --piece-0: #ffd45c;
  --piece-0-edge: #7a5313;
  --piece-1: #7fc8ff;
  --piece-1-edge: #143d5c;
  --piece-2: #9be89b;
  --piece-2-edge: #1d5220;
  --piece-3: #ff9c6e;
  --piece-3-edge: #6d2a12;

  /* Board furniture */
  --board-hint: rgba(255, 243, 176, 0.5);
  --board-last: var(--gold-2);
  --board-focus: var(--gold-1);
  --board-win: var(--ok);

  /*
     A PIECE THIS MOVE WOULD TAKE, and it gets its own colour rather than
     borrowing the hint's.

     `--board-hint` is deliberately faint -- it is the dot that may appear on
     twenty empty squares at once, and on a square board it carries a further
     50% opacity on top so that a field of dots does not shout. A capture is the
     opposite kind of event: rare, decisive, and the thing a child most needs to
     see. At the hint's weight the ring came out grey on a dark board, which is
     legible and does not read as a WARNING.

     It must stay distinguishable from `--board-last`, which is the dashed ring
     round the square the opponent came from. Solid against dashed is the shape
     half; this is the colour half.
  */
  --board-take: rgba(255, 176, 92, 0.95);

  /*
     THE HINT'S ARROW -- the move the solver is SUGGESTING (Wave 5 decision AA).

     A fourth mark on a board that already has three, so it has to differ from
     all of them in shape as well as colour, which is Standards.md §2 and
     decision 9:

       --board-hint   a faint DOT in an empty cell        "you may"
       --board-take   a solid RING round a piece          "this move takes it"
       --board-last   a dashed RING round a cell          "that just happened"
       --board-show   two rings joined by an ARROW        "do this"

     The arrow is the part that carries the meaning: the other three are all
     symmetrical, so on a puzzle whose whole hint is WHICH WAY a peg jumps,
     rings alone would say the two cells and not the direction. Under
     forced-colors it becomes Highlight like the rest, and the arrowhead still
     points, because a wedge is a shape and survives when every colour is gone.
  */
  --board-show: rgba(126, 231, 255, 0.95);

  /* A track's jumps. The SHAPE is what tells them apart (see §3); these are
     the third signal, after the shape and the spoken label. */
  --board-ladder: #9be89b;
  --board-snake: #ff9c6e;
  --board-snake-eye: #6d2a12;

  /*
     A panel over the board must be OPAQUE, and this token exists because the
     first version was not.

     --panel-1 and --panel-2 are translucent, which is right for a panel over
     the page background and wrong for one over a board: the move list was
     readable, the board showed through it, and the gold pieces behind it
     turned brown. Every automated assertion passed on that page — no scroll,
     no overflow, no console error, seven moves in the list. Only the
     screenshot showed it.
  */
  --sheet-bg: linear-gradient(160deg, #241a4d, #140e33);

  --board-gap: 8px;
}

/* -------------------------------------------------------------------------- */
/* 2. The board row, and how a board is sized                                 */
/* -------------------------------------------------------------------------- */

/*
   THE BOARD IS NOT FORCED SQUARE, AND THAT IS THE FIX, NOT A COMPROMISE.

   The obvious approach is `aspect-ratio: 1/1` on a square box sized to the
   smaller viewport dimension. It is wrong for this set: Connect Four is 7x6,
   Fanorona is 9x5, a Snakes and Ladders track is 10x10 but the Royal Game of
   Ur is 8x3. Forcing a square would letterbox half the games inside a box we
   drew ourselves, and crop the wide ones.

   So the SVG keeps its OWN aspect ratio, from the viewBox the topology
   supplies, and `preserveAspectRatio="xMidYMid meet"` fits it inside whatever
   space the flex row gives it. The browser does the arithmetic, at every
   viewport, for all eight topologies, with no JavaScript and no media query.

   THE QUARTER TURN IS NOT DONE HERE EITHER, AND THAT IS DECISION II.

   A board that never promised an axis — a track, a graph, anything render.js
   gives role="application" rather than role="grid" — turns ninety degrees when
   turning would make it materially bigger. Out of Egypt reads 33px a side on
   an iPhone 14 and 50px turned, which is the difference between missing the
   44px tap floor and clearing it.

   IT IS NOT A MEDIA QUERY, BECAUSE THE ANSWER IS NOT THE SAME ON EVERY PHONE.
   The frame is the viewport less about 365px of chrome — the header, the turn
   banner, the dice button, Undo/Redo/New game, Moves/How to Play, the three
   level buttons and the hint line. Measured in a real browser, 2026-09-08:

     360x640  small Android   frame 324x275   Out of Egypt 30.5px -> 25.9px
     375x667  iPhone SE       frame 339x352                31.9px -> 33.2px
     390x844  iPhone 14       frame 354x529                33.3px -> 49.8px
     412x915  Pixel 7         frame 376x600                35.4px -> 56.6px

   On the short phone the frame is LANDSCAPE and the turn makes every board
   SMALLER. A breakpoint at 768px would have turned them all, including there.
   So render.js measures the frame it actually has, with `BG.Render.turnGain()`,
   and turns only when the answer is worth it.

   AND IT IS NOT A CSS TRANSFORM, BECAUSE THAT WAS TRIED AND COLLAPSED. It was
   `@media (max-width: 767px) { .board--rotated { transform: rotate(90deg) } }`.
   A CSS transform turns the box AFTER layout: the box still has the frame's
   shape, `meet` has already fitted the wide content against the frame's WIDTH,
   and the turn then shows that width-limited result against the frame's
   height. The width constrains it twice, and the board read 172x319 inside
   360x640 — half what its own shape allows. BoardPlay caught it and refused.

   The turn instead swaps the viewBox's width and height in render.js and
   rotates one group inside it, so the SVG's own aspect IS the turned aspect
   and `meet` — the same rule described above, doing the same arithmetic it
   always did — fills the frame.

   `.board--turned` is set on the SVG so the tools and the tests can see the
   decision, and it deliberately carries NO STYLE: the moment it carries one,
   the picture depends on two files agreeing.

   What no layout can fix, and §9 records with its number instead: a board of
   more than about seven columns cannot reach 44px on a phone whichever way up
   it is. Pente is fifteen by fifteen and reads 23px turned either way.
*/
.board__frame {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
}

.board {
  display: block;
  width: 100%;
  height: 100%;
  /* Without this an SVG in a flex child can refuse to shrink and push the
     control row off the bottom — the same fault min-height: 0 fixes on the
     stage, one level down. */
  min-width: 0;
  min-height: 0;
  overflow: visible;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.board__surface {
  fill: var(--board-bg);
  stroke: var(--board-edge);
  stroke-width: 2;
  rx: 10;
}

/* -------------------------------------------------------------------------- */
/* 3. The board itself — cells, lines, pieces                                 */
/* -------------------------------------------------------------------------- */

.board__cell {
  fill: var(--board-cell);
  stroke: var(--board-line);
  stroke-width: 1;
  transition: fill var(--dur) ease;
}

.board__cell.is-alt {
  fill: var(--board-cell-alt);
}

/* A dead cell — a hole in a peg board, a masked square. Drawn, but not part of
   the game, and never focusable. */
.board__cell.is-void {
  fill: none;
  stroke: none;
  pointer-events: none;
}

/* The lines of a graph board. On Alquerque the lines ARE the rules: a diagonal
   move looks legal only because a line is drawn there. */
.board__link {
  stroke: var(--board-line);
  stroke-width: var(--board-line-width);
  stroke-linecap: round;
  fill: none;
}

/* -------------------------------------------------------------------------- */
/* 3a. The two boards that are not made of squares                            */
/* -------------------------------------------------------------------------- */

/*
   render.js writes `board board--<kind>` on the <svg>, so a topology's own
   drawing rules live HERE, once, rather than being repeated in each of its
   games' styles.css. Seventy-five copies of one rule is seventy-five chances
   to miss it, and the miss is silent: the board renders, it is simply wrong.
*/

/*
   AN INTERSECTION BOARD'S CELLS ARE INVISIBLE.

   The drawing is the LINES; the square around each crossing exists so a finger
   has somewhere to land. Drawn, it would put a grid of boxes over a grid of
   lines and turn a Go board into a chessboard with extra ink.

   `fill: transparent` and not `fill: none` — a shape with no fill is not
   hit-testable, and every one of these cells is a tap target.
*/
.board--intersect .board__cell {
  fill: transparent;
  stroke: none;
}

/* The star points. Small, solid, and the same ink as the lines, because they
   are part of the printed board rather than part of the play. */
.board__dot {
  fill: var(--board-line);
  stroke: none;
  pointer-events: none;
}

/*
   A HEX BOARD'S CELLS ARE THE DRAWING, and there are three shades because
   there have to be: three cells meet at every corner of a hex grid, so a
   two-colour parity would give touching cells the same shade and the board
   would read as smudged rather than as a grid.

   The three are close together on purpose. They are there to make the tiling
   legible, not to mean anything — nothing in any hex game depends on which
   shade a cell has, so a strong difference would be a signal about nothing.
*/
.board--hex .board__cell {
  stroke-width: 1.5;
}

.board--hex .board__cell.is-alt {
  fill: var(--board-cell-alt);
}

/*
   WHOSE EDGE IS WHOSE — the whole of Hex in one rule.

   Colour AND dash, because colour is never the only signal (Standards.md §2)
   and this is the single most important thing on the board: a child who cannot
   tell which two sides are theirs cannot play at all. Side 0's rails are solid
   and side 1's are dashed, so the two are told apart in greyscale, in a
   photocopy and under forced colours.
*/
.board__border {
  fill: none;
  stroke-width: 9;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

.board__border.is-side-0 {
  stroke: var(--piece-0);
}

.board__border.is-side-1 {
  stroke: var(--piece-1);
  stroke-dasharray: 22 12;
}

@media (forced-colors: active) {
  .board__border {
    stroke: CanvasText;
  }

  /* The palette is gone, so the dash is the whole signal. */
  .board__border.is-side-1 {
    stroke-dasharray: 22 12;
  }

  .board__dot {
    fill: CanvasText;
  }
}

/* The route of a track board, drawn under the squares. */
.board__route {
  stroke: var(--board-line);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  opacity: 0.5;
}

/*
   A ROAD. Drawn OVER the squares, because under them it is invisible -- and
   therefore it must never take a tap away from the square it lies on.

   The default is deliberately faint and thin: on a board whose roads matter,
   the game's own styles.css says so in its own colours (01-Architecture.md §9),
   and on a board where they do not, this is a thread rather than a wall.
*/
.board__road {
  stroke: var(--board-line);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  opacity: 0.45;
  pointer-events: none;
}

/*
   A LADDER AND A SNAKE ARE TOLD APART BY SHAPE, AND ONLY THEN BY COLOUR.

   Standards.md §2, and this is the case it was written for: on a track board
   the snakes and the ladders are the only things that change what happens to
   you, and the traditional drawing tells them apart with red against green —
   which is the single commonest confusion there is.

   So a ladder is a straight double rail with rungs across it, and a snake is a
   wavy line with a round head on the square that swallows you. Different
   shapes, told apart in greyscale, in a photocopy and under forced colours.
   The colours below are the third signal, not the first, and topo-track's
   label() says the same thing in words for anyone who can see neither.

   Neither takes a click: the square underneath is the hit target, and a ladder
   drawn across four squares must not stop any of them being tapped.
*/
.board__ladder {
  stroke: var(--board-ladder);
  stroke-width: 5;
  stroke-linecap: round;
  fill: none;
  pointer-events: none;
}

.board__ladder--rungs {
  stroke-width: 3.5;
  opacity: 0.85;
}

.board__snake {
  stroke: var(--board-snake);
  stroke-width: 7;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  pointer-events: none;
}

.board__snake-head {
  fill: var(--board-snake);
  stroke: var(--board-snake-eye);
  stroke-width: 3;
  pointer-events: none;
}

/*
   Pieces never take a click. The cell underneath is the hit target and the
   focusable element, so a piece sitting on top of it must be transparent to
   the pointer — otherwise tapping a piece to pick it up does nothing, which is
   the first thing anybody tries.
*/
.board__piece {
  stroke-width: 3;
  paint-order: stroke;
  pointer-events: none;
  transition: transform var(--dur) ease;
}

.board__piece.is-side-0 { fill: var(--piece-0); stroke: var(--piece-0-edge); }
.board__piece.is-side-1 { fill: var(--piece-1); stroke: var(--piece-1-edge); }
.board__piece.is-side-2 { fill: var(--piece-2); stroke: var(--piece-2-edge); }
.board__piece.is-side-3 { fill: var(--piece-3); stroke: var(--piece-3-edge); }

/* The piece a player has picked up and not yet put down. */
.board__piece.is-lifted {
  opacity: 0.55;
}

/*
   A LEGAL MOVE ONTO A CELL THAT IS NOT EMPTY.

   The legal-move dot is drawn under the pieces, which is right for the dozens
   of games whose every move lands on an empty square and useless for the first
   family whose moves land on an ENEMY -- a dot under the piece it points at is
   invisible, and this file's own draw-order note says a marker hidden by the
   thing it marks is not a marker.

   So it is a RING ROUND the piece rather than a dot in the middle of the
   square, and that is decision 9 rather than taste: a legal move is a dot, a
   piece you may lift is a rim, the last move is a dashed ring, and a piece you
   may TAKE is a solid ring drawn over it. Different shape, different position,
   and it cannot be confused with the lift rim because that one sits on a cell
   holding one of YOUR pieces and this one never does.

   Dashed would have collided with the last-move ring. Solid, thicker, and in
   the hint colour, so it belongs visibly to the same family as the dot it
   replaces.
*/
.board__take {
  fill: none;
  stroke: var(--board-take);
  stroke-width: 5;
  pointer-events: none;
}

@media (forced-colors: active) {
  .board__take {
    stroke: Highlight;
  }
}

/* Text drawn on the board — a square's number on a track, a stack's height. */
.board__ink {
  fill: var(--text-dim);
  font-family: var(--font-ui);
  font-size: 26px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
  user-select: none;
}

/*
   A NUMBER IN THE CORNER, WHICH IS WHERE A REAL BOARD PRINTS IT.

   The middle of a square is where the ladders, the snakes and the counters all
   are. Centred, a track number under a ladder rail measured 1.23:1 — cream on
   light green — and a number under a pawn could not be seen at all. In the
   corner it sits on the plain square, it survives a counter standing on it, and
   it is where a child looks for it.

   The geometry decides this, not the game: topo-track answers inkAt() and
   render.js adds this class when it does.
*/
.board__ink--corner {
  text-anchor: start;
  dominant-baseline: hanging;
  font-size: 22px;
}

/*
   The chip a corner number is printed on.

   A diagonal ladder still clips the corner of a square on its way past, and a
   cream number over a pale green rail measures about 1.2:1 — on those few
   squares the number becomes the hardest thing on the board to read, which is
   the wrong way round for the thing a child navigates by.

   A DARKENING rather than a colour, so it vanishes on the ninety-odd plain
   squares whatever shade they are, and shows only where something has been
   drawn across the corner. That is what a printed board does: the number is on
   the square, and the artwork is painted around it.

   0.85 AND NOT 0.55, MEASURED 2026-09-05.

   The ink now draws OVER the pieces (render.js), because on Backgammon five
   checkers share a point and the number underneath them could not be seen at
   all. A checker is the brightest thing on any of these boards, so a chip that
   was dark enough for a ladder rail was not dark enough for a gold disc: the
   number came out at 3.3:1 over one. At 0.85 it is 8.1:1 over the same disc.

   Every board on this site is dark — the palest --board-cell in the seventy-five
   is #3c2a1e — so on an EMPTY square this is a change of about two RGB values
   and the chip stays invisible, which is the whole point of it being a
   darkening. Re-measured across every built board afterwards.
*/
.board__ink-chip {
  fill: rgba(6, 4, 20, 0.85);
  pointer-events: none;
}

/* -------------------------------------------------------------------------- */
/* 4. Legal moves, the last move, focus                                       */
/* -------------------------------------------------------------------------- */

/*
   Three different things are marked on a board at once, and they must not be
   mistakable for one another by shape OR by colour alone:

     a legal move    a soft filled dot in the middle of the cell
     the last move   a ring around the cell's edge
     focus           a bright square bracket outside the cell

   Different shapes, different positions, different colours. A child using one
   signal still has the other two.
*/
.board__hint {
  fill: var(--board-hint);
  pointer-events: none;
  transition: opacity var(--dur) ease;
}

/*
   A GHOST, WHERE THE GAME DRAWS ITS OWN SHAPES.

   render.js draws the legal-move mark as the piece that WOULD arrive when the
   game supplies `shapes` — because on a board whose own drawing is made of
   dots, a dot per legal move turns thirty-six landmarks into a hundred and
   twenty-one identical marks and a child cannot tell the board from the moves.
   (Found by looking at a screenshot, 2026-09-04.)

   A ghost has to be unmistakably NOT a played piece, or the board looks full
   before anyone has moved. So it is thin, and it is the board's own hint
   colour rather than either player's — the same ink the dot always used.
*/
.board--square .board__hint,
.board--intersect .board__hint {
  opacity: 0.5;
}

.board__last {
  fill: none;
  stroke: var(--board-last);
  stroke-width: 3;
  stroke-dasharray: 6 5;
  pointer-events: none;
}

/*
   THE SUGGESTED MOVE. Two rings and an arrow between them.

   The `from` ring is dashed and the `to` ring is solid, so the two ends are
   told apart by SHAPE as well as by the arrowhead -- a hint printed in black
   and white, or seen by an eye that cannot separate the ends by hue, still
   reads in the right direction.
*/
.board__show {
  fill: none;
  stroke: var(--board-show);
  stroke-width: 5;
  pointer-events: none;
}

.board__show--from {
  stroke-dasharray: 9 6;
}

.board__show-arrow {
  stroke: var(--board-show);
  stroke-width: 5;
  stroke-linecap: round;
  pointer-events: none;
}

.board__show-head {
  fill: var(--board-show);
  stroke: none;
  pointer-events: none;
}

@media (forced-colors: active) {
  .board__show,
  .board__show-arrow {
    stroke: Highlight;
  }
  .board__show-head {
    fill: Highlight;
  }
}

/*
   The focus ring is DRAWN rather than left to `outline`.

   `outline` on an SVG child is inconsistent across browsers — some draw it
   around the element's bounding box, some around the whole SVG, some not at
   all — and Standards.md §2 requires a visible focus indicator that a keyboard
   or switch user can actually see. A drawn rectangle is the same everywhere.
   The CSS outline below stays as well, as a second belt for anything that
   renders the ring late.
*/
.board__focus {
  fill: none;
  stroke: var(--board-focus);
  stroke-width: 4;
  pointer-events: none;
}

.board__cell:focus {
  outline: 3px solid var(--board-focus);
  outline-offset: 2px;
}

.board__cell:focus:not(:focus-visible) {
  outline: none;
}

.board__cell:focus-visible {
  outline: 3px solid var(--board-focus);
  outline-offset: 2px;
}

/* The line that won the game. */
.board__win {
  fill: none;
  stroke: var(--board-win);
  stroke-width: 8;
  stroke-linecap: round;
  opacity: 0.85;
  pointer-events: none;
}

/*
   A PIECE YOU CAN PICK UP IS MARKED AT REST, AND NOT ONLY UNDER A POINTER.

   The dot above marks a square a piece can move TO. In a step game nothing is a
   destination until something has been lifted, so a board that marked only
   destinations showed a child nothing at all at the start of their turn. On a
   desktop the hover rule below hid that; on a phone there is no hovering, and
   twenty-five of the seventy-five games are step games.

   A rim rather than a second dot, and that is decision 9 rather than taste: a
   legal move is a DOT IN THE MIDDLE, a piece you may lift is a RIM AROUND THE
   EDGE, the last move is a DASHED RING and focus is a BRIGHT BRACKET OUTSIDE.
   Different shape, different position, different colour — so a child using any
   one of the three signals still has the other two.
*/
.board__cell.is-origin {
  stroke: var(--board-hint);
  stroke-width: 4;
}

/*
   Hover must be gated. On a touch screen the browser fakes a hover on tap and
   leaves the cell lit until you tap elsewhere — so every square a child has
   touched ends up glowing. Standards.md §2.
*/
@media (hover: hover) {
  .board__cell.is-playable:hover {
    fill: var(--board-cell-alt);
    cursor: pointer;
  }
}

/* -------------------------------------------------------------------------- */
/* 5. The turn indicator                                                      */
/* -------------------------------------------------------------------------- */

/*
   Whose turn it is, said three ways at once: the piece's SHAPE, the player's
   NAME, and the colour. Standards.md §2 — colour is never the only signal, and
   "it is the yellow player's turn" is unreadable to the one boy in twelve this
   rule exists for.
*/
.boardbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--board-gap);
  min-height: 34px;
}

.turn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 4px 12px;
  border: 1px solid var(--panel-edge);
  border-radius: 999px;
  background: linear-gradient(160deg, var(--panel-1), var(--panel-2));
  font-family: var(--font-ui);
  font-size: 0.92rem;
  color: var(--text);
}

.turn__shape {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}

.turn__who {
  font-weight: 700;
}

.turn__what {
  color: var(--text-dim);
}

/* The game is over. Kept as a state on the same element so the indicator never
   disappears and shift the row's height. */
.turn.is-over {
  border-color: var(--panel-edge-lit);
}

/* -------------------------------------------------------------------------- */
/* 6. The captured tray                                                       */
/* -------------------------------------------------------------------------- */

.tray {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  color: var(--text-dim);
}

.tray__group {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.tray__shape {
  width: 16px;
  height: 16px;
}

/* The count is the word half: "3 discs", never three shapes and a colour. */
.tray__count {
  font-variant-numeric: tabular-nums;
  color: var(--text);
  font-weight: 600;
}

/* -------------------------------------------------------------------------- */
/* 7. The control row                                                         */
/* -------------------------------------------------------------------------- */

.boardctl {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--board-gap);
}

/*
   THE ROW FOR MOVES THAT LAND ON NO SQUARE.

   A dice throw is a legal move with nothing to tap, and so is a pawn borne off
   the end of a track. Without a row for them the game reports legal moves and
   offers nothing to press, which is indistinguishable from a frozen board.

   It sits directly under the board rather than in the control row, because it
   is part of the TURN and Undo is not: a child looking for what to do next
   should find it beside the thing it acts on. The row collapses to nothing
   when there is nothing to offer, so a game that never has one pays no height.
*/
.boardactions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--board-gap);
}

.boardactions:empty {
  display: none;
}

/* Bigger and brighter than Undo, because it is the move. */
.board__action {
  min-height: 44px;
  padding: 0 22px;
  font-weight: 700;
  border-color: var(--panel-edge-lit);
  background: linear-gradient(180deg, #ffd45c, #e08c14);
  color: #241703;
}

@media (hover: hover) {
  .board__action:hover {
    background: linear-gradient(180deg, #ffe08a, #f09a1a);
  }
}

/* A disabled Undo must still be readable, not a grey smear — a child needs to
   see that the button exists and is simply not available yet. */
.boardctl .btn[disabled] {
  opacity: 0.45;
  cursor: default;
}

.boardctl .btn[disabled]:hover {
  border-color: var(--panel-edge);
}

/* The difficulty picker is a group of radio-like buttons, not a <select>,
   because a select on a phone opens a modal wheel for three options. */
.level {
  display: inline-flex;
  border: 1px solid var(--panel-edge);
  border-radius: 10px;
  overflow: hidden;
}

.level__btn {
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-right: 1px solid var(--panel-edge);
  background: transparent;
  color: var(--text-dim);
  font-family: var(--font-ui);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.level__btn:last-child {
  border-right: 0;
}

/* The chosen level is marked by weight and background as well as colour, and
   carries aria-pressed for anyone who cannot see either. */
.level__btn[aria-pressed="true"] {
  background: linear-gradient(180deg, #ffd45c, #e08c14);
  color: #241703;
  font-weight: 800;
}

/* -------------------------------------------------------------------------- */
/* 8. Panels — the move list and the rules sheet                              */
/* -------------------------------------------------------------------------- */

/*
   A panel covers the stage rather than taking a row of its own. It may scroll
   INSIDE itself; the page still may not. That is the same decision quiz.css
   made for the feedback panel, and for the same reason.
*/
.sheet {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--panel-edge-lit);
  border-radius: var(--radius);
  background: var(--sheet-bg);
  box-shadow: 0 18px 40px var(--panel-shadow);
}

.sheet__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--panel-edge);
}

.sheet__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--gold-2);
}

.sheet__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 8px 12px;
  font-family: var(--font-ui);
  font-size: 0.88rem;
  color: var(--text);
}

.sheet__body p {
  margin: 0 0 0.7em;
}

/* -------------------------------------------------------------------------- */
/* 8-i. "More" — the bar that says the sheet has not finished                  */
/* -------------------------------------------------------------------------- */

/*
   THE INSTRUCTIONS PANEL WAS CUT OFF ON A PHONE, ON ALL THIRTY-SEVEN GAMES.

   Found 2026-09-07, in a screenshot. Phone portrait showed THREE RULES OUT OF
   SIXTEEN TO EIGHTEEN. `.sheet__body` scrolls, so nothing was unreachable — and
   on three games it cut cleanly at the end of rule 3 and read as the complete
   instructions. Rules 4 onward are where most games say how to WIN.

   A FADE AT THE BOTTOM EDGE IS THE USUAL FIX AND IT IS FORBIDDEN HERE.
   Standards.md §2: colour is never the only signal. So this is three things a
   gradient is not —

     a WORD     "More", so it survives a greyscale print and a screen reader;
     a SHAPE    a triangle, drawn with borders rather than a glyph so that no
                font can fail to have it and no emoji renderer can turn it into
                something else;
     a BUTTON   pressable, and in the tab order, so a child who cannot manage a
                scroll gesture inside a modal has something to press and a
                keyboard user reaches it with one Tab from the close button.

   IT IS PINNED OVER THE FOOT OF THE BODY, AND THE BODY RESERVES THE ROOM
   PERMANENTLY. Laying it out in the flow instead would make the body shorter
   whenever the bar appeared — which changes how much there is to scroll, which
   is the quantity that decides whether the bar appears. A measurement that
   moves what it measures flickers on the last pixel. The reservation costs one
   bar's height of blank space at the end of a panel that does not need it,
   which lands after the theme verse and reads as breathing room.
*/
.sheet--more .sheet__body {
  padding-bottom: 52px;
}

.sheet__more {
  /*
     FLUSH TO THE PANEL'S INNER EDGE, and the zeroes are measured rather than
     tidy. Inset by a pixel, a sliver of the line of prose passing UNDER the bar
     showed between the bar and the panel border -- a torn half-line of text
     that read as a rendering fault. `.sheet__body` ends exactly here, so this
     is the one position where nothing can show beneath it.
  */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;

  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;

  /* 44px of thumb, and it is the whole width of the panel: this is the one
     control on the page a child is meant to be able to hit without looking. */
  min-height: 44px;
  padding: 0 12px;

  border: 0;
  border-top: 1px solid var(--panel-edge-lit);
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--sheet-bg);
  color: var(--gold-2);

  font-family: var(--font-ui);
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}

/* The shape. Borders rather than a character, so it cannot go missing. */
.sheet__more::after {
  content: "";
  display: block;
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 7px solid currentColor;
}

.sheet__more:hover,
.sheet__more:focus-visible {
  background: var(--panel-bg, rgba(255, 255, 255, 0.06));
}

/* The focus ring is the shell's, and it must not be lost behind the body: the
   bar sits over the scrolling text, so the outline is drawn inside. */
.sheet__more:focus-visible {
  outline: 2px solid var(--gold-2);
  outline-offset: -3px;
}

/* Windows High Contrast throws the palette away. The border and the triangle
   both come back as CanvasText, so the bar is still a bar and still points. */
@media (forced-colors: active) {
  .sheet__more {
    border-top: 1px solid CanvasText;
    color: CanvasText;
  }
}

/* The stage has to be a positioning context for the panel to cover it. */
.stage {
  position: relative;
}

/*
   The move list is the transcript, and it is describe() made visible — the
   same sentences a screen reader hears. A game whose opponent moves in silence
   is unplayable without sight (01-Architecture.md §4), and this is the half of
   that a sighted player uses.
*/
.moves {
  margin: 0;
  padding: 0 0 0 2.6em;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  line-height: 1.55;
}

.moves li {
  margin: 0 0 2px;
  color: var(--text-dim);
}

.moves li::marker {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.moves li:last-child {
  color: var(--text);
  font-weight: 600;
}

.moves__empty {
  margin: 0;
  color: var(--text-faint);
  font-style: italic;
}

/* -------------------------------------------------------------------------- */
/* 8a. How to Play — the same panel on all seventy-five                       */
/* -------------------------------------------------------------------------- */

/*
   EVERY BOARD GAME ALREADY CARRIED A RULES SHEET AND NOTHING EVER SHOWED IT.

   Found 2026-09-04. The heading of §8 above says "the move list and the rules
   sheet"; only the move list was ever built. Each game's rules.php held a list
   of sentences written for a child, its validator checked them on every page
   load, and the words went nowhere. A child arriving at Mū Tōrere — which
   almost nobody has heard of — got a star, eight counters, and no way at all
   to find out what the game was.

   The panel reuses `.sheet` rather than growing chrome of its own, so the
   instructions and the move list are visibly the same kind of thing and there
   is one set of rules about how a panel behaves. shared/board/howto.js builds
   the contents; this is only how they look.

   It is READING, so the type is bigger than the move list's and the measure is
   capped: a line of prose longer than about 70 characters is measurably harder
   to read, and a board game's panel is as wide as the board.
*/
.howto {
  font-size: 0.95rem;
  line-height: 1.55;
}

.howto__classic {
  margin: 0 0 0.9em;
  color: var(--text-dim);
  font-style: italic;
}

.howto__rules {
  max-width: 62ch;
  margin: 0 0 1em;
  padding: 0 0 0 1.6em;
}

/*
   Numbered, and the numbers are gold and bold.

   The rules are a SEQUENCE — what the board is, who goes first, how to move,
   how to win — and a child reading them wants to keep their place. A bulleted
   list gives them nothing to keep it with.
*/
.howto__rules li {
  margin: 0 0 0.55em;
  padding-left: 0.2em;
  color: var(--text);
}

.howto__rules li::marker {
  color: var(--gold-2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.howto__subtitle {
  margin: 0 0 0.3em;
  font-family: var(--font-display);
  font-size: 0.95rem;
  color: var(--gold-2);
}

.howto__keys {
  max-width: 62ch;
  margin: 0 0 1em;
  color: var(--text-dim);
}

/* A game that has not had its rules written yet says so where a person will
   see it, rather than showing an empty panel that looks like a broken one. */
.howto__missing {
  margin: 0 0 1em;
  color: var(--warn, #ffb454);
  font-weight: 600;
}

/*
   The theme verse, and it is LAST on purpose: it is the thing left on a
   child's screen as the panel closes and the game begins.
*/
.howto__scripture {
  margin: 0;
  padding: 10px 12px;
  border-left: 3px solid var(--gold-2);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--panel-bg, rgba(255, 255, 255, 0.04));
}

.howto__verse {
  margin: 0 0 0.35em;
  max-width: 62ch;
  font-family: var(--font-display);
  color: var(--text);
}

.howto__ref {
  color: var(--gold-2);
  font-family: var(--font-ui);
  font-size: 0.85rem;
  font-style: normal;
  font-weight: 700;
}

/* Windows High Contrast throws the palette away, and a panel drawn only with
   background colour becomes an invisible rectangle over the board. */
@media (forced-colors: active) {
  .howto__scripture {
    border-left: 3px solid CanvasText;
  }
}

/* -------------------------------------------------------------------------- */
/* 9. Responsive                                                              */
/* -------------------------------------------------------------------------- */

/*
   Phone landscape is the hard case for a board, not phone portrait, and it is
   the opposite of the quiz. A quiz is a column of text that wants width; a
   board is a shape that wants HEIGHT, and in landscape there is almost none.
   So the two rows either side of the board give up their padding first, and
   the board keeps every pixel it can.
*/
@media (max-height: 520px) {
  .boardbar {
    min-height: 0;
  }

  .turn {
    padding: 2px 10px;
    font-size: 0.85rem;
  }

  .tray {
    font-size: 0.76rem;
  }

  .boardctl .btn {
    min-height: 40px;
    padding: 0 12px;
    font-size: 0.88rem;
  }

  /* The action button keeps its 44px target where the ordinary controls give
     theirs up: it is the move, and a mis-tap on it costs a turn. */
  .board__action {
    padding: 0 18px;
  }

  /* The instructions panel is READING, and in a 360px-tall window there is
     room for about six lines of it. Tightening the leading and the gaps buys
     two more lines; the panel scrolls inside itself for the rest, which is
     what .sheet__body already does. */
  .howto {
    font-size: 0.88rem;
    line-height: 1.45;
  }

  .howto__rules li {
    margin-bottom: 0.4em;
  }

  .howto__classic,
  .howto__keys {
    margin-bottom: 0.6em;
  }

  /* The More bar gives up four pixels here, exactly as the ordinary controls
     above do, and for the same reason: in a 360px-tall window every line of
     reading is worth more than the four pixels. It keeps 40px of thumb, and it
     is a full-width target, which is the dimension that matters on a bar. */
  .sheet__more {
    min-height: 40px;
  }

  .sheet--more .sheet__body {
    padding-bottom: 48px;
  }
}

/*
   PHONE LANDSCAPE: THE CHROME MOVES BESIDE THE BOARD.

   This is the hard case for a board and it is the opposite of a quiz. A quiz is
   a column of text that wants width; a board is a shape that wants HEIGHT, and
   in landscape there is almost none — while there is more width than anything
   can use.

   Stacked, the chrome eats about 180 of a 360px window and Connect Four comes
   out 207x179: it passes every layout check, sits in the middle of a mostly
   empty screen, and its holes are thirty pixels across. Nothing is overflowing
   and nothing is scrolling. It is simply too small to play, which is the fault
   BibleGames_BoardPlay.py's "the board fills its space" check exists for, and
   the first version of it slipped past at 50% of the smaller side.

   Beside it, the same board is about 360x310 — three-quarters bigger on a side
   — with no change to the markup and nothing to maintain per game. The board
   spans every row of the left column; the turn, the actions, the controls and
   the hint stack down a fixed column on the right, and the last row is 1fr so
   any slack lands there rather than pushing the page taller.

   Held above 300px of height on purpose: below that the page is allowed to
   scroll (Standards.md §5) and the fixed-height board below takes over, and
   the two layouts would fight.
*/
@media (min-height: 300px) and (max-height: 520px) and (min-width: 520px) {
  .app {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(150px, 15rem);
    grid-template-rows: auto auto auto auto 1fr;
    column-gap: 12px;
    row-gap: 6px;
  }

  .topbar       { grid-column: 1 / -1; grid-row: 1; }
  .stage        { grid-column: 1; grid-row: 2 / -1; min-height: 0; }
  .boardbar     { grid-column: 2; grid-row: 2; }
  .boardactions { grid-column: 2; grid-row: 3; }
  .boardctl     { grid-column: 2; grid-row: 4; }
  .hint         { grid-column: 2; grid-row: 5; align-self: start; }

  /* The turn indicator is the one thing that must stay readable at a glance,
     so it keeps its size while the buttons give theirs up. */
  .boardbar {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }

  /* Two even columns rather than a wrapping row. Wrapped, four buttons of
     three different widths leave one stranded on a line of its own, which
     reads as a mistake; paired, they read as a keypad. */
  .boardctl {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
  }

  .boardctl .btn {
    width: 100%;
    padding: 0 8px;
  }

  .level {
    grid-column: 1 / -1;
  }

  .level__btn {
    flex: 1 1 0;
    padding: 0 6px;
  }
}

/* Narrow: the tray drops out rather than wrapping the row onto two lines and
   stealing the height back from the board. The counts are still in the move
   list and still announced, so nothing is lost that only exists here. */
@media (max-width: 420px) {
  .tray {
    display: none;
  }

  .boardctl {
    gap: 6px;
  }
}

/*
   Below 300px of viewport height the page may scroll — Standards.md §5. The
   board stops being the elastic row and takes a sensible fixed height, because
   a board squeezed into 120px is not a board.
*/
@media (max-height: 299px) {
  .board__frame {
    flex: 0 0 auto;
    height: 60vw;
    min-height: 220px;
  }

  .sheet {
    position: static;
    min-height: 220px;
  }
}

/* -------------------------------------------------------------------------- */
/* 10. Reduced motion, forced colours, more contrast, print                   */
/* -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .board__cell,
  .board__piece,
  .board__hint {
    transition: none !important;
  }

  .board__last {
    stroke-dasharray: none;
  }
}

/*
   Windows High Contrast discards the palette entirely. Every fill above
   becomes one of two system colours, so a disc and a ring would be two
   identical filled circles IF the shape were not carrying the signal.

   This is the case the shape vocabulary in sprites.js exists for, and the only
   thing needed here is to stop the browser flattening the pieces into the
   background and to keep the three markers apart by line style.
*/
@media (forced-colors: active) {
  .board__surface,
  .board__cell {
    forced-color-adjust: none;
    fill: Canvas;
    stroke: CanvasText;
  }

  .board__link,
  .board__route {
    forced-color-adjust: none;
    stroke: CanvasText;
  }

  /* Both become one colour, and that is fine: a straight rail with rungs and a
     wavy line with a head are still two different pictures. This is exactly
     what the shape was carrying the signal for. */
  .board__ladder,
  .board__snake {
    forced-color-adjust: none;
    stroke: CanvasText;
  }

  .board__snake-head {
    forced-color-adjust: none;
    fill: Canvas;
    stroke: CanvasText;
  }

  /* One side filled, the other hollow — so the two players stay apart even
     when both colours have become CanvasText. The shapes differ too; this is
     the belt as well as the braces. */
  .board__piece {
    forced-color-adjust: none;
    fill: Canvas;
    stroke: CanvasText;
    stroke-width: 4;
  }

  .board__piece.is-side-0 {
    fill: CanvasText;
    stroke: Canvas;
  }

  .board__focus {
    forced-color-adjust: none;
    stroke: Highlight;
    stroke-width: 5;
  }

  .board__last {
    forced-color-adjust: none;
    stroke: CanvasText;
  }

  .board__hint {
    forced-color-adjust: none;
    fill: Highlight;
  }

  .board__cell.is-origin {
    forced-color-adjust: none;
    stroke: Highlight;
    stroke-width: 4;
  }

  .sheet {
    border: 2px solid CanvasText;
    background: Canvas;
  }
}

@media (prefers-contrast: more) {
  :root {
    --board-line: rgba(255, 236, 190, 0.75);
    --board-cell-alt: rgba(255, 240, 205, 0.2);
  }

  .board__piece {
    stroke-width: 4;
  }
}

/* A board is worth printing — a child may want the position, or a teacher the
   rules sheet. Drop the chrome and let it fit the page. */
@media print {
  .boardctl,
  .tray,
  .beams {
    display: none !important;
  }

  .board__frame {
    height: auto;
  }

  .board__surface {
    fill: none;
    stroke: #000;
  }

  .board__cell {
    fill: none;
    stroke: #000;
  }

  /* An intersection board printed with its cells outlined is a Go board with a
     chessboard drawn over it. The lines are the board. */
  .board--intersect .board__cell {
    stroke: none;
  }

  .board__link,
  .board__dot,
  .board__border {
    stroke: #000;
    fill: #000;
  }

  .board__border {
    fill: none;
  }

  .board__piece {
    stroke: #000;
  }
}
