/* ==========================================================================
   base.css — reset, document defaults, focus, selection.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--nav-h) + var(--s5));
}

/* Smooth scroll only when motion is welcome. Lenis handles it when JS runs. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  line-height: var(--lh-heading);
  font-weight: var(--fw-semi);
  letter-spacing: -0.015em;
  color: var(--text);
  text-wrap: balance;
}

h1 { font-size: var(--fs-display); letter-spacing: -0.03em; }
h2 { font-size: var(--fs-xl);      letter-spacing: -0.02em; }
h3 { font-size: var(--fs-lg); }
h4 { font-size: var(--fs-md); font-weight: var(--fw-medium); }

p { text-wrap: pretty; }

a {
  color: var(--text);
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
  transition: color var(--dur-fast) var(--ease),
              text-decoration-color var(--dur-fast) var(--ease);
}

a:hover { color: var(--accent-hover); text-decoration-color: var(--accent); }

img, svg, video {
  display: block;
  max-width: 100%;
  height: auto;
}

button, input, select, textarea { font: inherit; color: inherit; }

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

ul, ol { padding: 0; list-style: none; }

code, pre, kbd, samp { font-family: var(--font-mono); }

hr { border: 0; border-top: var(--rule); }

/* ---- Focus ------------------------------------------------------------- */

:focus { outline: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::selection {
  background: var(--accent-fill);
  color: var(--on-accent-fill);
}

/* ---- Theme change ------------------------------------------------------ *
 * Only the elements whose colours actually change, and only the three
 * properties that change. There is deliberately no `*` rule here: a global
 * transition makes every hover and every panel expand feel laggy.
 *
 * Anything with its own transition (buttons, chips, rows, the nav, links) is
 * absent from this list — it already animates these properties at its own
 * speed, and adding it here would slow its hover down to the theme duration.
 *
 * `theme-ready` is set one frame after load, so the first paint does not
 * animate. The reduced-motion block below neutralises the duration. */

.theme-ready body,
.theme-ready .section,
.theme-ready .manifest,
.theme-ready .manifest-fallback,
.theme-ready .manifest-fallback li,
.theme-ready .row,
.theme-ready .row__panel,
.theme-ready .timeline__rule,
.theme-ready .job__marker,
.theme-ready .job__bullets li::before,
.theme-ready .education,
.theme-ready .skills__group,
.theme-ready .contact__list,
.theme-ready .contact__item,
.theme-ready .footer,
.theme-ready .footer__heading,
.theme-ready .hero__photo,
.theme-ready .meta-strip,
.theme-ready .callout,
.theme-ready .code,
.theme-ready .code__bar,
.theme-ready .code pre,
.theme-ready .case__index ol,
.theme-ready .case__next {
  transition: background-color var(--dur-theme) var(--ease),
              color var(--dur-theme) var(--ease),
              border-color var(--dur-theme) var(--ease);
}

/* ---- Utilities ---------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--s3);
  left: var(--s3);
  z-index: 100;
  padding: var(--s2) var(--s4);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  transform: translateY(-200%);
  transition: transform var(--dur-base) var(--ease);
}

.skip-link:focus-visible { transform: translateY(0); }

.measure { max-width: var(--measure); }

/* The hero entrance is pure CSS and owns no visibility beyond its own run.

   It used to be a GSAP timeline, with the hero held at `opacity: 0` by a
   class until the tween started. That meant the most important content on the
   site waited on a third-party CDN: on a hard refresh with a cold cache the
   hero was blank for as long as gsap.min.js took to arrive.

   As a CSS animation it starts at first paint, needs no script, and cannot be
   left half-applied. `both` holds the start state through the delay and the
   end state afterwards, so every element finishes visible. */
@media (prefers-reduced-motion: no-preference) {
  .anim-hero > div > *,
  .anim-hero > .hero__figure {
    /* `backwards`, not `both`: the start state applies during the delay, and
       once the run is over the element returns to its natural state rather
       than to a frozen keyframe. */
    animation: hero-in 620ms cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  }

  .anim-hero > div > *:nth-child(1) { animation-delay: 60ms; }
  .anim-hero > div > *:nth-child(2) { animation-delay: 145ms; }
  .anim-hero > div > *:nth-child(3) { animation-delay: 230ms; }
  .anim-hero > div > *:nth-child(4) { animation-delay: 315ms; }
  .anim-hero > .hero__figure        { animation-delay: 200ms; }
}

@keyframes hero-in {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* The last word on hero visibility. An inline script in <head> sets this a
   second and a half after parsing, and it cancels the animation outright —
   so even an animation clock that never advances (a frozen compositor, a
   throttled tab, a headless renderer) cannot leave the hero blank. */
.anim-done .anim-hero > div > *,
.anim-done .anim-hero > .hero__figure {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Lenis (smooth scroll) required styles ------------------------------ */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* Long identifiers in prose — `prefetch_related`, `scope_qs_for_user` — have
   no break opportunity and will push a narrow column wider than the screen.
   Inside a <pre> this is handled by its own horizontal scroll; inline, it
   has to be allowed to wrap. */
:not(pre) > code { overflow-wrap: anywhere; }
