/* Former Engineering: one stylesheet for every page.
   Visual direction: ticket 09, as amended (Royal Deep / Mint). Dark only. No inline styles anywhere, so the CSP stays 'self'. */

/* ---- Fonts ------------------------------------------------------------------ */
/* Subset latin woff2, weight axis narrowed (see README). Versioned names: they are cached as immutable. */

@font-face {
  font-family: "Plus Jakarta Sans";
  src: url("/fonts/plus-jakarta-sans-v1.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-v1.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}

/* ---- Tokens ----------------------------------------------------------------- */

:root {
  /* Colour: Royal Deep / Mint. A deep royal-blue ground, pale blue-white lines, one mint accent. */
  --bg: #1f3a9e;
  --muted: #aebfee;
  --ink: #f4f6ff;
  --line: #d6e0ff;
  --rule: color-mix(in srgb, var(--ink) 10%, transparent);
  --accent: #4dffc3; /* the lift, link hover and focus, the readout's dot. Nothing else. */
  --grain: .2; /* opacity of the background grain */

  /* Type, in rem so it follows the reader's default font size (16px unless they've changed it) */
  --f-sans: "Plus Jakarta Sans", system-ui, sans-serif;
  --f-mono: "JetBrains Mono", ui-monospace, monospace;
  --fs-hero: clamp(2.75rem, 7vw, 6rem);
  --fs-statement: clamp(1.5rem, 3vw, 2.375rem);
  --fs-reading-title: clamp(2rem, 4.5vw, 3.5rem);
  --fs-reading-h2: clamp(1.25rem, 2vw, 1.5rem);
  --fs-body: 1.0625rem; /* 17px */
  --fs-wordmark: .9375rem; /* 15px */
  --fs-small: .8125rem; /* 13px */
  --fs-label: .6875rem; /* 11px */
  --measure-body: 52ch;
  --measure-reading: 65ch;

  /* Grid and space */
  --cols: 12;
  --margin: clamp(16px, 4vw, 56px);
  --gutter: clamp(12px, 1.6vw, 24px);
  --space-top: clamp(88px, 16vh, 200px);    /* above the first section */
  --space-section: clamp(88px, 14vh, 180px); /* between sections */

  /* Hero field (read by terrain-v1.js) */
  --horizon: .47;
  --fade: 140px;
  --title-z: 8; /* the depth the hero title stands at: its baseline is on that ridge's base, just below the
                   horizon, and nearer ridges pass in front of it */
  --title-raise: 30px; /* then lifted this far up from that base */

  /* Motion */
  --ease-out: cubic-bezier(.2, .7, .1, 1);

  color-scheme: dark;
}

@media (max-width: 720px) {
  :root { --cols: 4; }
}

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

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

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

body {
  position: relative; /* for the grain, below */
  margin: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body > main { flex: 1; }

/* Grain: a static noise tile (img/grain-v1.png: 256px, 16 greys from 39 to 217) soft-lit over --bg,
   behind everything else on the page, the hero's lines included. It spans the whole body, so it
   scrolls with the page. body sets no z-index, so this stays in the root stacking context and blends
   with html's --bg. */
body::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url("/img/grain-v1.png") 0 0 / 256px 256px;
  image-rendering: pixelated;
  mix-blend-mode: soft-light;
  opacity: var(--grain);
}

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

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  transition: color .2s;
}

a:hover,
a:focus-visible { color: var(--accent); }

:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 4px;
}

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

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Grid ------------------------------------------------------------------- */
/* 12 columns (4 under 720px). Labels sit in columns 1–2, content in 3–10. */

.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--gutter);
  padding-inline: var(--margin);
}

.col-label { grid-column: 1 / 3; }
.col-content { grid-column: 3 / 11; }

@media (max-width: 720px) {
  .col-label,
  .col-content { grid-column: 1 / -1; }
}

/* ---- Labels and rules ------------------------------------------------------- */

/* Mono caps: index labels and instrument labels. */
.label,
.index {
  font-family: var(--f-mono);
  font-size: var(--fs-label);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  font-variant-ligatures: none;
  color: var(--muted);
}

/* Section index: <h2 class="index"><span aria-hidden="true">01</span>Purpose</h2> */
.index span[aria-hidden] { margin-right: .9em; }

.wordmark {
  font-family: var(--f-sans);
  font-weight: 700;
  font-size: var(--fs-wordmark);
  line-height: 1.2;
  letter-spacing: -.02em;
  color: var(--ink);
  text-decoration: none;
}

/* The mark, "Lines of thought" (the favicon without its tile). Inline SVG in currentColor, cap height tall and
   standing on the baseline, so it sits with the type it leads. viewBox is the bars' box on the icon's 32 grid. */
.mark {
  width: .894em; /* 18:15, the viewBox */
  height: .745em; /* Plus Jakarta Sans cap height */
  margin-right: .5em;
  fill: currentColor;
}

.site-footer .mark { margin-right: .7em; }

/* ---- Hero (homepage only) --------------------------------------------------- */

.hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  min-height: 540px;
  overflow: hidden;
  touch-action: pan-y pinch-zoom; /* a sideways drag steers the lift; pinch-zoom stays with the reader */
}

.field {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* No-JS only: the still frame as an image (inside <noscript>, so it's never fetched when JS runs).
   object-position keeps the image's horizon on the field's. */
.poster {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% calc(var(--horizon) * 100%);
}

/* Fade the foot of the field so the bottom labels read and the hero hands off to the page. A mask,
   not a --bg overlay, so the page's grain carries on under it. Clear from 78% of the way down the fade. */
.field,
.poster {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--fade)), transparent calc(100% - var(--fade) * .22));
  mask-image: linear-gradient(to bottom, #000 calc(100% - var(--fade)), transparent calc(100% - var(--fade) * .22));
}

.hero .over {
  position: absolute;
  z-index: 2;
  pointer-events: none;
}

.hero .wordmark { top: 26px; left: var(--margin); }

/* Baseline --title-raise above the base of the ridge at --title-z, in terrain-v1.js's projection at rest. The baseline is
   .072em above the line box's foot (Plus Jakarta Sans: ascent 1.038, descent .222, in a .96 line box).
   With JS the field draws the title among the ridges and this copy is only for reading. */
.hero-title {
  bottom: calc((1 - var(--horizon) - (1.04 - var(--horizon)) / var(--title-z)) * 100% + var(--title-raise) - .072em);
  left: var(--margin);
  right: var(--margin);
  text-align: center;
  white-space: nowrap;
  font-weight: 700;
  font-size: min(var(--fs-hero), 9vw); /* one line down to phone width */
  line-height: .96;
  letter-spacing: -.045em;
}

.hero.is-live .hero-title { color: transparent; }

/* Forced colours (Windows contrast themes) repaint the page but not the canvas, whose pale lines and title
   can vanish on a light theme. Drop the field there; the <h1> takes the theme's text colour. */
@media (forced-colors: active) {
  .field { display: none; }
}

.corner-tr { top: 28px; right: var(--margin); text-align: right; }
.corner-bl { bottom: 26px; left: var(--margin); }
.corner-br { bottom: 26px; right: var(--margin); }

/* Fixed-width values, so the right-aligned line doesn't shift when a value gains or loses a digit.
   x reads up to "+11.00", z up to "11.00"; each character is 1ch plus the label's letter-spacing. */
.readout b {
  display: inline-block;
  width: calc(var(--chars) * (1ch + .06em));
  text-align: right;
  font-weight: 500;
  color: var(--ink);
}

#rx { --chars: 6; }
#rz { --chars: 5; }

.readout .dot {
  margin-right: .5em;
  color: var(--accent);
}

@media (max-width: 720px) {
  .readout { display: none; }
}

/* ---- Homepage sections ------------------------------------------------------ */

.sections { padding-top: var(--space-top); }

.section { padding-bottom: var(--space-section); }

.section .index { padding-top: .55em; }

.section .col-content {
  display: grid;
  gap: 24px;
  align-content: start;
}

.statement,
.big-link {
  font-size: var(--fs-statement);
  font-weight: 500;
  line-height: 1.22;
  letter-spacing: -.025em;
}

.statement { text-wrap: balance; }

.body {
  max-width: var(--measure-body);
  color: var(--muted);
}

.big-link {
  justify-self: start;
  color: var(--ink);
  text-decoration: none;
}

@media (max-width: 720px) {
  .section .index { padding-top: 0; margin-bottom: 20px; }
}

/* ---- Header (every page except the homepage) -------------------------------- */

.site-header { padding-top: 26px; }
.site-header .wordmark { grid-column: 1 / -1; justify-self: start; }

/* ---- Reading layout (legal pages and 404) ----------------------------------- */

.reading { padding-block: var(--space-top) var(--space-section); }

.reading-body {
  max-width: var(--measure-reading);
  color: var(--muted);
}

.reading-body > * + * { margin-top: 1em; }
.reading-body > .label + h1 { margin-top: 20px; }

.reading-body h1 {
  font-size: var(--fs-reading-title);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -.035em;
  color: var(--ink);
  text-wrap: balance;
}

.reading-body h1 + * { margin-top: 32px; }

.reading-body h2 {
  margin-top: 2.4em;
  font-size: var(--fs-reading-h2);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -.02em;
  color: var(--ink);
}

.reading-body ul { padding-left: 1.2em; }

.reading-body li + li { margin-top: .4em; }

.reading-body strong,
.reading-body a { color: var(--ink); font-weight: 500; }

.reading-body a:hover,
.reading-body a:focus-visible { color: var(--accent); }

.updated { margin-top: 32px; }

/* ---- Footer (every page) ---------------------------------------------------- */

.site-footer {
  padding-block: 28px 120px;
  border-top: 1px solid var(--rule);
  font-size: var(--fs-small);
  color: var(--muted);
}

.site-footer .legal { grid-column: 1 / 9; }

.site-footer nav {
  grid-column: 9 / 13;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: baseline;
}

.site-footer nav a + a::before {
  content: "·";
  display: inline-block;
  margin-inline: .6em;
  color: var(--muted);
}

.site-footer a { text-decoration: none; }
.site-footer a:hover,
.site-footer a:focus-visible { text-decoration: underline; }

@media (max-width: 720px) {
  .site-footer .grid { row-gap: 12px; }
  .site-footer .legal,
  .site-footer nav { grid-column: 1 / -1; justify-content: flex-start; }
}

/* ---- Reveal and fade-up ----------------------------------------------------- */
/* js/reveal-v1.js adds .reveal-on to <html> (only when motion is allowed) and .is-in to each element
   once it enters the viewport. Without JS, or under reduced motion, everything is simply shown.

   .reveal-words  word-mask reveal: reveal-v1.js wraps each word as .word > span.
   .fade-up       opacity and a short rise. */

@media (prefers-reduced-motion: no-preference) {
  /* The hidden state has no transition, so arming is instant; only .is-in animates. */
  .reveal-on .fade-up {
    opacity: 0;
    transform: translateY(16px);
  }

  .reveal-on .fade-up.is-in {
    opacity: 1;
    transform: none;
    transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
    transition-delay: var(--delay, 0s);
  }

  .reveal-on .reveal-words .word {
    display: inline-block;
    overflow: hidden;
    vertical-align: top;
    /* Room for descenders inside the mask. */
    padding-bottom: .12em;
    margin-bottom: -.12em;
  }

  .reveal-on .reveal-words .word > span {
    display: inline-block;
    transform: translateY(110%);
  }

  .reveal-on .reveal-words.is-in .word > span {
    transform: none;
    transition: transform 1s var(--ease-out);
    transition-delay: calc(var(--delay, 0s) + var(--i, 0) * 45ms);
  }

  /* Touch screens: fade only. Anything that rises while a finger scrolls the page looks like it's
     scrolling at its own speed. */
  @media (hover: none) and (pointer: coarse) {
    .reveal-on .fade-up,
    .reveal-on .reveal-words .word > span { transform: none; }

    .reveal-on .reveal-words .word > span { opacity: 0; }

    .reveal-on .reveal-words.is-in .word > span {
      opacity: 1;
      transition: opacity .9s var(--ease-out);
    }
  }
}
