/*
 * One stylesheet. The app is a dozen pages and a grid; a build step and a
 * component library would both be more machinery than the thing they dress.
 *
 * Dark by default, because it is played in the evening on a phone.
 */

:root {
  --ink: #e8ecf4;
  --ink-dim: #97a2b8;
  --ground: #12161f;
  --panel: #1d2433;
  --line: #2f3a4f;
  --accent: #6ea8ff;
  --good: #57d9a3;
  --bad: #ff6b6b;
  --cell: #263049;
  /* The grid between cells. White, because it is drawn over ten different
     tints and has to read on all of them — the old #3d4a68 read on the one
     colour the board used to be and disappeared the moment there was colour
     behind it. Translucent rather than solid: at full strength a 1px lattice
     over ten colours is the loudest thing on the board, which is the problem
     the region borders had. */
  --cell-line: rgba(255, 255, 255, .45);
  --radius: 12px;

  /* One tint per region — ten of them, because the board is cut into one region
     per row and the widest board `PuzzleGenerator::SIZES` offers is 10. A
     narrower board leaves two or one of them unused, and which ones is that
     board's choice rather than the last in the list (below). In HSL because what
     separates them is the three numbers and hex hides all of them.
     Nothing else says where a region ends, so these have to be separable on
     their own.

     Separable is measured. `RegionPalette` reads these ten back out of this
     file, and `spec/assets/region_palette_spec.rb` fails if any two land closer
     than CIEDE2000 13, which is region-6 against region-7 — the closest pair
     here and so the bar the rest have to clear.

     Thirteen is not a distance a player can be asked to see, and it does not
     have to be: which region gets which tint is chosen per board by
     `PuzzleGenerator#recolour`, and it only has to separate the regions that
     share a border rather than all forty-five pairs. Measured over four hundred
     8×8s, the closest bordering pair on a recoloured board is 31 in the median
     and was never below 22.
     What these ten owe it is spread to choose from, which is what the bar above
     is now for — and a board narrower than ten leaves some of them out, so
     `--region-8` and `--region-9` are not "the ones the wide boards use". Any
     board may reach for any of the ten. STAR-36.

     8 and 9 were added for the wider boards by taking the two widest gaps left
     in the hue circle, and that is the wrong measure: hue degrees are not
     perceptual distance, and they are worth least in the greens. The widest gap
     left was 52 to 100, so region-8 went to hue 76 and landed 6.6 from
     region-3 in dE2000 — two greens a player could not tell apart, which is
     what was reported. region-9 at hue 330 had the same fault against magenta,
     at 9.3. Both are placed by distance now: region-8 is the pink and region-9
     the green, and every pair involving either is 14.7 or better.

     Placing a tint by hue is what fails here, because the lightnesses are
     capped (see below) and two dark greens 30 degrees apart are still one
     colour. A tint that has to sit near another's hue has to leave it in
     lightness or in saturation instead — which is why region-9 is a brighter,
     more saturated green than anything else on the board rather than a
     neighbouring shade of region-3.

     The lightnesses are ten distinct values, 19 through 40, in no order —
     lightness is what is left to a player who cannot separate two hues, and
     index order has nothing to do with which regions end up next to each other.
     Do not read that as a claim that this palette survives colour blindness: it
     does not. Measured, region-5 and region-7 are 3.9 apart for a deuteranope
     and region-0 and region-7 are 2.8 apart for a tritanope, both far below
     anything usable, and no arrangement of ten tints in the lightness band
     below fixes it. STAR-34.

     40% is the lightest in use, and the ceiling is the cross rather than the
     dog: the cross is drawn in --ink-dim, which is a mid grey, so contrast
     against it is worst for tints in the middle of the range rather than at
     either end. region-2 is the worst at 1.84:1 and predates this; nothing is
     allowed below it, and the spec pins that too. That ceiling is also what
     makes ten separable tints hard — it is the constraint to lift if an
     eleventh is ever wanted. STAR-33. */
  --region-0: hsl(  0, 46%, 30%);
  --region-1: hsl( 30, 60%, 19%);
  --region-2: hsl( 52, 36%, 36%);
  --region-3: hsl(100, 30%, 27%);
  --region-4: hsl(160, 40%, 33%);
  --region-5: hsl(200, 55%, 22%);
  --region-6: hsl(248, 34%, 40%);
  --region-7: hsl(300, 28%, 29%);
  --region-8: hsl(332, 60%, 39%);
  --region-9: hsl(128, 60%, 32%);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0 0 3rem;
  background: var(--ground);
  color: var(--ink);
  font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
}

main {
  max-width: 34rem;
  margin: 0 auto;
  padding: 1rem;
}

h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
h2 { font-size: 1.1rem; margin: 1.25rem 0 .5rem; }
h3 { font-size: .95rem; margin: 1rem 0 .35rem; color: var(--ink-dim); }
p { margin: .5rem 0; }

/* ── masthead ─────────────────────────────────────────────────────────────── */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  position: sticky;
  top: 0;
  z-index: 10;
}

.modes { display: flex; gap: .25rem; }

.mode {
  padding: .35rem .7rem;
  border-radius: 999px;
  color: var(--ink-dim);
  text-decoration: none;
  font-weight: 600;
  font-size: .9rem;
}

.mode.active { background: var(--cell); color: var(--ink); }

.account { display: flex; align-items: center; gap: .6rem; font-size: .85rem; }
.account .who { color: var(--ink-dim); text-decoration: none; }

.badge {
  background: var(--accent);
  color: #0b1020;
  padding: .2rem .5rem;
  border-radius: 999px;
  font-weight: 700;
  text-decoration: none;
  font-size: .78rem;
}

/* ── cards ────────────────────────────────────────────────────────────────── */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
  margin: 0 0 1rem;
}

.card.narrow { max-width: 24rem; margin: 3rem auto; }

.lede { color: var(--ink-dim); }
.note { color: var(--ink-dim); font-size: .85rem; }

.flash {
  padding: .6rem .8rem;
  border-radius: var(--radius);
  margin: 0 0 1rem;
  font-size: .9rem;
}
.flash.notice { background: rgba(87, 217, 163, .15); color: var(--good); }
.flash.alert  { background: rgba(255, 107, 107, .15); color: var(--bad); }

/* ── controls ─────────────────────────────────────────────────────────────── */

.button, button, input[type="submit"] {
  font: inherit;
  font-weight: 600;
  padding: .6rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--cell);
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}

.button.primary, input[type="submit"] { background: var(--accent); color: #0b1020; border-color: transparent; }
.button.small { padding: .35rem .7rem; font-size: .85rem; }
.button.danger { background: transparent; color: var(--bad); border-color: var(--bad); margin-top: 1rem; }

input[type="text"], input[type="email"] {
  font: inherit;
  padding: .6rem .75rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--ground);
  color: var(--ink);
  width: 100%;
}

input.code { font-size: 1.6rem; letter-spacing: .35em; text-align: center; }

.stack { display: flex; flex-direction: column; gap: .5rem; }
.stack label { font-size: .85rem; color: var(--ink-dim); }
.row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.row input { flex: 1 1 12rem; }
/* A checkbox and its words on one line. `.stack label` above puts labels in the
   dim caption colour, which is right for the caption above a field and wrong
   for a control the reader is meant to click. */
.check { display: flex; align-items: center; gap: .5rem; color: var(--ink); font-size: 1rem; }
.check input { flex: 0 0 auto; width: auto; }
form { margin: 0; }

/* ── the board ────────────────────────────────────────────────────────────── */

.board-card { padding: 1rem .75rem; }

.board-head { display: flex; align-items: baseline; justify-content: space-between; }
.board-actions { margin-top: 1rem; }
.counters { display: flex; gap: 1rem; align-items: baseline; }
.timer { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.mistake-count { color: var(--ink-dim); font-size: .85rem; }

/* Where the board would be, while it is put away. The same box the grid
   occupies — square, and the same width — so that resuming does not move
   anything on the page. */
.paused {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  aspect-ratio: 1;
  margin: 1rem auto;
  width: 100%;
  max-width: 26rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  text-align: center;
  padding: 1rem;
}

/* The board's rating, on the board page and on an archive row. Quiet on
   purpose: it is context for the day, not one of the numbers being played
   for, and it must not compete with the timer beside it. */
.difficulty { color: var(--ink-dim); font-size: .82rem; }

.board {
  display: grid;
  grid-template-columns: repeat(var(--board-size), 1fr);
  gap: 0;
  margin: 1rem auto;
  width: 100%;
  max-width: 26rem;
  background: var(--cell);
  border: 3px solid var(--ink);
  border-radius: 6px;
  overflow: hidden;
  /* Stops the browser waiting to see whether a tap is a double-tap-to-zoom,
     which would put ~300ms of lag on every mark on a board you are timed on. */
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

.cell {
  aspect-ratio: 1;
  border: 0;
  margin: 0;
  padding: 0;
  background: var(--cell);
  border-radius: 0;
  /* The grid, and the only line on the board. */
  box-shadow: inset -1px -1px 0 var(--cell-line), inset 1px 1px 0 var(--cell-line);
  display: flex;
  align-items: center;
  justify-content: center;
  /* The dog and the cross, sized against the cell rather than the screen. It
     used to be `min(6vw, 1.6rem)`, which was right while every board was eight
     cells across and leaves the glyph looking undersized at ten, because the
     cell shrinks with the width and the glyph did not. Both halves are the old
     numbers times eight, so an 8×8 is pixel-for-pixel what it was and the wider
     boards match it in proportion: 48/8 is the old 6vw, and 12.8/8 the old
     1.6rem cap that takes over once the board stops growing at its max-width. */
  font-size: min(calc(48vw / var(--board-size)), calc(12.8rem / var(--board-size)));
  line-height: 1;
  cursor: pointer;
  position: relative;
}

/* The regions, and the only thing that draws them. A region is where one tint
   stops and the next begins; there is no border around it. The class comes
   from the board partial. */
.cell.region-0 { background: var(--region-0); }
.cell.region-1 { background: var(--region-1); }
.cell.region-2 { background: var(--region-2); }
.cell.region-3 { background: var(--region-3); }
.cell.region-4 { background: var(--region-4); }
.cell.region-5 { background: var(--region-5); }
.cell.region-6 { background: var(--region-6); }
.cell.region-7 { background: var(--region-7); }
.cell.region-8 { background: var(--region-8); }
.cell.region-9 { background: var(--region-9); }

.cell.has-cross::after {
  content: "×";
  color: var(--ink-dim);
  font-size: 1.1em;
  font-weight: 400;
}

.cell.has-dog::after {
  content: "🐕";
  font-size: .85em;
}

/* A mistake, marked with a ring rather than the wash alone. The wash was enough
   when every cell was the same colour behind it; over ten tints it is not —
   on the red region a wrong dog looked exactly like a right one. The ring is
   inside the cell, so it sits clear of the region border outside it. */
.cell.has-dog.is-wrong {
  background: rgba(255, 107, 107, .28);
  box-shadow: inset 0 0 0 3px var(--bad);
}

.cell.has-dog.is-wrong::after { content: "🐕"; }

.board.static .cell { cursor: default; }

/* ── results and scoreboards ──────────────────────────────────────────────── */

.result { display: grid; grid-template-columns: auto 1fr; gap: .25rem 1rem; margin: 1rem 0; }
.result dt { color: var(--ink-dim); font-size: .9rem; }
.result dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.result dd.big { font-size: 1.5rem; color: var(--accent); }

.done { font-size: 1.05rem; }
.score { font-size: 1.05rem; }

.standings, .wins, .groups, .archive, .pending { list-style: none; margin: .5rem 0; padding: 0; }

.standings li, .wins li, .archive-row {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .45rem .5rem;
  border-radius: 8px;
}

.standings li:nth-child(odd), .wins li:nth-child(odd), .archive-row:nth-child(odd) {
  background: rgba(255, 255, 255, .03);
}

.standings li.winner { background: rgba(87, 217, 163, .14); }
.standings li.winner .who::before { content: "🏆 "; }

.standings .who, .wins .who, .archive .day { flex: 1 1 auto; font-weight: 600; }
.standings .detail, .archive .outcome { color: var(--ink-dim); font-size: .82rem; }
.standings .score, .wins .count, .archive-score {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  min-width: 3.5rem;
  text-align: right;
}

/* Three columns of wins rather than one, so they have to be narrower than a
   time is. Their headings sit outside the list, so that the list's striping
   still starts on the first player. */
.wins .count { min-width: 2.5rem; }

.wins-head {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .45rem .5rem;
  color: var(--ink-dim);
  font-size: .75rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.wins-head .who { flex: 1 1 auto; }
.wins-head .count { min-width: 2.5rem; text-align: right; }

.groups li { padding: .5rem 0; border-bottom: 1px solid var(--line); }
.groups li:last-child { border-bottom: 0; }
.group-name { color: var(--ink); font-weight: 600; text-decoration: none; }

.invitation {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .75rem;
  margin: .5rem 0;
}

.rules ul { margin: .5rem 0; padding-left: 1.1rem; color: var(--ink-dim); font-size: .9rem; }
.rules li { margin: .3rem 0; }

.day { margin-bottom: 1rem; }

/* Two striped lists back to back read as one list, so today's rows get air
   when they follow a group's win totals on the front page. */
.wins + .day { margin-top: 1rem; }
