/* The mechanics every style shares. Look lives in the style's own file.
 *
 * Deliberately almost naked: a style should be able to make this page look
 * like a newspaper or a terminal without fighting anything written here.
 */

/* Registered so it has a type.
 *
 * An unregistered custom property is a token stream, not a value: a style that
 * wrote `--cell: clamp(14px, 1.6vw, 26px)` laid out correctly, because CSS
 * resolves the clamp, while the engine read the literal string back through
 * parseFloat, got NaN, and counted columns against a fallback of 22px. Every
 * style using a clamp had a copy block the wrong width and nothing on screen
 * said so. Registering it makes the property compute to a length, so both
 * sides agree. */
@property --cell {
  syntax: "<length>"; inherits: true; initial-value: 22px;
}

:root {
  --cell: 22px;
  --sweep-span: 900ms;   /* how long the green front takes to cross */
  --sweep-dur: 700ms;    /* how long one cell spends green */
  --green: #17a34a;
}

* { box-sizing: border-box; }
/* `flex-direction` is inert unless a style flexes the body, and two of them do
   to centre the hero -- which laid the site's sections out in a row beside it
   and pushed the page sideways. Declared here so a style cannot forget. */
body { margin: 0; flex-direction: column; }

/* ------------------------------------------------------------- the block */

.fill {
  display: grid;
  grid-template-columns: repeat(var(--cols, 12), var(--cell));
  grid-auto-rows: var(--cell);
  grid-auto-flow: row dense;
  justify-content: center;
  align-content: start;
}

/* The hole. Auto-placed days flow around it because it is placed explicitly
   and they are not. */
.copy {
  grid-column: 1 / span var(--copy-cols, 6);
  grid-row: 1 / span var(--copy-rows, 6);
  z-index: 2;
  min-width: 0;
}

.day {
  display: block;
  background: var(--c, transparent);
  opacity: 0;
}
.day.settled { opacity: 1; }

/* ---------------------------------------------------------- the arriving */

/* Each day waits its turn on the way in, in the same order the green will
   cross later -- the year assembles along one axis and finishes along it. */
.fill.arriving .day.settled {
  animation: day-in 320ms cubic-bezier(.2, .9, .3, 1) both;
  animation-delay: calc(var(--in, 0) * var(--sweep-span));
}
@keyframes day-in {
  from { opacity: 0; transform: scale(.4); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------- the swap */

/* One day changing hands. Short, and it lifts: the point is that your eye is
   caught by a day moving from one person to another, which is what the solver
   spends the whole middle of the run doing.

   Two names for one animation so it can be restarted by swapping classes. */
.day.swap-a, .day.swap-b {
  /* No fill mode: the last keyframe is the resting state anyway, and holding
     it leaves a finished animation (and its compositing layer) attached to
     every day that ever changed hands. */
  animation: day-swap 440ms cubic-bezier(.24, .86, .28, 1);
  animation-delay: var(--swap-lag, 0ms);
}
/* Scale alone is not enough on a small day: at ten pixels a pop is two pixels
   of movement and nobody sees it. So the day leaves -- it flies out of its
   place along `--ux`/`--uy`, changes hands in the air, and drops back. A style
   that has a meaningful outward direction (orbit: away from the centre) sets
   the vector; anything that does not gets straight up.

   The halo does the rest: a glow is drawn outside the box without being part
   of it, while distance and scale both count towards the scrollable area, so
   `--swap-out` is kept small enough that a day at the edge of the page cannot
   push a scrollbar out. */
@keyframes day-swap {
  0% {
    background: var(--was, var(--c));
    transform: none;
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--was, var(--c)) 60%, transparent);
  }
  34% {
    background: var(--was, var(--c));
    transform:
      translate(calc(var(--ux, 0) * var(--swap-out, 14px)),
                calc(var(--uy, -1) * var(--swap-out, 14px)))
      scale(1.5);
    filter: brightness(1.95);
    box-shadow: 0 0 20px 8px color-mix(in srgb, var(--was, var(--c)) 48%, transparent);
  }
  52% {
    background: var(--c);
    transform:
      translate(calc(var(--ux, 0) * var(--swap-out, 14px)),
                calc(var(--uy, -1) * var(--swap-out, 14px)))
      scale(1.5);
    filter: brightness(2.2);
    box-shadow: 0 0 24px 10px color-mix(in srgb, var(--c) 52%, transparent);
  }
  82% {
    background: var(--c);
    transform: scale(.92);
    filter: brightness(1.15);
    box-shadow: 0 0 12px 4px color-mix(in srgb, var(--c) 24%, transparent);
  }
  100% {
    background: var(--c);
    transform: none;
    filter: none;
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--c) 0%, transparent);
  }
}

/* ------------------------------------------------------------- the green */

/* The search has been improving something invisible for two seconds. This is
   the page saying it stopped, and that what is on screen is the answer. */
.fill.sweeping .day.settled,
.fill.sweeping .day.settled.swap-a,
.fill.sweeping .day.settled.swap-b {
  animation: day-green var(--sweep-dur) ease-out both;
  animation-delay: calc(var(--d, 0) * var(--sweep-span));
}
@keyframes day-green {
  0%   { background: var(--c); }
  22%  { background: var(--green); }
  40%  { background: var(--green); }
  100% { background: var(--c); }
}

@media (prefers-reduced-motion: reduce) {
  .fill.arriving .day.settled,
  .fill.sweeping .day.settled,
  .fill.sweeping .day.settled.swap-a,
  .fill.sweeping .day.settled.swap-b,
  .day.swap-a, .day.swap-b { animation: none; }
}

/* -------------------------------------------------------------- the story */

/* Bare bones only. A style owns the look of this the same way it owns the
   look of the hero -- all that is guaranteed here is that the text is
   readable and the list is not numbered twice. */

.story { padding: 8vh 6vw; }
.story .asked { list-style: none; margin: 0; padding: 0; }
.story h2, .story h3, .story p { margin: 0; }

/* ------------------------------------------------------- the rest of it */

/* Same deal as the story: enough that the sections are readable in any
   style, and no look at all. */

.how, .proof, .rules, .never, .facts, .end { padding: 8vh 6vw; }
.steps, .fact-grid, .rule-cols ul { list-style: none; margin: 0; padding: 0; }
.how h2, .how h3, .proof h2, .proof h3, .rules h2, .rules h3,
.never h2, .facts h3, .end h2, .how p, .proof p, .rules p, .never p,
.facts p, .end p { margin: 0; }
figure { margin: 0; }
figure img { display: block; max-width: 100%; height: auto; }

/* The bar that follows you down the page. Sticky rather than fixed, and
   placed after the hero, so it only exists once the year is behind you. */
.topnav { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 20px; }
.topnav nav { display: flex; gap: 18px; }
