/* Reset + foundations ======================================================= */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;

  /* The canvas colour: this is what shows when the page is over-scrolled at
     the top or bottom (iOS rubber-band). The body background used to
     propagate here, so a light band flashed above the dark hero and below
     the dark footer. */
  background: var(--color-bg-inverse);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  font-family: var(--font-body);
  font-size: var(--title-16-size);
  line-height: var(--title-16-leading);
  letter-spacing: var(--title-16-tracking);
  font-weight: var(--title-16-weight);
  color: var(--color-text);

  /* The page's base colour is dark — the same as the top (hero) and bottom
     (footer) of the page. In iOS 26 Safari draws its chrome as translucent
     layers that sample the page underneath rather than theme-color, and the
     over-scroll band is the base colour too. This makes both dark without
     relying on Safari's behaviour.

     Light sections (the bio) therefore carry their own background. */
  background: var(--color-bg-inverse);
  -webkit-font-smoothing: antialiased;
  /* Stops a stray overflowing element from causing sideways scroll on mobile */
  overflow-x: hidden;
}

img, picture, svg, video {
  display: block;
  max-width: 100%;
  height: auto;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  text-wrap: balance;      /* nicer line breaks in headings */
}

/* The hero headline is the only type that steps at breakpoints (36 → 44 → 54) */
h1 {
  font-size: var(--role-hero-size);
  line-height: var(--role-hero-leading);
  letter-spacing: var(--role-hero-tracking);
  font-weight: var(--role-hero-weight);
}

h2 {
  font-size: var(--display-36-size);
  line-height: var(--display-36-leading);
  font-weight: var(--display-36-weight);
}

h3 {
  font-size: var(--headline-28-size);
  line-height: var(--headline-28-leading);
  font-weight: var(--headline-28-weight);
}

p { text-wrap: pretty; }   /* avoids a lone word on the last line */

a { color: inherit; }

/* Visible focus for keyboard navigation — accessibility.

   currentColor, not a token: --color-accent was never defined, which made
   this declaration invalid at computed-value time and dropped the ring
   altogether, leaving the focus appearance to the browser's own default.
   currentColor also solves the harder half of the problem — the ring has to
   read on the near-black hero, the light bio and six section colours, and
   the text already contrasts with every one of them. */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* Visible to screen readers, not to the eye */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Pre-animation state --------------------------------------------------

   js/animations.js is a module, and it imports src/lib/motion.js — a SECOND,
   serial request the browser only discovers once animations.js has arrived.
   Measured on the live site: HTML done at 191ms, animations.js at 717, and
   motion.js only started at 719 and finished at 956. The document is parsed
   and styled long before that, and nothing blocks painting in between, so the
   finished page was on screen for roughly 240ms before the JS hid anything —
   the end state flashed, then the intro ran. Over localhost that window is
   zero, which is why it only showed in production.

   So the starting state is declared HERE, in CSS, where it applies at the
   first paint. The gate class is set by an inline script in <head> (one is in
   each page) and removed by animations.js once the real animations are in
   place; their fill: 'both' holds the same values through the delay, so the
   handover is invisible. index.html also preloads motion.js, which closes the
   gap itself — this rule is what makes the result independent of the network.

   Gated on the class rather than applied outright, because without JS the
   content has to stay visible: the inline script also drops the class on
   window load, so a failed or blocked animations.js cannot leave the page
   blank. Only inside no-preference, since clipReveal() returns early under
   reduced motion and would never reveal what we hid. */
@media (prefers-reduced-motion: no-preference) {
  /* The hero headline, the header/meta items (data-after), the scroll
     chevron, and every scroll-reveal target: each one is animated from
     opacity 0 by motion.js. */
  .motion-pending [data-split],
  .motion-pending [data-after],
  .motion-pending [data-chevron],
  .motion-pending [data-reveal] {
    opacity: 0;
  }

  /* data-reveal="media" is the exception: images unfold from a clip-path and
     their opacity is never animated, so they need the clip, not a fade. */
  .motion-pending [data-reveal="media"] {
    clip-path: inset(100% 0% 0% 0%);
    opacity: 1;
  }
}
