/* The binary board, the start/pause gate, the style control and the rules list.
   Every colour comes from tokens.css.

   The page's furniture — header, gate, status bar, result panel, prev/next — is
   the acrostic's, deliberately: the two games are one product and a player who
   knows where the pause button is on one should not have to look for it on the
   other. What is genuinely different is the board, and the board is where all
   the new work is.

   The acrostic's board is light tiles on a near-black panel, because a REVEALED
   letter has to land against its blank neighbours. A binary reveals nothing —
   it is filled in — so it is drawn as what it actually is: a printed grid. Paper
   ground, hairlines between the squares, a firmer rule around the outside. */

/* The square is sized from the viewport AND from the grid size, because what has
   to fit is n squares across and no breakpoint knows what n is. The 10x10 is the
   binding case: at 390px it lands near 33px, still a comfortable tap target, and
   it degrades smoothly instead of overflowing.

   --bin-w is declared HERE, on the grid, and not on :root — which is where it
   started and where it was wrong. `--bin-n` is set per puzzle as an inline style
   on this same element (views/binary.ejs), so a :root rule computing
   `calc(... / var(--bin-n))` resolves against :root's own fallback of 8 whatever
   the puzzle is. Every 10x10 came out sized for eight columns and ran off the
   right of a phone. Custom properties resolve per element: the divisor is only
   correct on an element that can see the real value.

   Every one of these sums is `W - chrome - n*1px`, over n. The chrome is written
   out because it is a real list of real boxes, and the list grew: the board no
   longer sits directly on the page, it sits inside `.playfield` (site.css), the
   panel the theme gave the grid and its style control to share. That panel's
   1px border and 20px padding either side are 42px the board used to have and
   does not, and the sums here were not told — which is how an 8x8 came to hang
   off the right of a phone with its own panel clipping it. Anything that adds a
   box between the viewport and `.grid` has to be added here too.

   At full width: the wrap's 20px margins, the playfield's border and padding,
   the board's border (2x2) and padding (2x10) — 106px — plus the grid's own
   border and ONE HAIRLINE PER GAP, of which there are n-1: 107px + n*1px. */
.grid{
  --bin-n:8;      /* the inline style on #grid overrides this on every puzzle */
  --bin-w:clamp(26px, calc((min(100vw, 880px) - 107px - var(--bin-n) * 1px) / var(--bin-n)), 46px);
}
/* The nine-block special is eighteen squares across, which is where a floor of
   26px stops being a comfortable tap target and starts being a board that runs
   off the side of a phone. It gets its own clamp rather than a smaller floor for
   everybody: 26px is right for the sizes it was measured against, and the point
   of this board is comparing a row against the rows above it — something a
   square you have to scroll to cannot do at all. So the square gives way and the
   grid always fits.

   The subtrahend is the same 107px the rule above subtracts — the same boxes
   stand between the viewport and this grid — written out rather than rounded to
   a constant, because a fixed number is close enough at 8 columns and eleven
   pixels short at 18, which is exactly how this once shipped overflowing a
   phone. `107px + n*1px` stays right at any width.

   880px is the wrap's own max-width, so past that the board stops growing with
   the window because its column has. The 36px ceiling is what it settles at:
   eighteen of those is a 691px board, comfortably inside the 798px the playfield
   leaves it and a sixth larger than the 620px cap it had — which read as small
   for a board that is nine puzzles at once. */
.grid[data-variant="nine"]{
  --bin-w:clamp(20px, calc((min(100vw, 880px) - 107px - var(--bin-n) * 1px) / var(--bin-n)), 36px);
}

/* progress hairline pinned to the very top, above the masthead */
#line{
  position:fixed;top:0;left:0;height:3px;width:0;background:var(--accent);
  z-index:9;transition:width .35s var(--ease);
}

/* ---------------- header ---------------- */
/* Lifted from acrostic.css unchanged. Same clock, same pause control, same
   dateline — see the note there on why the clock is set in the serif. */
.wrap header{
  padding:26px 0 14px;margin-bottom:20px;border-bottom:1px solid var(--rule);
  display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;
}
.wrap h1{
  font-family:var(--display);font-weight:700;font-size:clamp(26px,4.4vw,36px);
  letter-spacing:-.035em;line-height:1.05;margin:0;
}
.wrap h1 em{font-style:normal;color:var(--accent-ink)}
.dateline{
  flex-basis:100%;margin:6px 0 0;font-size:12px;letter-spacing:.01em;color:var(--dim);
}

/* ---------------- the gate ----------------
   The puzzle does not show itself. On arrival the board, the style control, the
   rules and the status bar are all out of the document and this panel stands in
   their place; the clock starts on the button and not before. Pause is the same
   panel and the same mechanism.

   It matters at least as much here as it does for the acrostic. A binary grid is
   exactly the sort of thing somebody would sit and study with the clock off —
   the whole puzzle is on screen at once, unlike a sentence that has to be
   uncovered — so a pause that merely dimmed it would be no pause at all. */
.gate{
  display:none;
  background:var(--card);border:1px solid var(--rule-firm);border-radius:var(--radius-card);
  border-top:4px solid var(--accent);
  min-height:300px;padding:44px 26px;
  text-align:center;
}
body.is-gated .gate{display:grid;place-items:center}
/* everything the gate stands in for */
body.is-gated .playfield,
body.is-gated .board,
body.is-gated .styles,
body.is-gated .rules,
body.is-gated .note,
body.is-gated .entry{display:none}
/* the status bar is out of the flow and the page owes back its height (see
   `.entry`); while it is gone, the debt is cancelled */
body.is-gated{padding-bottom:56px}

.gate-in{max-width:420px}
.gate-kicker{
  font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--accent-ink);
}
.gate h2{
  font-family:var(--display);font-weight:700;font-size:clamp(24px,4vw,32px);
  letter-spacing:-.03em;line-height:1.1;margin:10px 0 0;
}
.gate p{margin:12px 0 0;font-size:15px;line-height:1.6;color:var(--slate)}
#gateBtn{margin-top:22px;padding:14px 30px;font-size:14px}
.gate-figures{
  margin-top:16px;font-size:11px;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;color:var(--dim);
}

/* ---------------- the rules, on the gate ----------------
   The same list that lives under the board, shown again on the start panel —
   because the panel is where the clock is NOT running. The gate exists so a
   player is not charged for the time before they start, and reading the rules
   was the one piece of that time the page still made them pay for.

   Ranged left inside a centred panel: a numbered list is read down its left
   edge, and centring one turns it into a poster. */
.gate-rules{
  margin:22px auto 0;text-align:left;
  border-top:1px solid var(--rule);padding-top:16px;
}
.gate-rules-head{
  font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--dim);text-align:center;margin-bottom:12px;
}
.gate-rules ol{
  margin:0;padding:0 0 0 20px;
  display:grid;gap:7px;
  font-size:13.5px;line-height:1.5;color:var(--slate);
}
.gate-rules li::marker{color:var(--accent-ink);font-weight:800}
.gate-rules b{color:var(--ink);font-weight:800}

/* ---------------- the board ---------------- */
/* The panel is only as wide as the grid inside it — a 6x6 in an 880px box would
   be a small square marooned in a large one. `width:fit-content` and auto
   margins are what keep every size centred and tight. */
.board{
  width:fit-content;max-width:100%;margin:0 auto;
  background:var(--bin-cell);
  border:2px solid var(--bin-edge);border-radius:var(--radius-card);
  padding:10px;
  box-shadow:0 20px 44px -34px var(--shadow-board);
}
/* Nothing on this board can be selected. Unlike the acrostic there is no hidden
   letter to leak — an empty square is an empty node — so this is purely about
   the interaction: the whole game is clicking squares, and a double-click or a
   long press would otherwise paint a selection across half the grid. */
.board{-webkit-user-select:none;user-select:none}

/* One square per column, sized by --bin-w. The 1px gap over a --bin-line ground
   IS the grid: exactly one hairline between any two squares and none doubled,
   the same trick the homepage cards use for their dividers. */
.grid{
  display:grid;
  grid-template-columns:repeat(var(--bin-n), var(--bin-w));
  gap:1px;background:var(--bin-line);
  border:1px solid var(--bin-line);
}
.bcell{
  width:var(--bin-w);height:var(--bin-w);
  background:var(--bin-cell);
  display:grid;place-items:center;
  /* 600, not 900: this is the PLAYER's digit, and the weight is half of what
     tells it apart from the puzzle's own. See `.bcell.given` below. */
  font-family:var(--serif);font-weight:600;
  font-size:calc(var(--bin-w) * .52);line-height:1;
  font-variation-settings:"SOFT" 70,"WONK" 1;
  font-variant-numeric:tabular-nums;
  color:var(--bin-ink);
  cursor:pointer;
  transition:background .12s var(--ease),color .12s var(--ease);
}
/* ---------------- the block rules, on the special only ----------------
   Nine puzzles in a square need a line that says where one ends and the next
   begins, and it has to be a DIFFERENT line from the hairline between two
   squares — that is the whole readability of the board.

   It is a border on the leading square of each block rather than anything drawn
   over the grid, for one reason: `box-sizing:border-box` means the border eats
   its width out of the square instead of moving the column, so the grid keeps
   its geometry exactly and the rule lands in the gutter the gap already draws.
   The two obvious alternatives both fail on this board — a wider `gap` cannot
   vary per track, and an inset `box-shadow` is already spoken for twice over,
   by the focus ring and by the line flash.

   --bin-edge is the board's own outer rule, which is what these are: the inside
   edges of nine boards.

   Width is the smaller half of what separates a block rule from a hairline, and
   assuming otherwise is what shipped a dark theme nobody could find the blocks
   in. 3px of a colour one step from the hairline's is a THICK HAIRLINE, not a
   different kind of line: light read fine at a 1.55:1 step between --bin-edge
   and --bin-line, and dark shipped at 1.25:1 and disappeared — worst in the
   coloured-cells style, where saturated squares crowd a grey rule from both
   sides. The colours carry it now (dark is at 2.23:1) and
   npm run check:contrast holds that pair in both themes. */
.bcell.blk-l{border-left:3px solid var(--bin-edge)}
.bcell.blk-t{border-top:3px solid var(--bin-edge)}

/* A given is the puzzle's own digit: full ink, full weight, and not clickable.
   Weight and value together are what carry "who put this here" in all THREE
   styles — colour is already spoken for, it says which digit. Without this the
   coloured styles have no way to show it at all, since a 0 the player placed and
   a 0 the puzzle gave are the same hue by definition. */
.bcell.given{
  color:var(--bin-given);
  font-weight:900;
  cursor:default;
}
/* An empty square invites a click; a given never does. */
.bcell:not(.given):hover{background:var(--accent-soft)}
.bcell:not(.given):active{background:var(--accent-soft)}
/* The keyboard's position on the board, which the mouse never shows. It is drawn
   INSIDE the square with an inset shadow rather than with an outline, because an
   outline on a 1px-gapped grid overlaps its neighbours. */
.bcell:focus-visible{
  outline:none;
  box-shadow:inset 0 0 0 3px var(--accent);
}
/* the reveal, on give-up: the same spring the acrostic's tiles land with */
.bcell.pop{animation:bpop .46s var(--spring)}
@keyframes bpop{
  0%{transform:scale(1);background:var(--accent)}
  40%{transform:scale(1.14);background:var(--accent)}
  100%{transform:scale(1)}
}

/* ---------------- the line flash ----------------
   A row or a column that has just been FILLED says once whether it is right,
   and then stops saying it. The ring blooms and goes, the tint comes up and
   goes, and the square is left exactly as it was found — nothing on this board
   is permanently marked, because re-reading the board IS the game and a grid
   carrying twenty little verdicts is a grid you can no longer read.

   The ring is the signal and the tint is support, not the other way round. A
   3px inset ring is saturated colour that never lands under the figure, while
   the tint has to stay pale enough to keep the digit legible while it is up —
   every digit, in all three styles, is measured against both tints by
   npm run check:contrast.

   The coloured styles come free: an animation beats a normal declaration, so
   these keyframes own the square's background for as long as the flash runs and
   hand it straight back afterwards.

   --fd is set per square by binary.js, so the flash travels ALONG the line the
   way an eye reads it, rather than blinking ten squares at once. */
.bcell.flash-ok{--bflash:var(--bin-flash-ok);--bflash-tint:var(--bin-flash-ok-soft)}
.bcell.flash-no{--bflash:var(--bin-flash-no);--bflash-tint:var(--bin-flash-no-soft)}
.bcell.flash-ok,.bcell.flash-no{animation:bline .62s var(--ease) var(--fd,0s) both}
@keyframes bline{
  0%{box-shadow:inset 0 0 0 0 var(--bflash)}
  22%{box-shadow:inset 0 0 0 3px var(--bflash);background:var(--bflash-tint)}
  62%{box-shadow:inset 0 0 0 3px var(--bflash);background:var(--bflash-tint)}
  100%{box-shadow:inset 0 0 0 0 var(--bflash)}
}
/* The verdict is information, so it still has to arrive — what goes is the
   travel: no stagger, one shorter pulse, the whole line at once. */
@media (prefers-reduced-motion:reduce){
  .bcell.flash-ok,.bcell.flash-no{animation-duration:.4s;animation-delay:0s}
}

/* ---------------- style: coloured digits ----------------
   The squares stay paper and the digits take the colour. `[data-v]` is written
   by binary.js at the moment a digit lands, so these selectors need no extra
   class and an empty square matches nothing. */
:root[data-bstyle="ink"] .bcell[data-v="0"]{color:var(--bin-zero-ink-soft)}
:root[data-bstyle="ink"] .bcell[data-v="1"]{color:var(--bin-one-ink-soft)}
:root[data-bstyle="ink"] .bcell.given[data-v="0"]{color:var(--bin-zero-ink)}
:root[data-bstyle="ink"] .bcell.given[data-v="1"]{color:var(--bin-one-ink)}

/* ---------------- style: coloured squares ----------------
   The square takes the colour and the digit is set on it. A given square is the
   full-strength fill and one the player placed is the soft one, so the board
   reads at a glance as what was handed over and what was worked out — which is
   the distinction the reference screenshots never had to make. */
:root[data-bstyle="fill"] .bcell[data-v="0"]{background:var(--bin-zero-cell-soft);color:var(--bin-zero-on)}
:root[data-bstyle="fill"] .bcell[data-v="1"]{background:var(--bin-one-cell-soft);color:var(--bin-one-on)}
:root[data-bstyle="fill"] .bcell.given[data-v="0"]{background:var(--bin-zero-cell);color:var(--bin-zero-on)}
:root[data-bstyle="fill"] .bcell.given[data-v="1"]{background:var(--bin-one-cell);color:var(--bin-one-on)}
/* the hover tint would otherwise wash out the fill it is sitting on */
:root[data-bstyle="fill"] .bcell:not(.given):hover[data-v]{filter:brightness(.95)}

/* ---------------- the style control ----------------
   A board setting, so it sits with the board rather than in the masthead, and it
   goes away with the board behind the gate. The choice is global and remembered
   across every puzzle; the pre-paint half of that is inline in head.ejs. */
.styles{
  display:flex;align-items:center;justify-content:center;gap:6px;flex-wrap:wrap;
  margin:16px auto 0;
}
.styles-label{
  font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--dim);margin-right:4px;
}
.styles button{
  font-family:var(--ui);font-size:11px;font-weight:800;letter-spacing:.06em;
  padding:7px 12px;cursor:pointer;
  color:var(--slate);background:transparent;
  border:1px solid var(--rule-firm);border-radius:var(--radius-card);
  transition:color .15s,border-color .15s,background .15s;
}
.styles button:hover{color:var(--ink);border-color:var(--ink)}
.styles button.is-on{
  color:var(--accent-ink);border-color:var(--accent-deep);background:var(--accent-soft);
}

/* ---------------- the status bar ----------------
   The acrostic's `.entry`, minus the input field. The bar stays because what it
   holds — the running status line, and then the result panel that replaces it —
   is the same in both games, and because a result panel that appeared halfway up
   a scrolled page would be missed. */
.entry{
  position:fixed;left:0;right:0;bottom:0;z-index:6;
  margin:0;
  padding:12px 14px calc(12px + env(safe-area-inset-bottom,0px));
  background:var(--page);
  border-top:1px solid var(--rule-firm);
}
.entry .status,.entry .result{max-width:520px;margin-left:auto;margin-right:auto}
/* the bar is out of the flow, so the page owes back the height it would have
   occupied — without this it covers the rules list and the give-up button */
body{padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))}
/* the result is a panel, not a bar: `.done` drops the block back into the flow,
   where it takes the status line's place — and the debt is paid off with it */
.entry.done{
  position:static;max-width:520px;margin:26px auto 0;padding:0;
  background:none;border-top:0;
}
body:has(.entry.done){padding-bottom:56px}
.status{
  min-height:19px;font-size:12.5px;color:var(--slate);text-align:center;
  letter-spacing:.01em;line-height:1.5;
}

/* ---------------- the rules ---------------- */
/* The four rules stay on the page for the whole game, unlike the acrostic's
   clues which the player works through. They are the puzzle's entire
   instruction set and a player checks back against them constantly — a rule you
   have to scroll away from is a rule you will misremember. */
.rules{width:100%;max-width:560px;margin:30px auto 0}
.rules ol{
  margin:0;padding:0 0 0 22px;
  display:grid;gap:8px;
  font-size:14.5px;line-height:1.5;color:var(--slate);
}
.rules li::marker{color:var(--accent-ink);font-weight:800}
.rules b{color:var(--ink);font-weight:800}

/* Clear and give up: the two ways out of a puzzle that has gone wrong, one of
   which you can come back from. Both are deliberately quiet and both are
   two-step — the whole board is a click target, so one stray tap must not empty
   a grid or end a puzzle the player is still working on. They are the same
   control drawn twice, which is also what says they carry the same weight. */
.acts{display:flex;justify-content:center;gap:10px;flex-wrap:wrap;padding:22px 0 0}
.acts.gone{display:none}
#clear,#giveup{
  background:transparent;border:1px solid var(--rule-firm);border-radius:var(--radius-card);
  padding:9px 18px;cursor:pointer;
  font-family:var(--ui);font-size:11px;font-weight:800;
  letter-spacing:.1em;text-transform:uppercase;color:var(--dim);
  transition:color .15s,border-color .15s,background .15s;
}
#clear:hover,#giveup:hover{color:var(--ink);border-color:var(--ink)}
#clear.armed,#giveup.armed{
  color:var(--accent-ink);border-color:var(--accent-deep);background:var(--accent-soft);
  box-shadow:0 0 14px 1px var(--accent-glow);
}

.note{
  max-width:560px;margin:24px auto 0;padding:16px 18px;
  background:var(--brand-soft);border-left:4px solid var(--brand);
  border-radius:var(--radius-card);
  font-size:13px;line-height:1.7;color:var(--slate);
}
.note b{color:var(--ink);font-weight:800}

/* ---------------- result (replaces the status line) ---------------- */
.result{display:none;text-align:center}
.entry.done .status{display:none}
.entry.done .result{
  display:block;padding:26px 20px;
  background:var(--card);border:1px solid var(--rule);border-radius:var(--radius-card);
  border-top:3px solid var(--accent);
  animation:rise .4s var(--ease);
}
@keyframes rise{from{transform:translateY(8px);opacity:0}to{transform:none;opacity:1}}
.result .eyebrow{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-ink);font-weight:800}
.result .final{
  font-family:var(--serif);font-weight:900;font-size:56px;line-height:1.05;
  font-variation-settings:"SOFT" 80,"WONK" 1;
  font-variant-numeric:tabular-nums;margin:8px 0 0;letter-spacing:-.02em;
}
.result .flabel{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin-top:4px}
.result button{margin-top:20px;width:100%}

/* What the puzzle was. The acrostic's card at this spot is a REVEAL — it names
   the sentence — so it is styled as one. This one is a summary of something the
   header showed all along, so it borrows the same shape and none of the drama. */
.source{
  margin-top:22px;padding-top:18px;border-top:1px solid var(--rule);
  text-align:left;
}
.src-label{
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--accent-ink);font-weight:800;
}
.src-name{
  font-family:var(--serif);font-weight:900;font-size:19px;line-height:1.25;
  font-variation-settings:"SOFT" 70,"WONK" 1;
  color:var(--ink);margin-top:6px;
}
.src-meta{font-size:12.5px;color:var(--dim);margin-top:4px;line-height:1.5}
.src-note{font-size:13.5px;line-height:1.65;color:var(--slate);margin:10px 0 0}
.src-note code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12.5px;color:var(--accent-ink);
}

@media (max-width:680px){
  /* The same sum with this width's smaller boxes: the wrap pads 14 either side
     (28), the playfield 14 plus its border (30), the board's border and its
     6px padding (16), the grid's border (2) — 76px — less the hairline the n*1px
     double-counts, so 75px + n*1px. On the grid, not :root — see the note up
     top. */
  .grid{
    --bin-w:clamp(22px, calc((100vw - 75px - var(--bin-n) * 1px) / var(--bin-n)), 44px);
  }
  /* The floor is 12px rather than 14: at 320px — the narrowest screen worth
     serving — the formula asks for 12.6, and a floor above what the sum asks for
     is a floor that overflows instead of one that protects anything. The digit
     rides down with the square and stays legible because it is a 0 or a 1, with
     no other glyph on this board to confuse it with. */
  .grid[data-variant="nine"]{
    --bin-w:clamp(12px, calc((100vw - 75px - var(--bin-n) * 1px) / var(--bin-n)), 36px);
  }
  /* A step down with the square. 3px of a 15px cell is a fifth of it, which
     starts eating the digit; 2px still reads as a bar against the hairline. */
  .bcell.blk-l{border-left-width:2px}
  .bcell.blk-t{border-top-width:2px}
  .board{padding:6px}
  .wrap header{padding-top:20px}
  .gate{min-height:240px;padding:34px 18px}
  .gate-rules ol{font-size:13px}
  .rules ol{font-size:13.5px}
  /* the row wraps to two lines before either button gives up its label */
  .acts{gap:8px}
  #clear,#giveup{padding:9px 14px;letter-spacing:.08em}
  .styles button{padding:6px 10px;font-size:10px}
  .styles-label{width:100%;text-align:center;margin:0 0 2px}
  .result .final{font-size:44px}
  .status{font-size:12px}
}
