/* =========================================================
   Design tokens, reset and base typography.
   Greyscale only — the palette carries no hue at all, so the
   dark and light schemes are the same design inverted.
   ========================================================= */

:root {
  color-scheme: dark light;

  /* Surfaces and text */
  --bg: #0a0a0a;
  --bg-inset: #131313;
  --fg: #f4f4f4;
  --muted: #b4b4b4;
  --faint: #8a8a8a;
  --line: rgba(255, 255, 255, 0.14);
  --line-strong: rgba(255, 255, 255, 0.34);
  --invert-bg: #f4f4f4;
  --invert-fg: #0a0a0a;
  /* The closing CTA is a full-width slab, so it does not invert in the dark
     scheme — a white block that size is glaring. It steps up off the page
     instead. */
  --cta-bg: #171717;
  --cta-fg: #f4f4f4;

  /* Type */
  --font-sans:
    ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  /* Used only by the marker highlight and the pull-quote, where an italic serif
     is the one thing on the page that is neither the sans nor the mono. */
  --font-serif: Georgia, "Times New Roman", Times, serif;

  --step-000: 0.6875rem;
  --step-00: 0.8125rem;
  --step-0: 1rem;
  --step-1: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  --step-2: clamp(1.3125rem, 1.15rem + 0.8vw, 1.625rem);
  --step-3: clamp(1.75rem, 1.4rem + 1.8vw, 2.5rem);
  --step-4: clamp(2.125rem, 1.6rem + 2.6vw, 3.25rem);
  --step-display: clamp(2.5rem, 0.9rem + 12.5vw, 7rem);

  /* Layout */
  --container: 68rem;
  --gutter: 1.25rem;
  --section-space: clamp(3.5rem, 9vw, 6.5rem);
  --radius: 0;

  /* ---------- Effects ----------
     The shared easing curve for every hover and state change. Long tail, so a
     movement arrives quickly and settles rather than gliding the whole way. */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* A wash used behind marked text and swept buttons. It is the one "accent"
     the palette has: no hue, only a step in value away from the surface. */
  --accent-tint: rgba(255, 255, 255, 0.08);
  --line-soft: rgba(255, 255, 255, 0.07);

  /* Two layers each: a tight contact shadow that seats the element, and a long
     diffuse one that lifts it. On the dark scheme black shadows have very
     little to darken, so the contact layer is dropped and the diffuse layer is
     pushed harder — depth here comes mostly from the rim in --shadow-rim. */
  --shadow-sm: 0 6px 18px -10px rgba(0, 0, 0, 0.8);
  --shadow-md: 0 22px 48px -20px rgba(0, 0, 0, 0.9);
  --shadow-lift: 0 30px 60px -28px rgba(0, 0, 0, 1);
  --shadow-rim: inset 0 1px 0 rgba(255, 255, 255, 0.06);

  /* Paper grain. Three weights of the same fractal noise, inlined as SVG so
     none of them costs a request. --grain is the raw tile, only ever used
     through a low-opacity layer; the other two are pre-faded for use directly
     on a surface. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' opacity='0.5'/%3E%3C/svg%3E");
  --grain-mid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' opacity='0.2'/%3E%3C/svg%3E");
  --grain-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' opacity='0.09'/%3E%3C/svg%3E");

  /* The noise tile is black. On a dark page multiplying it changes nothing, so
     the dark scheme screens it on to lift specks out of the background and the
     light scheme multiplies it in, as paper grain. */
  --grain-blend: screen;
  /* Screening noise on to near-black lifts the whole page, so this is lower
     than it looks like it should be: past about 0.12 the background stops
     reading as #0a0a0a and the texture starts to look like a raised black
     level rather than grain. */
  --grain-opacity: 0.1;

  /* Light pooling, used on the hero and the closing slab. Same two pools in
     both schemes; only the light they cast is inverted. */
  --veil:
    radial-gradient(120% 85% at 50% 108%, rgba(255, 255, 255, 0.07) 0%, transparent 60%),
    radial-gradient(85% 60% at 78% 8%, rgba(255, 255, 255, 0.05) 0%, transparent 70%);
  --vignette: radial-gradient(110% 80% at 50% 50%, transparent 40%, rgba(0, 0, 0, 0.35) 100%);

  /* ---------- Geometry ----------
     An isometric lattice built from gradients: no image, no extra request.
     Three families of hairlines sixty degrees apart, which is what a squared
     grid is not — it reads as engineering paper rather than as the default
     backdrop every site ships.

     Drawn a tile at a time rather than as infinite repeating gradients across
     the element, because repeating gradients phase from the box edge: three of
     them at three angles would land their intersections wherever the element
     happened to be sized, and the triangles would never close. One tile is a
     rectangle of side x side*sqrt(3), which the lattice divides exactly.
     Anything painting --grid-lines therefore has to set --grid-tiles as its
     background-size.

     Each family is subdivided twice and graded by weight, so the coarse
     triangle carries a half-scale and a quarter-scale one inside it: the
     surface reads as a drawing that has been detailed rather than as one pitch
     of graph paper, and the eye still has the strongest lines to measure by.
     The two subdivisions are what a plain grid cannot do — a square grid drawn
     this way just looks like two grids.

     The percentages are shared by all three families, which is not a
     coincidence: for the horizontals the gradient axis spans the tile height
     H = side*sqrt(3), and for the diagonals it spans twice the perpendicular
     gap between corner-to-corner lines, and both work out to 4 rows of the
     lattice. So one row is 12.5% and the weight pattern repeats every 50%.

     The gradients themselves are not here. A custom property substitutes its
     var() references on the element that declares it, so --grid-lines written
     at :root would bake in the root's --grid-line and inherit down already
     resolved — and the dark slab, which re-tints the line, would get the root's
     colour anyway. They live on the surfaces that paint them, in styles.css. */
  --grid-side: 96px;
  --grid-tile: var(--grid-side) calc(var(--grid-side) * 1.7320508);
  --grid-tiles: var(--grid-tile), var(--grid-tile), var(--grid-tile);

  /* One colour; the two subdivisions are stepped down from it, so re-tinting
     the lattice stays a single declaration. */
  --grid-line: rgba(255, 255, 255, 0.055);

  /* Section edges, fading out at both ends instead of running corner to
     corner. Applied through border-image, so it costs no pseudo-element. */
  --rule-fade:
    linear-gradient(to right, transparent 0%, var(--line) 16%, var(--line) 84%, transparent 100%);

  /* Dark-slab variants. The closing panel is dark in both schemes, so anything
     drawn on it has to stay light in both and cannot use the tokens above.
     The lattice needs no twin of its own: --grid-lines resolves its colour at
     use time, so the slab re-points --grid-line at this and gets the same
     geometry. */
  --grid-line-on-dark: rgba(255, 255, 255, 0.06);
  --rule-fade-on-dark:
    linear-gradient(
      to right,
      transparent 0%,
      rgba(255, 255, 255, 0.2) 16%,
      rgba(255, 255, 255, 0.2) 84%,
      transparent 100%
    );

  /* ---------- Skeletal motif ----------
     The mark as a mask rather than an image: the glyph supplies the shape and
     CSS supplies the colour, so one asset works on any surface in either
     scheme. evenodd is what keeps the eyes cut out of the silhouette instead of
     filled.

     Same geometry as skullMark() in src/components/icons.mjs. The two are the
     same drawing in two forms the platform cannot share, so a change to the
     mascot is a change in both places. */
  --skull-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M50 8 C24 8 10 26 10 48 C10 64 20 76 32 76 L34 76 L34 83 C34 88 43 88 43 83 L43 76 L45.5 76 L45.5 86 C45.5 91 54.5 91 54.5 86 L54.5 76 L57 76 L57 83 C57 88 66 88 66 83 L66 76 L68 76 C80 76 90 64 90 48 C90 26 76 8 50 8 Z M44.8 45 a10.8 12.4 0 0 1-21.6 0 10.8 12.4 0 0 1 21.6 0 Z M76.8 45 a10.8 12.4 0 0 1-21.6 0 10.8 12.4 0 0 1 21.6 0 Z'/%3E%3C/svg%3E");
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #ffffff;
    --bg-inset: #f4f4f4;
    --fg: #0b0b0b;
    --muted: #3f3f3f;
    --faint: #6b6b6b;
    --line: rgba(0, 0, 0, 0.14);
    --line-strong: rgba(0, 0, 0, 0.36);
    --invert-bg: #0b0b0b;
    --invert-fg: #fafafa;
    --cta-bg: #0b0b0b;
    --cta-fg: #fafafa;

    --accent-tint: rgba(0, 0, 0, 0.06);
    --line-soft: rgba(0, 0, 0, 0.07);

    /* Here a black shadow has something to darken, so both layers are used and
       the rim is inverted into a hairline of shade along the top edge. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05), 0 6px 18px -10px rgba(0, 0, 0, 0.22);
    --shadow-md: 0 2px 6px rgba(0, 0, 0, 0.06), 0 22px 48px -20px rgba(0, 0, 0, 0.35);
    --shadow-lift: 0 4px 10px rgba(0, 0, 0, 0.08), 0 30px 60px -28px rgba(0, 0, 0, 0.45);
    --shadow-rim: inset 0 1px 0 rgba(0, 0, 0, 0.04);

    --grain-blend: multiply;
    --grain-opacity: 0.055;

    /* --grid-lines resolves var() at use time, so overriding the colour here
       re-tints it without restating the gradients. */
    --grid-line: rgba(0, 0, 0, 0.05);

    --veil:
      radial-gradient(120% 85% at 50% 108%, rgba(0, 0, 0, 0.05) 0%, transparent 60%),
      radial-gradient(85% 60% at 78% 8%, rgba(0, 0, 0, 0.035) 0%, transparent 70%);
    --vignette: radial-gradient(110% 80% at 50% 50%, transparent 40%, rgba(0, 0, 0, 0.06) 100%);
  }
}

/* ---------- Reset ---------- */

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

body,
h1,
h2,
h3,
h4,
p,
ul,
ol,
figure {
  margin: 0;
}

ul[role="list"],
ol[role="list"] {
  padding: 0;
  list-style: none;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored sections clear the sticky header, which is one row at every width
     now — taller on a phone, where the lockup carries the bar on its own (see
     responsive.css). */
  scroll-padding-top: calc(6rem + env(safe-area-inset-top));
}

img,
svg {
  display: block;
  max-width: 100%;
}

/* ---------- Base ---------- */

body {
  min-height: 100vh;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Paper grain over the whole page.
   The kit raises the content above this layer instead, but that only works when
   the sections are transparent. Here the cards, the header and the closing slab
   all paint their own opaque background, so grain underneath them would show
   through in patches. Sitting on top, it lands on every surface at the same
   weight. It never takes a pointer event, so nothing below it changes
   behaviour. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  background-image: var(--grain);
  background-size: 220px 220px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}

h1,
h2,
h3,
h4 {
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  text-decoration-color: var(--faint);
}

::selection {
  background: var(--fg);
  color: var(--bg);
}

/* The outline sits outside the element, so it has to contrast with the page
   background rather than with the element's own text colour — currentColor on a
   filled button would draw a white ring on a white page. */
:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  /* Hover lift is a movement, not a state change — drop it entirely rather than
     make it instant. The colour and border changes still carry the state. */
  .btn:hover,
  .btn:active {
    transform: none;
  }
}
