/*
  Page chrome and body content — everything that isn't the hero.
  Renders over .bg-fixed (hero.css/hero.js): a near-black backdrop
  with faint, slow-drifting dust, no bright center element once past
  the hero. That's already dark enough for --text/--text-dim — no
  scrim is added here by default. If a specific block later proves
  unreadable against the dust, give THAT block a local scrim; don't
  add one globally.

  All values are tokens from tokens.css — no literals, same rule as
  the rest of the codebase.
*/

/* ---------------------------------------------------------------
   Section-jump rail — now the site's ONLY navigation, since the top
   bar it used to duplicate is gone. Every section always listed and
   always clickable, with the currently-in-view one brightened
   (js/sectionNav.js, IntersectionObserver-driven) and the rest dimmed.
   Present from the first frame at every width: it can't be hidden on
   narrow screens any more, because there'd be no way to navigate at
   all — so on mobile it shrinks and tucks tighter to the edge rather
   than disappearing.
------------------------------------------------------------------ */
.section-rail {
  position: fixed;
  top: 50%;
  right: var(--space-4);
  z-index: 9;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-3);
  transform: translateY(-50%);
}

.section-rail__link {
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--duration-fast) var(--ease-weighty);
}

/* Entrance — each link drifts in from the right, one after another.
   Visible/settled is the element's NORMAL state and the keyframes only
   animate toward it, so a browser that skips the animation entirely
   still shows a complete, usable rail (the same reason the hero title
   is white by default). animation-fill-mode: both is what holds each
   link hidden through its own stagger delay. */
@media (prefers-reduced-motion: no-preference) {
  .section-rail__link {
    animation: section-rail-in var(--duration-base) var(--ease-weighty) both;
  }

  .section-rail__link:nth-child(1) { animation-delay: 0.30s; }
  .section-rail__link:nth-child(2) { animation-delay: 0.36s; }
  .section-rail__link:nth-child(3) { animation-delay: 0.42s; }
  .section-rail__link:nth-child(4) { animation-delay: 0.48s; }
  .section-rail__link:nth-child(5) { animation-delay: 0.54s; }
  .section-rail__link:nth-child(6) { animation-delay: 0.60s; }
  .section-rail__link:nth-child(7) { animation-delay: 0.66s; }
}

@keyframes section-rail-in {
  from { opacity: 0; transform: translateX(var(--space-3)); }
  to   { opacity: 1; transform: translateX(0); }
}

.section-rail__link:hover,
.section-rail__link:focus-visible {
  color: var(--accent);
}

.section-rail__link.is-active {
  color: var(--text-bright);
}

@media (max-width: 900px) {
  .section-rail {
    right: var(--space-2);
    gap: var(--space-2);
  }

  .section-rail__link {
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
  }

  /* Reserve the rail's column so body text can't run underneath it.
     Only needed at these widths: above 900px the measure is far
     narrower than the viewport and the rail sits clear of it anyway. */
  .content {
    padding-right: calc(var(--content-pad) + 4.75rem);
  }
}

/* ---------------------------------------------------------------
   Section 1 — intro prose. A centered, narrower-than-usual column
   (not the full 68ch .content measure), left-aligned within that
   column (centering the paragraphs themselves, at this length, would
   ragged the line breaks and hurt readability). Body size is
   deliberately the SAME token as .goal-card__body / .roadmap-station__
   body / .coverage-card__headline below (--step-1) — one shared
   "content prose" size across every below-the-fold section,
   comfortable long-form reading rather than a shouted, oversized lede.
------------------------------------------------------------------ */
.section-intro {
  /* Tighter at the bottom than the top: the story's last paragraph and
     the "Why we care" lead-in were separated by two full --space-8
     stacks (18rem of nothing), which read as the page having ended.
     Worth being aggressive here because the drift (js/storyDrift.js)
     leaves that last paragraph's box sitting there empty once it has
     faded, which adds its own gap on top of whatever padding says. */
  padding-block: var(--space-8) var(--space-5);
}

.section-intro__col {
  max-width: 56ch;
}

.section-intro__lede {
  font-size: var(--step-1);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text);
  /* Scroll-driven lift/fade lives in js/storyDrift.js. */
  will-change: transform, opacity;
}

.section-intro__lede + .section-intro__lede {
  margin-top: var(--space-5);
}

/* ---------------------------------------------------------------
   Section 2 — three goals, editorial list rather than a card grid:
   numerals + hairline rules, not boxes/icons/shadows.
------------------------------------------------------------------ */
.section-goals {
  padding-block: var(--space-5) var(--space-8);
}

.goal-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.goal-card {
  display: grid;
  grid-template-columns: var(--space-6) 1fr;
  gap: var(--space-4);
  padding-block: var(--space-6);
  border-top: 1px solid var(--line);
}

.goal-card:last-child {
  border-bottom: 1px solid var(--line);
}

.goal-card__index {
  font-size: var(--step-1);
  font-weight: var(--weight-light);
  color: var(--text-faint);
}

.goal-card__title {
  font-size: var(--step-1);
  font-weight: var(--weight-medium);
  color: var(--text);
  margin-bottom: var(--space-2);
}

.goal-card__body {
  max-width: 46ch;
  font-size: var(--step-1);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text-dim);
}

@media (max-width: 700px) {
  .goal-card {
    grid-template-columns: 1fr;
    gap: var(--space-2);
  }
}

/* ---------------------------------------------------------------
   Section 2.5 — "Measure a magnetar", an interactive residual-fit
   figure. Populated/driven at runtime by js/measure.js (data/
   chirp_grid.json) — the canvas and slider values below start empty;
   the whole block stays [hidden] until the data has loaded and the
   first frame is ready, so there's never a flash of an inert slider
   sitting at 0. Same hairline-topped, boxless language as the goal/
   roadmap/coverage blocks — the plot is drawn straight onto the page's
   own background, no panel behind it, no library chrome.
------------------------------------------------------------------ */
.section-measure {
  padding-block: var(--space-8);
}

/* This whole section runs brighter than the rest of the page's
   supporting text — it's a figure to be read closely, and the muted
   greys that suit prose were hard to make out over the dust. */
.measure-intro {
  max-width: 60ch;
  font-size: var(--step-1);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text);
  margin-bottom: var(--space-6);
}

.measure {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.measure-plot {
  display: flex;
  gap: var(--space-3);
  border-top: 1px solid var(--line);
  padding-top: var(--space-5);
}

.measure-plot__yaxis {
  flex-shrink: 0;
  align-self: center;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--step--1);
  color: var(--text-dim);
  white-space: nowrap;
}

.measure-plot__main {
  flex: 1;
  min-width: 0;
}

.measure-plot__canvas-wrap {
  position: relative;
  width: 100%;
  /* Shorter than the old 16/9 — the y domain is now a fixed [-1, 1]
     (js/measure.js), so the interesting part of the curve no longer
     needs as much height to stay legible. */
  aspect-ratio: 5 / 2;
}

.measure-plot__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.measure-plot__xaxis {
  margin-top: var(--space-2);
  text-align: center;
  font-size: var(--step--1);
  color: var(--text-dim);
}

/* ONE grid for all three columns, not three independent stacks. The
   two sliders and the fit readout put their children directly into
   this grid (display:contents on the wrappers), so "label row",
   "control row" and "caption row" are shared row TRACKS — the labels
   sit on one baseline and the two slider tracks and the chi2 bar are
   on one line, by construction, no matter how much taller the chi2
   numeral is than a slider's value. (The previous attempt matched the
   two rows' heights with a min-height, which lined the bars up but
   left the label row ragged, since each column was still its own
   independent stack.) */
.measure-controls {
  display: grid;
  grid-template-columns: repeat(2, 1fr) minmax(14rem, 1fr);
  column-gap: var(--space-6);
  row-gap: var(--space-3);
}

.measure-slider,
.measure-fit {
  display: contents;
}

[data-measure-slider="P"] > * { grid-column: 1; }
[data-measure-slider="B"] > * { grid-column: 2; }
.measure-fit > * { grid-column: 3; }

/* Row 1 — the labels. Baseline-aligned across all three columns, so
   "Spin period (P)", "Magnetic field (B)" and the chi2 label all sit
   on the same line even though the chi2 readout beside them is set
   several steps larger. */
.measure-slider__head,
.measure-fit__head {
  grid-row: 1;
  align-self: baseline;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
}

/* Row 2 — the controls. The bar is centred in the track so its line
   lands on the slider tracks' line rather than its top edge. */
.measure-range { grid-row: 2; align-self: center; }
.measure-fit__bar { grid-row: 2; align-self: center; }

/* Row 3 — the small print under each control. Row 4 is the button,
   which only column 3 has. */
.measure-slider__guide,
.measure-fit__hint { grid-row: 3; }
.measure-snap { grid-row: 4; }

.measure-slider__label {
  font-size: var(--step-0);
  font-weight: var(--weight-medium);
  color: var(--text);
}

.measure-slider__value {
  font-size: var(--step-0);
  font-weight: var(--weight-medium);
  color: var(--accent);
  white-space: nowrap;
}

.measure-slider__unit {
  margin-left: 0.3em;
  font-size: var(--step--1);
  font-weight: var(--weight-regular);
  color: var(--text-dim);
}

.measure-slider__guide {
  font-size: var(--step--1);
  font-weight: var(--weight-light);
  color: var(--text);
}

/* Custom range input — thin track, small accent thumb, no native
   browser chrome. --measure-range-fill (set in js/measure.js on every
   update) drives the webkit "filled up to the thumb" look; Firefox
   gets the same effect for free from ::-moz-range-progress. */
.measure-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 1.5rem;
  background: transparent;
  cursor: pointer;
  --measure-range-fill: 0%;
}

.measure-range::-webkit-slider-runnable-track {
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--accent) var(--measure-range-fill),
    var(--line-raised) var(--measure-range-fill)
  );
}

.measure-range::-moz-range-track {
  height: 2px;
  border-radius: 999px;
  background: var(--line-raised);
}

.measure-range::-moz-range-progress {
  height: 2px;
  border-radius: 999px;
  background: var(--accent);
}

.measure-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  margin-top: -5px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--bg);
  transition: transform var(--duration-fast) var(--ease-weighty);
}

.measure-range::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--bg);
  transition: transform var(--duration-fast) var(--ease-weighty);
}

.measure-range:hover::-webkit-slider-thumb,
.measure-range:focus-visible::-webkit-slider-thumb {
  transform: scale(1.2);
}

.measure-range:hover::-moz-range-thumb,
.measure-range:focus-visible::-moz-range-thumb {
  transform: scale(1.2);
}

.measure-range:focus-visible {
  outline: none;
}

.measure-range:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--line-raised);
}

.measure-range:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--line-raised);
}

.measure-fit__label {
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text);
}

.measure-fit__value {
  font-size: var(--step-2);
  font-weight: var(--weight-medium);
  color: var(--text);
  /* Tamed from the inherited body line-height (1.6, meant for prose)
     so this large numeral doesn't inflate .measure-fit__head taller
     than the shared min-height above — glyph-height only. */
  line-height: 1;
}

.measure-fit__bar {
  height: 4px;
  border-radius: 999px;
  background: var(--line-raised);
  overflow: hidden;
}

.measure-fit__bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--accent);
  opacity: 0.45;
  /* Deliberately near-instant on width — this updates on every slider
     input event, and a slower transition here would read as the bar
     lagging behind the thumb. opacity keeps a touch of the site's
     usual --duration-fast softness since it changes less abruptly. */
  transition: width 0.08s linear, opacity var(--duration-fast) var(--ease-weighty);
}

.measure.is-near-best .measure-fit__bar-fill {
  box-shadow: 0 0 10px 0 var(--accent);
}

.measure-fit__hint {
  font-size: var(--step--1);
  font-weight: var(--weight-light);
  color: var(--text);
}

.measure-snap {
  align-self: start;
  /* Grid item now (see .measure-controls) — without this it would
     stretch to the full column width instead of hugging its label. */
  justify-self: start;
  margin-top: var(--space-1);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--line-raised);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-weighty),
              color var(--duration-fast) var(--ease-weighty),
              background var(--duration-fast) var(--ease-weighty);
}

.measure-snap:hover,
.measure-snap:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--bg-raised);
}

@media (max-width: 700px) {
  /* One column: the three groups go back to being their own stacks,
     since there are no longer sibling columns to align rows against
     (and a shared 4-row grid would interleave them). */
  .measure-controls {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }

  .measure-slider,
  .measure-fit {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  [data-measure-slider="P"] > *,
  [data-measure-slider="B"] > *,
  .measure-fit > * {
    grid-column: auto;
  }

  .measure-slider__head,
  .measure-fit__head,
  .measure-range,
  .measure-fit__bar,
  .measure-slider__guide,
  .measure-fit__hint,
  .measure-snap {
    grid-row: auto;
  }

  .measure-plot {
    flex-direction: column;
    gap: var(--space-2);
  }

  .measure-plot__yaxis {
    writing-mode: horizontal-tb;
    transform: none;
    align-self: flex-start;
  }

  .measure-plot__canvas-wrap {
    aspect-ratio: 3 / 2;
  }
}

/* ---------------------------------------------------------------
   Section 3 — roadmap, as a horizontal timeline — deliberately NOT
   the Goals treatment (grid + hairline rules): three stations strung
   along one axis line, left to right, each a node + a small tag
   number + title + sentence, reading as a forward progression rather
   than a parallel list of properties. Collapses to a vertical
   timeline on mobile (same line/node language, rotated).
------------------------------------------------------------------ */
.section-roadmap {
  padding-block: var(--space-8);
}

/* Shared quiet eyebrow for a section's lead-in line — used here and
   by .section-coverage below. */
.section-lead {
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--space-6);
}

.roadmap-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
}

/* The axis line — one continuous stroke behind every node, spanning
   the full row. Positioned at the node's own vertical center (nodes
   are --space-4 square, so half of that from the row's top) rather
   than a hardcoded pixel value, so it stays correct if --space-4 is
   ever retuned. */
.roadmap-timeline::before {
  content: '';
  position: absolute;
  top: calc(var(--space-4) / 2);
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line-raised);
}

.roadmap-station {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  padding-top: var(--space-6);
}

/* The node — a small filled dot sitting ON the axis line. Every
   station has the same padding-top above, so every node lands at the
   same height, in line with .roadmap-timeline::before. */
.roadmap-station__node {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--space-4);
  height: var(--space-4);
  border-radius: 50%;
  background: var(--accent);
}

.roadmap-station__index {
  display: block;
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--text-faint);
  margin-bottom: var(--space-3);
}

.roadmap-station__title {
  font-size: var(--step-1);
  font-weight: var(--weight-medium);
  color: var(--text);
  margin-bottom: var(--space-2);
}

.roadmap-station__body {
  font-size: var(--step-1);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text-dim);
}

@media (max-width: 700px) {
  .roadmap-timeline {
    flex-direction: column;
    gap: var(--space-7);
  }

  /* Axis line rotates to vertical, running the full height of the
     stacked list instead of the full width of the row. */
  .roadmap-timeline::before {
    top: 0;
    bottom: 0;
    left: calc(var(--space-4) / 2);
    right: auto;
    width: 1px;
    height: auto;
  }

  .roadmap-station {
    padding-top: 0;
    padding-left: calc(var(--space-4) + var(--space-4));
  }

  .roadmap-station__node {
    top: 0.35em;
    left: 0;
  }
}

/* ---------------------------------------------------------------
   Section 3.5 — "Who we are" team grid. Populated at runtime from
   data/people.json (js/team.js) — the grid below starts empty.
   Deliberately flat: one uniform card per person, same size for the
   founder as for everyone else — no featured/enlarged treatment.
   Headshots are pre-cropped 4:5 portrait files (560x700), the same
   ratio as --avatar-ratio (tokens.css) — masked into a true ellipse via
   border-radius:50% on that non-square box, no clip-path needed.
   object-fit:cover is still there as a safety net (a differently-
   cropped photo added later without matching the 4:5 ratio would be
   covered, not letterboxed), but since ratios already match, it maps
   the source in 1:1 with no cropping on either axis — no black caps at
   top/bottom, no shaved edges left/right.
------------------------------------------------------------------ */
.section-team {
  padding-block: var(--space-8);
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--avatar-w) + var(--space-4)), 1fr));
  gap: var(--space-7) var(--space-4);
}

.person {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
}

.person__photo {
  width: var(--avatar-w);
  aspect-ratio: var(--avatar-ratio);
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-raised);
  transition: transform var(--duration-fast) var(--ease-weighty),
              box-shadow var(--duration-fast) var(--ease-weighty);
}

.person__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Restrained hover — a hair of lift and a soft glow, nothing flashy. */
.person:hover .person__photo {
  transform: scale(1.03);
  box-shadow: 0 0 0 1px var(--line-raised), 0 var(--space-2) var(--space-5) -1.5rem var(--accent);
}

.person__info {
  max-width: calc(var(--avatar-w) * 1.6);
}

.person__name {
  font-size: var(--step-0);
  font-weight: var(--weight-medium);
  color: var(--text);
}

.person__contact {
  display: inline-flex;
  align-items: center;
  color: var(--text-faint);
  transform: translateY(0.1em);
  transition: color var(--duration-fast) var(--ease-weighty);
}

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

.person__role {
  margin-top: var(--space-1);
  font-size: var(--step--1);
  font-weight: var(--weight-light);
  line-height: var(--leading-snug);
  color: var(--text-dim);
}

/* ---------------------------------------------------------------
   Section 4 — coverage/press gallery. Populated at runtime from
   data/press.json (js/coverage.js) — the grid below starts empty.
   Cards are hairline-topped, not boxed panels (same language as
   .goal-card/.roadmap-station, not a SaaS card grid): a logo mark (or
   text wordmark fallback), the headline, a quiet "read →" link.
------------------------------------------------------------------ */
.section-coverage {
  padding-block: var(--space-8);
}

.coverage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-6) var(--space-5);
}

.coverage-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}

.coverage-card__mark {
  display: flex;
  align-items: center;
  height: var(--space-5);
}

.coverage-card__logo {
  max-width: 7rem;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: left center;
  opacity: 0.88;
  transition: opacity var(--duration-fast) var(--ease-weighty);
}

.coverage-card:hover .coverage-card__logo {
  opacity: 1;
}

/* Text wordmark — hidden by default, shown only once js/coverage.js
   has confirmed (via the <img>'s own error event) that a domain has
   no cached logo. Styled to read as a deliberate mark, not a stray
   label, so a card looks intentional either way. */
.coverage-card__wordmark {
  display: none;
  font-size: var(--step-0);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}

.coverage-card__mark--fallback .coverage-card__wordmark {
  display: block;
}

.coverage-card__headline {
  flex: 1;
  font-size: var(--step-0);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text);
}

.coverage-card__link {
  align-self: flex-start;
  font-size: var(--step--1);
  color: var(--text-dim);
}

/* ---------------------------------------------------------------
   Footer — stub.
------------------------------------------------------------------ */
.site-footer {
  padding-block: var(--space-6);
  border-top: 1px solid var(--line);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
}

.site-footer__text {
  font-size: var(--step--1);
  color: var(--text-faint);
}

.site-footer__text a {
  color: inherit;
}
