/* Figure.
 *
 * The grid is the whole screen and everything else gets out of its way. The one piece of decoration
 * that is not decoration is the heavier line every five cells: counting to fourteen along an
 * unmarked row is the most annoying thing about a badly drawn nonogram, and the five-block rule is
 * how paper has always solved it.
 *
 * Motion budget, as in every game in this family: transforms and opacity only, nothing loops,
 * everything bounded, all of it turn-off-able. The one real animation is the reveal at the end,
 * because that is the reward and the rest of the game is in service of it.
 */

:root {
  --ink: #1d2430;
  --ink-soft: #5b6676;
  --ink-faint: #8a94a3;
  --page: #eef1f5;
  --card: #ffffff;
  --line: #d6dbe3;

  --cell-blank: #e9edf2;
  --cell-hover: #dde3ea;
  --cell-fill: #2b3442;
  --cell-line: #ccd3dc;
  --cell-major: #9aa4b2;
  --cross: #97a1af;

  --done: #9aa4b2;
  --broken: #d93a2b;
  --good: #2f855a;
  --focus: #2b6cb0;

  --radius: 10px;
  --t: 160ms;
  --ease: cubic-bezier(.22, .8, .3, 1);

  --cell: 26px;
  --gutter-row: 80px;
  --gutter-col: 80px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e7ecf3;
    --ink-soft: #a3adbb;
    --ink-faint: #737e8d;
    --page: #14181f;
    --card: #1c222b;
    --line: #2b323d;

    --cell-blank: #29313c;
    --cell-hover: #333c49;
    --cell-fill: #e2e8f0;
    --cell-line: #202731;
    --cell-major: #55607048;
    --cross: #6b7686;

    --done: #5d6875;
    --broken: #f16452;
    --good: #56b98a;
    --focus: #63a4e8;
  }
}

html[data-motion="fast"] { --t: 80ms; }
html[data-motion="instant"] { --t: 0ms; }

/* Ties the tier rules above on specificity and comes later, so it wins - except where the player
   has explicitly asked for Full, which is the one case it should lose. */
@media (prefers-reduced-motion: reduce) {
  html:not([data-motion="full"]) { --t: 0ms; }
}

* { box-sizing: border-box; }

/* The overlays are display:grid, which beats the browser default that makes [hidden] work. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 15px/1.5 "Segoe UI", system-ui, -apple-system, "Helvetica Neue", sans-serif;
  -webkit-text-size-adjust: 100%;
}

/* One screen, always. The frame around the board is the only stretchy row: it takes whatever the
   header, the bar and the two lines underneath leave, and the board sizes its cells to that - so
   the clues, the grid, the clock and the buttons are all in view at once, on a phone included. */
#app {
  max-width: 1120px;
  height: 100vh;
  height: 100dvh;
  margin: 0 auto;
  padding:
    max(clamp(8px, 2.5vw, 26px), env(safe-area-inset-top))
    max(clamp(8px, 2.5vw, 22px), env(safe-area-inset-right))
    max(clamp(8px, 2vw, 20px), env(safe-area-inset-bottom))
    max(clamp(8px, 2.5vw, 22px), env(safe-area-inset-left));
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  gap: clamp(8px, 1.8vw, 16px);
  min-width: 0;
}

#app > * { min-width: 0; }

/* --- Header ---------------------------------------------------------------------------------- */

.head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 10px 16px;
}

h1 {
  margin: 0;
  font-size: clamp(24px, 4.4vw, 34px);
  letter-spacing: -.02em;
  font-weight: 650;
}

.tagline { margin: 2px 0 0; color: var(--ink-soft); font-size: 13px; }

.actions { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }

/* Share sits in the bar once the picture is done, dressed as one of these. */
.actions button,
.bar .share {
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink-soft);
  font: 13px/1 inherit;
  cursor: pointer;
  transition: border-color var(--t) var(--ease), color var(--t) var(--ease);
}

.actions button:hover,
.bar .share:hover { border-color: var(--ink-faint); color: var(--ink); }
.actions button[aria-pressed="true"] { border-color: var(--focus); color: var(--ink); }
#help { width: 34px; padding: 7px 0; text-align: center; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* --- The panel ------------------------------------------------------------------------------- */

.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) + 4px);
  padding: clamp(6px, 1.6vw, 18px);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: clamp(6px, 1.4vw, 12px);
  min-height: 0;
}

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-variant-numeric: tabular-nums;
}

.which {
  flex: 1 1 0;
  min-width: 0;
  font-size: 13px;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bar .clock { flex: 1 1 0; text-align: right; }

/* A finished picture has nothing left to fill or cross, so the switch steps aside and Share takes
   its place - the same spot, so sending the result costs the board no height on a phone. */
.bar .share { flex: none; }

/* Fill | Cross. Both halves always showing, the live one solid, so which one a tap will do is never
   a guess - on a phone this is the only way to cross a cell out. */
.mode {
  display: flex;
  flex: none;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--cell-blank);
  font: 600 14px/1 inherit;
  cursor: pointer;
}

.mode span {
  padding: 8px 14px;
  border-radius: 7px;
  color: var(--ink-soft);
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease);
}

.mode[aria-pressed="false"] .mode-fill,
.mode[aria-pressed="true"] .mode-cross { background: var(--cell-fill); color: var(--card); }

.clock {
  font: 600 19px/1 ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.frame {
  display: flex;
  width: 100%;
  max-width: 100%;
  min-height: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

/* --- The board ------------------------------------------------------------------------------- */

.board {
  display: grid;
  grid-template-columns: var(--gutter-row) max-content;
  grid-template-rows: var(--gutter-col) max-content;
  width: max-content;
  height: max-content;
  /* Auto margins centre it, and unlike centring the frame's content they never push an oversized
     board out past the top or left edge where scrolling cannot reach it. */
  margin: auto;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.corner { grid-area: 1 / 1; }

.col-clues {
  grid-area: 1 / 2;
  display: grid;
  align-items: end;
}

.row-clues {
  grid-area: 2 / 1;
  display: grid;
  justify-items: end;
}

.cells { grid-area: 2 / 2; display: grid; }

/* A clue: numbers running down a column or along a row, packed against the grid. */
.clue {
  display: flex;
  gap: calc(var(--cell) * .12);
  font: 600 var(--clue, 12px)/1 ui-monospace, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  transition: color var(--t) var(--ease), opacity var(--t) var(--ease);
}

.col-clues .clue {
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-bottom: calc(var(--cell) * .18);
  height: 100%;
}

.row-clues .clue {
  flex-direction: row;
  align-items: center;
  justify-content: flex-end;
  padding-right: calc(var(--cell) * .3);
  width: 100%;
}

/* A line that matches its clue steps back. It has nothing left to say and should stop taking up
   attention the player needs elsewhere. */
.clue.done { color: var(--done); opacity: .45; }

/* A line that can no longer be satisfied. It does not say which cell is wrong, only that one is. */
.clue.broken { color: var(--broken); }

.cell {
  width: var(--cell);
  height: var(--cell);
  background: var(--cell-blank);
  box-shadow: inset 0 0 0 1px var(--cell-line);
  cursor: pointer;
  position: relative;
  transition: background-color calc(var(--t) * .5) var(--ease);
}

@media (hover: hover) {
  .cell:not(.on):hover { background-color: var(--cell-hover); }
}

.cell.on { background-color: var(--cell-fill); }

/* The cross. Drawn rather than a character, so it is the same on every machine and scales with
   the cell instead of with the font. */
.cell.off::before,
.cell.off::after {
  content: "";
  position: absolute;
  inset: 34%;
  border-top: 2px solid var(--cross);
  top: 50%;
}

.cell.off::before { transform: rotate(45deg); }
.cell.off::after { transform: rotate(-45deg); }

/* Every fifth line heavier. Counting to fourteen along an unmarked row is the single most annoying
   thing about a badly drawn nonogram, and this is how paper has always fixed it. */
.cell.major-left { box-shadow: inset 0 0 0 1px var(--cell-line), inset 2px 0 0 0 var(--cell-major); }
.cell.major-top { box-shadow: inset 0 0 0 1px var(--cell-line), inset 0 2px 0 0 var(--cell-major); }
.cell.major-left.major-top {
  box-shadow: inset 0 0 0 1px var(--cell-line), inset 2px 0 0 0 var(--cell-major), inset 0 2px 0 0 var(--cell-major);
}

/* Mid-drag. The rules are not told until the pointer comes up - one drag is one move - so the
   preview has to look exactly like the real thing. */
.cell.preview-on { background-color: var(--cell-fill); }
.cell.preview-off::before,
.cell.preview-off::after {
  content: "";
  position: absolute;
  inset: 34%;
  border-top: 2px solid var(--cross);
  top: 50%;
}
.cell.preview-off::before { transform: rotate(45deg); }
.cell.preview-off::after { transform: rotate(-45deg); }
.cell.preview-clear { background-color: var(--cell-blank); }
.cell.preview-clear::before, .cell.preview-clear::after { content: none; }

/* --- The reveal ------------------------------------------------------------------------------ */

/* The one real animation in the game. The crosses go, the gridlines fade, and the filled cells lose
   their borders and run together - so the picture stops being a grid and becomes a thing. */
.board.revealed .cell {
  box-shadow: none;
  transition: background-color calc(var(--t) * 3) var(--ease), box-shadow calc(var(--t) * 3) var(--ease);
}

.board.revealed .cell:not(.on) { background-color: transparent; }
.board.revealed .cell.off::before,
.board.revealed .cell.off::after { opacity: 0; transition: opacity var(--t) var(--ease); }
.board.revealed .clue { opacity: .2; }

/* --- Below the board ------------------------------------------------------------------------- */

.status {
  margin: 0;
  min-height: 1.4em;
  line-height: 1.4;
  text-align: center;
  font-size: 14px;
  color: var(--ink-soft);
}

.status[data-tone="best"] { color: var(--good); font-weight: 600; }
.status[data-tone="first"],
.status[data-tone="done"] { color: var(--ink); font-weight: 600; }

.foot {
  margin: 0;
  text-align: center;
  font-size: 12px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* What happened to the result takes the progress line's place for a moment rather than adding a
   line under it, which on a phone would come out of the board. */
.share-note { color: var(--ink-soft); }

/* Only when nothing could copy it: the text itself, selected, to copy by hand. */
.share-text {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: 4px auto 0;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  font: 12px/1.4 ui-monospace, Consolas, monospace;
  resize: none;
}

/* --- Overlays -------------------------------------------------------------------------------- */

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(12, 16, 22, .55);
  display: flex;
  padding: max(12px, env(safe-area-inset-top)) 12px max(12px, env(safe-area-inset-bottom));
  z-index: 10;
  overflow-y: auto;
}

.sheet {
  background: var(--card);
  border-radius: 14px;
  border: 1px solid var(--line);
  padding: clamp(18px, 3vw, 26px);
  max-width: 500px;
  width: 100%;
  /* Centred when it fits; when it does not, the top stays reachable and the overlay scrolls. */
  margin: auto;
  animation: rise calc(var(--t) * 1.6) var(--ease);
}

.wide-sheet { max-width: 680px; }

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

.sheet h2 { margin: 0 0 4px; font-size: 20px; }
.sheet h3 { margin: 18px 0 4px; font-size: 14px; }
.sheet p { margin: 0 0 8px; font-size: 14px; color: var(--ink-soft); }
.sheet .lede { margin-bottom: 12px; }
.sheet .example { padding: 8px 12px; background: var(--cell-blank); border-radius: 8px; }
.sheet b { color: var(--ink); }
.ex-done { color: var(--done) !important; }
.ex-broken { color: var(--broken) !important; }

.rules { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; margin: 0; font-size: 14px; }
.rules dt { font-weight: 600; color: var(--ink); }
.rules dd { margin: 0; color: var(--ink-soft); }
.rules + .rules { margin-top: 0; }
.sheet .small { margin-top: 10px; font-size: 13px; }

kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--cell-blank);
  color: var(--ink);
  font: 600 12px/1.4 ui-monospace, Consolas, monospace;
  text-align: center;
}

/* Mouse and keyboard, or touch: the sheet shows the controls for the device in hand. */
@media (pointer: coarse) { .only-mouse { display: none; } }
@media not all and (pointer: coarse) { .only-touch { display: none; } }

.wide {
  margin-top: 20px;
  width: 100%;
  padding: 10px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--cell-blank);
  color: var(--ink);
  font: 14px/1 inherit;
  cursor: pointer;
}

.wide:hover { border-color: var(--ink-faint); }

/* Close rides along the bottom of a sheet taller than the screen, so it is never a scroll away. */
.sheet > .wide {
  position: sticky;
  bottom: 0;
  box-shadow: 0 -8px 0 0 var(--card), 0 14px 0 0 var(--card);
}

/* The puzzle list. The catalogue is the progression, so this is the closest thing the game has to
   a score screen - and an unsolved puzzle keeps its name to itself. */
.picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }

.pick {
  display: grid;
  gap: 2px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--card);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--t) var(--ease);
}

.pick:hover { border-color: var(--ink-faint); }
.pick.done { border-color: var(--good); }
.pick b { font-size: 14px; color: var(--ink); font-weight: 600; }
@media (max-width: 620px) {
  .picks { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 6px; }
  .pick { padding: 8px 9px; }
}

.pick span { font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* A phone held upright. The title and the buttons share a compact header so the board gets the
   height. */
@media (max-width: 620px) {
  .head { flex-direction: column; align-items: stretch; gap: 6px; }
  .title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
  h1 { font-size: 24px; }
  .tagline { margin: 0; font-size: 12px; }
  .actions { width: 100%; }
  .actions button { flex: 1 1 auto; padding: 9px 8px; }
  #help { flex: none; width: 40px; }
  .status { font-size: 13px; }
}

/* A phone on its side. Too short for a header above the board, so the header, the status line and
   the progress line stack down the left and the board has the full height on the right. */
@media (orientation: landscape) and (max-height: 500px) {
  #app {
    grid-template-columns: minmax(150px, 220px) minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
    align-content: start;
  }
  .head { grid-area: 1 / 1; flex-direction: column; align-items: stretch; gap: 8px; }
  .actions { display: grid; grid-template-columns: 1fr 1fr; }
  #help { width: auto; }
  .panel {
    grid-area: 1 / 2 / 4 / 3;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }
  /* The bar turns on its side too, beside the board rather than above it. */
  .bar { flex-direction: column; align-items: stretch; justify-content: flex-start; }
  .which, .bar .clock { flex: none; white-space: normal; text-align: left; }
  .status { grid-area: 2 / 1; text-align: left; font-size: 13px; }
  .foot { grid-area: 3 / 1; text-align: left; align-self: start; }
  h1 { font-size: 24px; }
}
