/* Lanternsong
   ----------------------------------------------------------------------------
   The brief, in Kevin's words: camping was frightening as a kid, but when the
   lantern was on the picnic table next to you it was okay. Just the warm glow
   of it being safety.

   So this is not a dark page with a gold accent. It is a pool of warm light
   with the dark left alone outside it, and the one genuinely bright thing on
   the page is a game you can actually play. */

:root {
  /* The room. Warm black, because warm light falls onto it. */
  --room:   #0A0A0B;
  --ink:    #F2EDE4;
  --soft:   #A39C92;
  --faint:  #6B655E;
  --edge:   rgba(255, 240, 220, 0.09);
  --edge-lit: rgba(255, 220, 170, 0.28);

  /* The mantle, and its falloff. A camping lantern is not a yellow bulb: the
     core is near white and it goes amber on the way out. */
  --mantle:  #FFE7BE;
  --lantern: #FFC372;
  --amber:   #FF9E3D;

  /* Nil's own pads, unchanged. These belong to the game, not to the site. */
  --green: #00D26A;
  --blue:  #2E7BFF;
  --yellow:#FFC800;
  --violet:#9B5CFF;

  /* One grotesk with a real width axis, plus a mono for anything that is
     data rather than voice. No display serif: a big elegant serif on a dark
     page is a fashion, and it belongs to every other site using it. */
  --sans: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* The board's own geometry, scaled up to hold the page. Four columns,
     because the game is four by four, and a gap in the same proportion to the
     column as the board's 6px gap is to its cell. */
  --gap: 1.5rem;

  /* Unhurried on purpose. Most sites are tuned to feel instant. */
  --slow: 620ms;
  --ease: cubic-bezier(0.22, 0.61, 0.24, 1);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--room);
  color: var(--ink);
  font: 400 16px/1.7 var(--sans);
  font-variation-settings: 'wdth' 100;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---- the three atmosphere layers ---- */

/* Stars: cold and far. The contrast between the cold outside and the warm
   near light is the whole point, so these never get warmed. */
#sky {
  position: fixed;
  inset: 0;
  /* A canvas is a replaced element: with width:auto it falls back to its
     intrinsic 300x150 instead of filling the viewport. Both are required. */
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* The lantern. Three stops, because a mantle does not fall off evenly: a small
   near-white core, the amber body, and a wide dim wash that reaches the
   corners without ever really lighting them.
   Fixed to the viewport at reading height, not to the page, because the
   lantern was on the table next to you rather than hanging above it. The
   light goes where you go. */
.lamp {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(30rem 20rem at 44% 30vh, rgba(255, 231, 190, 0.115), transparent 70%),
    radial-gradient(52rem 38rem at 44% 34vh, rgba(255, 195, 114, 0.095), transparent 72%),
    radial-gradient(100rem 76rem at 44% 30vh, rgba(255, 158, 61, 0.06), transparent 76%);
  /* It comes up rather than switching on. You pumped it, the mantle caught,
     and it took a moment to come to full. */
  animation: catch 2600ms cubic-bezier(0.18, 0.66, 0.28, 1) both,
             breathe 9s ease-in-out 2600ms infinite;
}

@keyframes catch {
  from { opacity: 0; filter: blur(22px); }
  60%  { opacity: 0.94; filter: blur(3px); }
  to   { opacity: 1; filter: blur(0); }
}

/* A mantle is never perfectly steady. Deliberately almost too small to see. */
@keyframes breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.94; }
}

/* Grain. A night outdoors is never a clean flat black. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- the shell ---- */

.page {
  position: relative;
  z-index: 1;
  max-width: 60rem;
  margin: 0 auto;
  padding: 0 2rem 7rem;
}

/* Everything on the page sits on the board's grid. Four columns is not a
   layout convention here, it is the game's shape. */
.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
}

/* The left column carries the marker, the remaining three carry the text.
   A hanging label, the way a field manual sets its sections. */
.span23 { grid-column: 2 / -1; }
.span12 { grid-column: 1 / 3; }
.span34 { grid-column: 3 / -1; }

@media (max-width: 760px) {
  .grid { grid-template-columns: minmax(0, 1fr); gap: 0.75rem; }
  .span23, .span12, .span34 { grid-column: 1 / -1; }
}

header.top { padding: 3.5rem 0 0; }

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.95rem;
  font-weight: 500;
  font-variation-settings: 'wdth' 108;
  letter-spacing: -0.005em;
  color: var(--ink);
  text-decoration: none;
  border: none;
}

/* The source the rest of the light comes from, and the one genuinely bright
   thing on the page apart from the game. */
.wordmark .flame {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mantle);
  box-shadow:
    0 0 6px 1px rgba(255, 231, 190, 0.85),
    0 0 18px 4px rgba(255, 195, 114, 0.45),
    0 0 42px 12px rgba(255, 158, 61, 0.22);
  animation: flicker 9s ease-in-out infinite;
}

@keyframes flicker {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.88; }
}

/* ---- type ---- */

h1 {
  font-weight: 500;
  /* Wide rather than large. Width reads as calm where size reads as loud, and
     it echoes the squares. */
  font-variation-settings: 'wdth' 112;
  font-size: clamp(1.85rem, 3.4vw, 2.5rem);
  line-height: 1.22;
  letter-spacing: -0.012em;
  margin: 0;
  max-width: 20ch;
}

.lede {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--soft);
  max-width: 42ch;
  margin: 1.5rem 0 0;
  text-wrap: pretty;
}

/* A section marker, with a hairline running off to the edge. */
.label {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--faint);
  margin: 0;
}

p { margin: 0 0 1.3rem; max-width: 60ch; text-wrap: pretty; }

a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--edge);
  transition: border-color var(--slow) var(--ease), color var(--slow) var(--ease);
}

a:hover { border-bottom-color: var(--lantern); color: var(--mantle); }

/* ---- hero ---- */

.hero {
  display: grid;
  gap: 3rem;
  margin: 4rem 0 3.25rem;
}

.play { width: 100%; max-width: 300px; }

/* Two columns only where there is genuinely room. The board is pushed down so
   it sits against the lede rather than lining up with the headline: the
   asymmetry is what stops it reading as a stock hero. */
@media (min-width: 900px) {
  .hero {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: 4.5rem;
    align-items: start;
  }
  .play { width: 272px; margin-top: 2.5rem; justify-self: end; }
}

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

.board {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  /* Matched by PAD in board.js: an absolutely positioned child resolves
     against the padding box, so the inset is applied in the transform too. */
  padding: 8px;
  border-radius: 18px;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 195, 114, 0.06), transparent 62%),
    linear-gradient(180deg, #17161A, #0B0A0C);
  border: 1px solid var(--edge);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035),
              0 24px 60px rgba(0, 0, 0, 0.55);
  touch-action: none;
  outline: none;
  transition: box-shadow var(--slow) var(--ease), border-color var(--slow) var(--ease);
}

.board:focus-visible { border-color: var(--edge-lit); }

/* Emptying it is the only thing the board ever celebrates. */
.board.emptied {
  border-color: var(--edge-lit);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035),
              0 0 80px rgba(255, 158, 61, 0.16),
              0 24px 60px rgba(0, 0, 0, 0.55);
}

.tile {
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 7px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28);
  transition: transform 340ms var(--ease), opacity 480ms var(--ease);
  will-change: transform;
}

.tile.new { opacity: 0; }

/* A pair that has cancelled leaves rather than vanishing. */
.tile.gone {
  opacity: 0;
  transition: transform 340ms var(--ease), opacity 520ms var(--ease);
}

.playrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1rem;
}

.hint {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--faint);
}

.sound {
  font: 400 0.68rem/1 var(--mono);
  color: var(--soft);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 100px;
  padding: 0.45rem 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--slow) var(--ease), border-color var(--slow) var(--ease);
}

.sound:hover { color: var(--ink); border-color: var(--faint); }
.sound[aria-pressed="true"] { color: var(--lantern); border-color: var(--edge-lit); }

/* ---- the shelf ---- */

.game {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
  align-items: start;
  padding: 2.25rem 0 2.5rem;
  border-top: 1px solid var(--edge);
  border-bottom: none;
  position: relative;
}

/* The row warms under the cursor, as though the lantern were moved closer. */
.game::before {
  content: "";
  position: absolute;
  inset: 0 -1.5rem;
  border-radius: 10px;
  background: radial-gradient(60% 120% at 20% 50%, rgba(255, 195, 114, 0.05), transparent 70%);
  opacity: 0;
  transition: opacity var(--slow) var(--ease);
  pointer-events: none;
}

.game:hover::before { opacity: 1; }
.game:hover { border-bottom: none; }

/* Four pads at the size they are on the board. The marker for the section is
   the game's own colors rather than a word. */
.chips { display: grid; grid-template-columns: repeat(2, 15px); gap: 4px; }
.chips i { width: 15px; height: 15px; border-radius: 4px; display: block; }
.chips i:nth-child(1) { background: var(--green); }
.chips i:nth-child(2) { background: var(--blue); }
.chips i:nth-child(3) { background: var(--yellow); }
.chips i:nth-child(4) { background: var(--violet); }

.game-main { grid-column: 2 / 4; }

.game-name {
  font-size: 1.15rem;
  font-weight: 500;
  font-variation-settings: 'wdth' 108;
  line-height: 1.3;
  margin: 0 0 0.5rem;
  transition: color var(--slow) var(--ease);
}

.game:hover .game-name { color: var(--mantle); }
.game-blurb { color: var(--soft); margin: 0; font-size: 0.95rem; }

.game-meta {
  grid-column: 4 / -1;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--faint);
}

@media (max-width: 760px) {
  .game { grid-template-columns: minmax(0, 1fr); gap: 1.1rem; }
  .game-main, .game-meta { grid-column: 1 / -1; }
  .game-meta { flex-direction: row; gap: 1rem; }
}

/* ---- document pages ---- */

.doc { margin-top: 3.5rem; }

.doc h1 { max-width: 22ch; }

/* One section: its name hangs in the first column, its prose fills the rest.
   The way a field manual sets a section, and it lands on the same four
   columns as everything else on the site. */
.section {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
  margin-top: 3rem;
}

.section h2 {
  grid-column: 1 / 2;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--faint);
  margin: 0;
  line-height: 1.5;
}

.section .body { grid-column: 2 / -1; }
.section .body > :last-child { margin-bottom: 0; }

@media (max-width: 760px) {
  .section { grid-template-columns: minmax(0, 1fr); gap: 0.6rem; margin-top: 2.5rem; }
  .section h2, .section .body { grid-column: 1 / -1; }
}

.doc p, .doc ul { max-width: 62ch; color: var(--soft); font-size: 0.95rem; }
.doc .lede { color: var(--ink); font-size: 1.05rem; max-width: 44ch; }
.doc p strong { color: var(--ink); font-weight: 500; }
.doc ul { padding-left: 1.1rem; }
.doc li { margin-bottom: 0.6rem; color: var(--soft); font-size: 0.95rem; }
.doc li strong { color: var(--ink); font-weight: 500; }

.updated {
  font-family: var(--mono);
  color: var(--faint);
  font-size: 0.68rem;
  margin-top: 1.5rem;
  max-width: 56ch;
  line-height: 1.6;
}

/* ---- footer ---- */

footer {
  border-top: 1px solid var(--edge);
  margin-top: 6rem;
  padding-top: 1.75rem;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 0.68rem;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  align-items: baseline;
}

footer a { border-bottom: none; color: var(--faint); }
footer a:hover { color: var(--ink); }
footer .spacer { flex: 1; }

@media (max-width: 560px) {
  .page { padding: 0 1.5rem 5rem; }
  header.top { padding-top: 2.5rem; }
  .hero { margin: 3rem 0 5rem; }
  body { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .tile, .game, .game::before, a, .board, .sound { transition-duration: 1ms !important; }
  .lamp { animation: catch 1ms both; }
  .wordmark .flame { animation: none; }
}
