/* logic.ninja
   One page: a disc of cells that fills with light as the puzzle is solved.
   Every colour and every size is named here, in :root, and nowhere else;
   js/view.js reads the disc's sizes from these names. */

/* Fraunces, cut down to basic Latin: one file with an optical-size axis (a
   fine cut for the large name, a sturdy one for small numerals) and weights
   400 to 600. Its licence is beside it in /fonts/. */
@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Colour. Each has one job. */
  --ground: #000000; /* the page, and a number's seat */
  --night: #0f0f0e;  /* a dark cell */
  --line: #3b3935;   /* the seams of the disc */
  --light: #efe9dc;  /* a lit cell; text */
  --joint: #e3ddcd;  /* where two cells of one light meet */
  --quiet: #8e8b85;  /* secondary text; the count of dark cells; 6:1 on the ground */
  --mark: #3d6bff;   /* where you are: keyboard focus, the held number, a hint */

  /* Type. One family. Sizes grow smoothly from a 360 px phone to a 2,560 px monitor. */
  --font: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --text: clamp(1rem, 0.94rem + 0.27vw, 1.375rem);
  --small: 0.875em;
  --name: clamp(1.75rem, 1.4rem + 1.6vw, 3.75rem);
  --leading: 1.45;

  /* Space */
  --edge: clamp(0.75rem, 0.4rem + 1.6vw, 3rem); /* from the side of the screen */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --gutter: clamp(1.25rem, 0.6rem + 2.6vw, 4.5rem); /* between the disc and the column beside it */

  /* Shapes */
  --control: 2.75rem; /* the least height and width of anything pressed: 44 px */
  --round: 999px;     /* the end of the one filled button */
  --side: clamp(18rem, 26vw, 24rem);   /* least width of the column beside the disc on a wide screen */
  --side-most: 26rem;                  /* and its greatest */
  --screen-h: 100vh;  /* the height of the screen; made the small-viewport height below, where the browser has one */
  --edge-x: max(var(--edge), env(safe-area-inset-left), env(safe-area-inset-right)); /* the page's side padding, clear of a notch */
  --edge-y: max(var(--edge), env(safe-area-inset-top), env(safe-area-inset-bottom)); /* its top and bottom padding */
  --tall-rest: 24rem; /* on a tall screen, the height kept for everything that is not the disc */
  /* The disc's width. On a tall screen: the page's width, or what the height
     leaves. On a wide screen: the page's height, or what the width leaves
     once the column beside it has its least width. The wide layout's first
     column is this same value, so the disc cannot be wider than its column. */
  --disc-tall: max(14rem, var(--screen-h) - 2 * var(--edge-y) - var(--tall-rest));
  --disc-wide: min(var(--screen-h) - 2 * var(--edge-y), 100vw - var(--side) - var(--gutter) - 2 * var(--edge-x));
  --focus: 2px;       /* weight of the focus outline on a button or link */

  /* Motion: a cell lighting, and the disc closing when solved. */
  --quick: 120ms;
  --slow: 600ms;

  /* The disc, in the units of its own drawing: the rim is 100 from the centre. */
  --disc-rim: 100;
  --disc-margin: 3;     /* clear space outside the rim, for the focus mark */
  --disc-hole: 27;      /* radius of the centre, where the count of dark cells stands */
  --disc-seam: 0.9;     /* the line between two cells */
  --disc-lap: 0.6;      /* how far the light under a join reaches into the cell on either side */
  --disc-seat: 0.41;    /* radius of a number's seat, as a share of a ring's width */
  --disc-spent: 0.32;   /* a spent number's dot, as a share of its seat */
  --disc-numeral: 0.6;  /* type size of a numeral, as a share of a ring's width */
  --disc-figure: 0.702; /* height of the typeface's figures, as a share of its type size: measured from the font file */
  --disc-count: 18;     /* type size of the count in the centre */
  --disc-guide: 1.5;    /* radius of the dot on a cell the held number can reach */
  --disc-line: 1.5;     /* weight of the marks: focus, held, hint */
  --disc-dash: 2.6;     /* length of a dash, and of the gap after it, in the ring on a hinted cell */
  --disc-halo: 2.4;     /* how far the ring round a held number stands off its seat */

  color-scheme: dark;
}

/* Where the browser knows the small-viewport height (the screen with its
   bars showing), use it, so nothing sits behind a phone's address bar. */
@supports (height: 100svh) {
  :root {
    --screen-h: 100svh;
  }
}

/* Where the line about keys is not shown (see .keys), less height is kept for the words. */
@media (hover: none), (pointer: coarse) {
  :root {
    --tall-rest: 18rem;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html {
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--light);
  font-family: var(--font);
  font-size: var(--text);
  line-height: var(--leading);
  font-kerning: normal;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

p,
h1 {
  margin: 0;
}

/* ---- The page ----
   Tall screen: the name and levels; the disc, in the middle of the height
   left over; the puzzle's facts and controls; the foot. */

.page {
  min-height: var(--screen-h);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto auto;
  grid-template-areas: "head" "stage" "panel" "foot";
  row-gap: var(--space-4);
  padding: var(--edge-y) var(--edge-x);
}

.head {
  grid-area: head;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  column-gap: var(--space-4);
}

.name {
  font-size: var(--name);
  font-weight: 400;
  line-height: 1.1;
}

.levels {
  display: flex;
  /* The buttons' own padding stands in from the edge; pull the row out so the words line up with it. */
  margin: 0 calc(-1 * var(--space-2));
}

.stage {
  grid-area: stage;
  display: grid;
  place-items: center;
  min-width: 0;
}

.disc {
  display: block;
  width: min(100%, var(--disc-tall));
  aspect-ratio: 1;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

.panel {
  grid-area: panel;
  display: grid;
  align-content: start;
  row-gap: var(--space-2);
  min-width: 0;
}

.facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  column-gap: var(--space-4);
}

.which {
  color: var(--quiet);
}

.acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-1);
  margin: 0 calc(-1 * var(--space-2));
}

.how {
  color: var(--quiet);
  font-size: var(--small);
  max-width: 34em;
}

.foot {
  grid-area: foot;
  color: var(--quiet);
  font-size: var(--small);
}

/* Wide screen (at least as wide as it is tall): the disc takes the height,
   and its column is exactly its width. Beside it the name, the levels and
   the puzzle's facts and controls stand one under another from the top, with
   the foot at the bottom of the screen. */
@media (min-aspect-ratio: 1/1) {
  .page {
    grid-template-columns: var(--disc-wide) minmax(var(--side), var(--side-most));
    grid-template-rows: auto auto 1fr;
    grid-template-areas: "stage head" "stage panel" "stage foot";
    column-gap: var(--gutter);
    justify-content: center;
  }

  .disc {
    width: var(--disc-wide);
  }

  .head {
    flex-direction: column;
    align-items: flex-start;
  }

  .facts {
    justify-content: flex-start;
  }

  .foot {
    align-self: end;
  }
}

/* Wide and tall enough to spare the room (30rem): the facts and controls
   are level with the middle of the disc, and the name stands just above. */
@media (min-aspect-ratio: 1/1) and (min-height: 30rem) {
  .page {
    grid-template-rows: 1fr auto 1fr;
  }

  .head {
    align-self: end;
  }
}

/* ---- Things pressed ---- */

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.level,
.act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control);
  min-width: var(--control);
  padding: 0 var(--space-2);
  text-underline-offset: 0.3em;
  text-decoration-thickness: 1px;
}

/* The level in play is the light one, and underlined; the others are quiet. */
.level {
  color: var(--quiet);
}

.level[aria-pressed="true"] {
  color: var(--light);
  text-decoration-line: underline;
}

/* A dot after a level whose puzzle for today is solved. */
.level.is-done::after {
  content: "";
  width: 0.36em;
  height: 0.36em;
  margin-left: 0.45em;
  border-radius: 50%;
  background: currentColor;
}

.act:disabled {
  color: var(--quiet);
  cursor: default;
}

/* The one filled button: on to the next puzzle. */
.act.main {
  margin-left: var(--space-2);
  padding: 0 var(--space-4);
  border-radius: var(--round);
  background: var(--light);
  color: var(--ground);
}

@media (hover: hover) {
  .level:hover,
  .act:not(.main):not(:disabled):hover,
  .home:hover {
    color: var(--light);
    text-decoration-line: underline;
  }
}

button:focus-visible,
a:focus-visible {
  outline: var(--focus) solid var(--mark);
  outline-offset: var(--focus);
}

/* ---- The disc ---- */

/* A plate in the line colour lies under the cells; what shows between them is the seams. */
.plate {
  fill: var(--line);
}

.cell {
  outline: none;
  cursor: pointer;
}

.tile {
  fill: var(--night);
  transition: fill var(--quick) ease-out;
}

.cell.is-lit .tile,
.cell.is-number .tile {
  fill: var(--light);
}

/* Two cells of one light are joined across the seam. Underneath, light
   reaches a little into both cells, so nothing of the plate shows at the
   edges; over it, exactly on the seam, a line a little darker than the
   light, so the cells can still be counted. */
.joint {
  visibility: hidden;
}

.joint.is-on {
  visibility: visible;
}

.joint .under {
  fill: var(--light);
}

.joint .over {
  fill: var(--joint);
}

/* A number sits on a dark seat. Spent, it has no numeral and the seat is a dot. */
.seat {
  fill: var(--ground);
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--quick) ease-out;
}

.cell.is-spent .seat {
  transform: scale(var(--disc-spent));
}

.numeral {
  fill: var(--light);
  font-weight: 600;
  text-anchor: middle;
  font-variant-numeric: lining-nums tabular-nums;
  pointer-events: none;
}

/* The centre: how many cells are still dark. Solved, the centre fills with light. */
.core {
  fill: var(--ground);
  transition: fill var(--slow) ease-out;
}

.count {
  fill: var(--quiet);
  text-anchor: middle;
  font-variant-numeric: lining-nums tabular-nums;
  pointer-events: none;
}

.is-solved .core {
  fill: var(--light);
}

.is-solved .joint .over {
  fill: var(--light);
  transition: fill var(--slow) ease-out;
}

.is-solved .cell {
  cursor: default;
}

/* Marks, all in the one mark colour and each a different shape: a dot on a
   cell the held number can reach, a ring round the held number, a dashed
   ring on a hinted cell, an outline round the cell with keyboard focus. */
.guide {
  fill: var(--mark);
  visibility: hidden;
  pointer-events: none;
}

.cell.is-within .guide {
  visibility: visible;
}

.mark {
  fill: none;
  stroke: var(--mark);
  stroke-width: var(--disc-line);
  visibility: hidden;
  pointer-events: none;
}

.mark.is-on {
  visibility: visible;
}

.mark.hint {
  stroke-dasharray: var(--disc-dash);
}

@media (prefers-reduced-motion: reduce) {
  .tile,
  .seat,
  .core,
  .is-solved .joint .over {
    transition: none;
  }
}

/* ---- Small things ---- */

/* Read by a screen reader, not shown. */
.unseen {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The line about keys is shown where there is likely to be a keyboard, and
   is always there for a screen reader. */
@media (hover: none), (pointer: coarse) {
  .keys {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.noscript {
  padding: 0 var(--edge-x) var(--edge-y);
  color: var(--quiet);
}

/* The page for a wrong address. */
.lost {
  min-height: var(--screen-h);
  display: grid;
  place-content: center;
  justify-items: center;
  row-gap: var(--space-4);
  padding: var(--edge-y) var(--edge-x);
  text-align: center;
}

.home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control);
  min-width: var(--control);
  padding: 0 var(--space-2);
  color: var(--quiet);
  text-decoration: none;
  text-underline-offset: 0.3em;
  text-decoration-thickness: 1px;
}

::selection {
  background: var(--light);
  color: var(--ground);
}
