/* ==========================================================================
   World Futures Lab — style.css
   --------------------------------------------------------------------------
   One stylesheet, written to be read. Every rule in it is a *token* or a
   *component*, and the file is laid out in that order.

   CONTENTS (in the order the blocks appear below)
     01  reset       strip browser defaults
     02  tokens      palette, type scale, spacing scale — the only place
                     literal values are allowed to appear
     03  base        element defaults: body, links, selection
     04  layout      page scaffolding: .shell, .panel, .exchange, .two-col
     05  type        headings and long-form copy: .intro, .prose, .statement
     06  components  the named pieces of interface
     07  responsive  the component rules that change with the viewport
     08  motion      animation, all of it behind prefers-reduced-motion

   The cascade order is fixed by the line below, not by where a block sits in
   the file — which is why motion can be written last yet still beat the
   responsive layer.
   ========================================================================== */

@layer reset, tokens, base, layout, type, components, motion, responsive;

/* ------------------------------------------------------------------- fonts
   Self-hosted, latin subset only: the two families the site actually sets,
   served from our own origin so no third-party request blocks the first
   paint. Newsreader carries the display and the body text; Jost the logo and
   the nav. The weight ranges match each font's variable axis. Files live in
   src/assets/fonts and are copied to /assets/fonts.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/jost-roman.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/newsreader-roman.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/newsreader-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   NAMING
     .block                a component      .site-header   .card
     .block__element       a part of it     .site-header__inner
     .block--modifier      a variant        .panel--dark   .btn--primary

   COLOUR — never hard-code a colour; use a token, and use it *by job*
     --p-*                 the ten source palette values (tokens layer only)
     --paper/-2/-3         page, tinted page, raised card
     --ink/-2/-mid/-faint  text, in four weights
     --line/-2             hairlines
     --accent              links, focus, section rules, diagram strokes
     --fill                every call to action

     Components read the *contextual* tokens — --surface, --text, --rule,
     --accent. A band is any block that re-maps those four once:
       .panel--dark  .site-header  .hero  .site-footer  .site-menu
     Everything inside a band therefore inverts on its own, which is why
     almost no component needs a dark variant.

   SIZE
     spacing  --sp-1 … --sp-5         fixed steps
     type     --step--2 … --step-5    fluid, clamp()-based
     layout   --shell --gutter --row --radius

   BUILDING A PAGE — a panel, a shell, components
     <section class="panel panel--paper">
       <div class="shell">
         <header class="intro">
           <h2 class="intro__title">Research Areas</h2>
           <p class="intro__lede">Our research focuses on…</p>
         </header>
       </div>
     </section>
   -------------------------------------------------------------------------- */

/* ------------------------------------------------------------------ reset */
@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  * {
    margin: 0;
  }

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

  ul,
  ol {
    list-style: none;
    padding: 0;
  }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

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

/* ----------------------------------------------------------------- tokens */
@layer tokens {
  :root {
    /* Horizon palette (source values) */
    --p-indigo: #141b41;
    --p-bg: #f4f6fa;
    --p-raised: #ffffff;
    --p-text: #22262e;
    --p-muted: #5b6270;
    --p-rule: #d9dee8;
    --p-link: #0e7c7c;
    --p-teal: #1fa3a3;
    --p-amber: #f5a623;
    --p-violet: #7b5cfa;

    /* Light surfaces */
    --paper: var(--p-bg);
    --paper-2: color-mix(in srgb, var(--p-bg) 95%, var(--p-muted) 5%);
    --paper-3: var(--p-raised);

    /* Light ink */
    --ink: var(--p-text);
    --ink-2: color-mix(in srgb, var(--p-text) 88%, var(--p-muted) 12%);
    --ink-mid: var(--p-muted);
    --ink-faint: color-mix(in srgb, var(--p-muted) 88%, var(--p-text) 12%);

    /* Light hairlines */
    --line: var(--p-rule);
    --line-2: color-mix(in srgb, var(--p-rule) 58%, var(--p-muted) 42%);

    /* The deep band */
    --dark-bg: var(--p-indigo);
    --dark-bg-2: color-mix(in srgb, var(--p-indigo) 86%, #ffffff 14%);
    --dark-text: color-mix(in srgb, #ffffff 91%, var(--p-indigo) 9%);
    --dark-mid: color-mix(in srgb, var(--dark-text) 62%, var(--p-indigo) 38%);
    --dark-faint: color-mix(in srgb, var(--dark-text) 53%, var(--p-indigo) 47%);
    --dark-line: color-mix(in srgb, var(--dark-text) 17%, var(--p-indigo) 83%);
    --dark-line-2: color-mix(in srgb, var(--dark-text) 33%, var(--p-indigo) 67%);

    /* Accents, held by job */
    --link: var(--p-link);
    --accent: var(--link);
    --accent-on-dark: color-mix(in srgb, var(--p-teal) 70%, #ffffff 30%);
    --fill: var(--p-amber);
    --fill-hover: color-mix(in srgb, var(--p-amber) 84%, #ffffff 16%);
    --fill-ink: var(--p-indigo);
    --series-2: var(--p-violet); /* a second series inside one diagram */

    /* Contextual, re-mapped per band below */
    --surface: var(--paper);
    --surface-2: var(--paper-2);
    --surface-3: var(--paper-3);
    --text: var(--ink);
    --text-2: var(--ink-2);
    --text-mid: var(--ink-mid);
    --text-faint: var(--ink-faint);
    --rule: var(--line);
    --rule-2: var(--line-2);
    --quiet: color-mix(in srgb, var(--ink) 5%, transparent);

    color-scheme: light;

    /* Type — Newsreader for display and, for the moment, for text too; Jost for
       the logo. See the TRIAL note on --font-sans below. */
    --font-display: 'Newsreader', ui-serif, Georgia, 'Times New Roman', serif;
    /* TRIAL — the text face is currently set to Newsreader. The previous sans
       stack was: 'Google Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
       Helvetica, Arial, sans-serif. Restore that one value to revert the whole
       site; nothing else depends on this choice. */
    --font-sans: 'Newsreader', ui-serif, Georgia, 'Times New Roman', serif;
    --font-logo: 'Jost', 'Helvetica Neue', Arial, sans-serif;

    /* Fluid type scale */
    --step--2: clamp(0.7rem, 0.68rem + 0.08vw, 0.76rem);
    --step--1: clamp(0.82rem, 0.8rem + 0.1vw, 0.88rem);
    --step-0: clamp(1rem, 0.97rem + 0.16vw, 1.09rem);
    --step-1: clamp(1.12rem, 1.05rem + 0.34vw, 1.32rem);
    --step-2: clamp(1.34rem, 1.2rem + 0.7vw, 1.82rem);
    --step-3: clamp(1.62rem, 1.32rem + 1.44vw, 2.6rem);
    --step-4: clamp(1.95rem, 1.4rem + 2.6vw, 3.75rem);
    --step-5: clamp(2.35rem, 1.45rem + 4.3vw, 5.8rem);

    --sp-1: 0.35rem;
    --sp-2: 0.7rem;
    --sp-3: 1.1rem;
    --sp-4: 1.7rem;
    --sp-5: 2.6rem;

    /* Layout */
    --shell: 1340px;
    --gutter: clamp(1.15rem, 4vw, 3.5rem);
    --row: clamp(3.4rem, 7vw, 7rem);
    --radius: 2px;
    --header-h: 88px;
  }

  /* Automatic dark theme: the same derivation run against the deep end. */
  @media (prefers-color-scheme: dark) {
    :root {
      --paper: color-mix(in srgb, var(--p-indigo) 64%, #000000 36%);
      --paper-2: color-mix(in srgb, var(--p-indigo) 80%, #000000 20%);
      --paper-3: var(--p-indigo);
      --ink: var(--dark-text);
      --ink-2: color-mix(in srgb, var(--dark-text) 90%, var(--p-indigo) 10%);
      --ink-mid: var(--dark-mid);
      --ink-faint: color-mix(in srgb, var(--dark-faint) 74%, var(--dark-text) 26%);
      --line: var(--dark-line);
      --line-2: var(--dark-line-2);
      --link: var(--accent-on-dark);
      --quiet: color-mix(in srgb, var(--dark-text) 6%, transparent);
      color-scheme: dark;
    }
  }

  /* Indigo bands. The palette assigns indigo to headers, hero and footer, so
     those are bands rather than the page; a section can join them. Every
     component inside one adapts on its own. */
  .panel--dark,
  .site-header,
  .hero,
  .site-footer,
  .site-menu {
    --surface: var(--dark-bg);
    --surface-2: var(--dark-bg-2);
    --surface-3: var(--dark-bg-2);
    --text: var(--dark-text);
    --text-2: color-mix(in srgb, var(--dark-text) 80%, var(--p-indigo) 20%);
    --text-mid: var(--dark-mid);
    --text-faint: var(--dark-faint);
    --rule: var(--dark-line);
    --rule-2: var(--dark-line-2);
    --accent: var(--accent-on-dark);
    --quiet: color-mix(in srgb, var(--dark-text) 9%, transparent);
    color: var(--text);
    background-color: var(--surface);
  }
}

/* ------------------------------------------------------------------- base */
@layer base {
  html {
    background-color: var(--paper);
  }

  body {
    font-family: var(--font-sans);
    font-size: var(--step-0);
    line-height: 1.62;
    color: var(--text);
    background-color: var(--paper);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  a {
    color: inherit;
    text-decoration: none;
  }

  ::selection {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
  }

  [id] {
    scroll-margin-top: calc(var(--header-h) + 1.25rem);
  }
}

/* ----------------------------------------------------------------- layout */
@layer layout {
  .shell {
    width: 100%;
    max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  /* The Bootstrap-spelled alias for the same thing. */
  .container {
    width: 100%;
    max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .container-fluid {
    width: 100%;
    padding-inline: var(--gutter);
  }

  .panel {
    --panel-pad: var(--row);
    padding-block: var(--panel-pad);
    background-color: var(--surface);
    color: var(--text);
  }

  .panel--paper {
    --surface: var(--paper);
    background-color: var(--paper);
  }

  /* Bands that follow one another, rather than opening a page, need a lighter
     rhythm than the full --row. */
  .panel--tight {
    --panel-pad: clamp(2.75rem, 5.5vw, 4.5rem);
  }

  /* Declared after --tight and driven by the same --panel-pad, so a tight band
     that opens a page can have just its top edge pulled up toward the header
     above it. Setting padding-block in --tight would otherwise win on source
     order and leave this unable to combine with it. */
  .panel--flush-top {
    padding-top: calc(var(--panel-pad) * 0.5);
  }

  /* Its pair. Not "halve the padding" but "this side is a join": the padding
     drops to a block gap, so two bands carrying one argument close up into a
     single run of content instead of reading as two sections with a seam. It
     carries its own value rather than a fraction of --panel-pad, so tuning the
     band padding does not drag the join along with it. */
  .panel--joined-top {
    padding-top: clamp(0.9rem, 1.8vw, 1.25rem);
  }

  .panel--joined-bottom {
    padding-bottom: clamp(0.9rem, 1.8vw, 1.25rem);
  }

  /* The about page's alternating pair: a column of text beside a plate, with
     the sides mirrored on alternate rows. The text is given the wider column
     and the larger type, so the plate reads as a figure rather than the subject. */
  .exchange {
    display: grid;
    gap: clamp(2rem, 5vw, 3.5rem);
    align-items: start;
  }

  .exchange__text p {
    font-size: var(--step-1);
    line-height: 1.55;
  }

  .exchange__figure {
    min-width: 0;
  }

  /* A graphic sits directly on the panel with no card of its own, so the
     contextual tokens are reset to the theme's own values and the diagram stays
     legible on a light panel and on the violet band alike. */
  .exchange__figure .plate__canvas,
  .method-figure .plate__canvas {
    --text: var(--ink);
    --text-2: var(--ink-2);
    --text-mid: var(--ink-mid);
    --text-faint: var(--ink-faint);
    --rule: var(--line);
    --rule-2: var(--line-2);
    --accent: var(--link);
    --series-2: var(--p-violet);
    --fill: var(--p-amber);
    color: var(--ink);
  }

  .exchange__figure .plate__canvas::before,
  .method-figure .plate__canvas::before {
    display: none;
  }

  /* A centred statement: the page opens with a claim rather than a column of
     body copy, so it is centred and set to a narrow measure. */
  .statement {
    max-width: 38rem;
    margin-inline: auto;
    text-align: center;
  }

  .statement p {
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--text-2);
    text-wrap: pretty;
  }

  .statement p + p {
    margin-top: var(--sp-4);
  }

  .statement p:first-child {
    font-size: var(--step-2);
    line-height: 1.3;
    color: var(--text);
    text-wrap: balance;
  }

  /* The claim when it is a heading rather than body copy: set in the display
     serif and opened up, so a page-long sentence reads as a statement. */
  .statement h2 {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-2);
    letter-spacing: -0.015em;
    line-height: 1.35;
    color: var(--text);
    text-wrap: pretty;
  }

  /* The same claim when it sits beside a figure rather than alone: aligned to
     the start and free to fill the exchange's wider column, so the page opens
     across its full width instead of in a narrow centred measure. */
  .statement--start {
    max-width: none;
    margin-inline: 0;
    text-align: left;
  }

  /* Two blocks side by side, divided by a hairline rather than a gap alone. */
  .two-col {
    display: grid;
    gap: clamp(2.25rem, 5vw, 4rem);
  }

  .two-col p {
    font-size: var(--step-1);
    line-height: 1.55;
  }

  @media (min-width: 900px) {
    .two-col {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 0;
    }

    .two-col > * {
      padding-right: clamp(2.25rem, 5vw, 4rem);
    }

    .two-col > * + * {
      padding-right: 0;
      padding-left: clamp(2.25rem, 5vw, 4rem);
      border-left: 1px solid var(--rule);
    }
  }

  /* When the pair follows another block, a rule across the top closes it off as
     a supporting row rather than a second, separate opening. */
  .two-col--ruled {
    margin-top: clamp(2.25rem, 4.5vw, 3.5rem);
    padding-top: clamp(2.25rem, 4.5vw, 3.5rem);
    border-top: 1px solid var(--rule);
  }

  /* The contact page: the form takes two thirds, the ways in the remaining
     third. Divided by a hairline rather than a gap alone, as the site's other
     pairs are. */
  .contact-split {
    display: grid;
    gap: clamp(2.25rem, 5vw, 4rem);
  }

  @media (min-width: 900px) {
    .contact-split {
      grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
      gap: 0;
    }

    .contact-split > * {
      padding-right: clamp(2.25rem, 5vw, 4rem);
    }

    .contact-split > * + * {
      padding-right: 0;
      padding-left: clamp(2.25rem, 5vw, 4rem);
      border-left: 1px solid var(--rule);
    }
  }

  /* Ways in: the contact page's short list of routes, each a heading and a line
     of explanation. Divided by hairlines for the same reason the pair is. */
  .routes {
    display: grid;
  }

  .route + .route {
    margin-top: clamp(2.25rem, 4.5vw, 3.5rem);
    padding-top: clamp(2.25rem, 4.5vw, 3.5rem);
    border-top: 1px solid var(--rule);
  }

  .route__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-2);
    letter-spacing: -0.015em;
    line-height: 1.2;
  }

  .route__text {
    margin-top: var(--sp-3);
    max-width: 62ch;
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--text-2);
    text-wrap: pretty;
  }

  .route__action {
    margin-top: var(--sp-4);
  }

  @media (min-width: 900px) {
    .exchange {
      grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
    }

    /* No cap and no push to the outer edge any more: the graphic fills the
       whole of its column, so the pair reads as text beside graphic. */
    .exchange__figure {
      justify-self: stretch;
    }

    /* The mirrored rows put the plate on the left without moving it ahead of
       the text in the document, so mobile still reads text first. */
    .exchange--reverse .exchange__text {
      order: 2;
    }

    .exchange--reverse .exchange__figure {
      order: 1;
      justify-self: start;
    }
  }
}

/* ------------------------------------------------------------------- type */
@layer type {
  .intro {
    margin-bottom: clamp(2rem, 5vw, 3.5rem);
  }

  .intro__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-3);
    letter-spacing: -0.024em;
    line-height: 1.05;
    text-wrap: balance;
  }

  .intro__lede {
    margin-top: var(--sp-4);
    max-width: 112ch;
    font-size: var(--step-1);
    line-height: 1.5;
    color: var(--text-mid);
    text-wrap: pretty;
  }

  /* Interior-page copy: headings and body text outside the homepage's blocks. */
  .prose {
    max-width: 68ch;
  }

  .prose h2 {
    margin-top: clamp(2.5rem, 5vw, 3.5rem);
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-3);
    letter-spacing: -0.024em;
    line-height: 1.1;
  }

  .prose h2:first-child {
    margin-top: 0;
  }

  .prose p {
    margin-top: var(--sp-4);
    color: var(--text-2);
    text-wrap: pretty;
  }
}

/* ------------------------------------------------------------- components */
@layer components {
  /* Header ------------------------------------------------------------- */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 90;
    background: color-mix(in srgb, var(--dark-bg) 96%, transparent);
    backdrop-filter: saturate(140%) blur(10px);
    border-bottom: 1px solid var(--rule);
  }

  /* Scroll-aware header. At the top of the page the bar is out of the way and
     the hero carries the identity instead; once the reader is under way it
     slides in. Gated on `.js` so that without JavaScript the bar simply stays
     put, in flow, and the navigation is never lost.

     Faded with opacity rather than `visibility` on purpose: an opacity-0 link
     is still focusable, so tabbing into the bar reveals it via `:focus-within`
     and the navigation stays reachable by keyboard. */
  .js .site-header {
    position: fixed;
    inset: 0 0 auto 0;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-100%);
    transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .js .site-header.is-scrolled,
  .js .site-header:focus-within {
    opacity: 1;
    pointer-events: auto;
    transform: none;
  }

  /* A page can opt out of the scroll behaviour and keep the bar in place. It
     stays in flow, so interior pages begin underneath it rather than behind. */
  .js .site-header--static {
    position: sticky;
    opacity: 1;
    pointer-events: auto;
    transform: none;
  }

  .site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-4);
    min-height: var(--header-h);
    width: 100%;
    max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  /* Logo --------------------------------------------------------------- */
  .logo {
    position: relative;
    display: inline-block;
    padding: 0.5em 0 0 0.5em;
    font-family: var(--font-logo);
    font-weight: 600;
    font-size: 1em;
    line-height: 1.15;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--text);
    white-space: nowrap;
  }

  .logo::before {
    content: '';
    position: absolute;
    inset: 0 auto auto 0;
    width: 1em;
    height: 1em;
    border-top: 0.12em solid var(--fill);
    border-left: 0.12em solid var(--fill);
  }

  .logo span {
    color: var(--fill);
  }

  .brand {
    display: inline-flex;
    align-items: center;
    flex: none;
  }

  .brand--header .logo {
    font-size: 1.2rem;
    line-height: 1.05;
    letter-spacing: 0.12em;
  }

  /* The hero wordmark: the identity at the top of the page, before the bar.
     A step up from the footer mark so it reads as the page's masthead. */
  .brand--hero {
    margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
  }

  .brand--hero .logo {
    font-size: clamp(1.75rem, 4.5vw, 3rem);
    line-height: 1.1;
    letter-spacing: 0.14em;
  }

  /* Navigation --------------------------------------------------------- */
  .site-nav {
    margin-left: auto;
  }

  .site-nav__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: clamp(0.75rem, 2vw, 1.9rem);
  }

  .site-nav__link {
    position: relative;
    display: inline-block;
    padding-block: 0.4rem;
    font-family: var(--font-logo);
    font-size: var(--step-0);
    color: var(--text-mid);
    transition: color 0.2s ease;
  }

  .site-nav__link::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--fill);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .site-nav__link:hover {
    color: var(--text);
  }

  .site-nav__link:hover::after {
    transform: scaleX(1);
  }

  /* Mobile menu. The hamburger is the navigation on small screens (see the
     responsive layer); the panel itself is a full-screen indigo band. */
  .menu-toggle {
    display: none;
    align-items: center;
    gap: 0.55rem;
    padding: 0.5rem 0.75rem;
    background: none;
    border: 1px solid var(--rule-2);
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--step--2);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    cursor: pointer;
  }

  .menu-toggle__bars {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 18px;
    height: 12px;
  }

  .menu-toggle__bars span {
    display: block;
    height: 1.5px;
    background: currentColor;
    transition: transform 0.3s ease;
  }

  .menu-toggle[aria-expanded='true'] .menu-toggle__bars span:first-child {
    transform: translateY(2.75px) rotate(45deg);
  }

  .menu-toggle[aria-expanded='true'] .menu-toggle__bars span:last-child {
    transform: translateY(-2.75px) rotate(-45deg);
  }

  .site-menu {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: flex;
    flex-direction: column;
    padding: 5rem var(--gutter) 2.5rem;
    overflow-y: auto;
    animation: menu-in 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .site-menu[hidden] {
    display: none;
  }

  .site-menu__list {
    display: flex;
    flex-direction: column;
  }

  .site-menu__link {
    display: flex;
    align-items: baseline;
    gap: 1rem;
    padding-block: 0.85rem;
    border-bottom: 1px solid var(--rule);
    font-family: var(--font-display);
    font-size: var(--step-3);
    letter-spacing: -0.02em;
    transition: color 0.2s ease, padding-left 0.25s ease;
  }

  .site-menu__link:hover,
  .site-menu__link:focus-visible {
    color: var(--accent);
    padding-left: 0.4rem;
  }

  .site-menu__num {
    font-family: var(--font-sans);
    font-size: var(--step--2);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.1em;
    color: var(--text-faint);
  }

  .site-menu.is-open .site-menu__list > li {
    animation: menu-item-in 0.4s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
    animation-delay: calc(var(--i, 0) * 45ms + 40ms);
  }

  @keyframes menu-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  @keyframes menu-item-in {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }

  /* Contact form -------------------------------------------------------- */
  /* A raised card on the paper page: fields laid in a two-column grid, controls
     as filled boxes with a clear focus edge, and one amber action. */
  .contact-form {
    padding: clamp(1.5rem, 3.5vw, 2.5rem);
    background: var(--surface-3);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
  }

  .field-grid {
    display: grid;
    gap: clamp(1.1rem, 2.4vw, 1.6rem);
  }

  @media (min-width: 640px) {
    .field-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .field--wide {
      grid-column: 1 / -1;
    }
  }

  .field {
    display: block;
    min-width: 0;
  }

  /* `display: block` above would otherwise beat the browser's own [hidden]
     rule, so a field hidden by script (an internship's resume on any other
     programme) would still show. Same guard as .site-menu[hidden]. */
  .field[hidden] {
    display: none;
  }

  .field__label {
    display: block;
    margin-bottom: 0.45rem;
    font-family: var(--font-sans);
    font-size: var(--step--2);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-mid);
  }

  .field__control {
    display: block;
    width: 100%;
    padding: 0.72rem 0.85rem;
    border: 1px solid var(--rule-2);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--step-0);
    line-height: 1.4;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
  }

  .field__control::placeholder {
    color: var(--text-faint);
  }

  .field__control:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
  }

  /* A message box needs more room than a single line, and only a vertical drag:
     stretching it sideways would break the grid. */
  textarea.field__control {
    min-height: 7rem;
    resize: vertical;
  }

  /* Suppressing the native arrow means drawing one: two gradients, arranged as
     the two halves of a chevron. */
  select.field__control {
    appearance: none;
    padding-right: 2.4rem;
    background-image:
      linear-gradient(45deg, transparent 50%, currentColor 50%),
      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right 1rem top 52%, right 0.6rem top 52%;
    background-size: 0.42rem 0.42rem, 0.42rem 0.42rem;
    background-repeat: no-repeat;
  }

  /* A file field borrows the same box; only its own native button needs
     bringing into the site's type. */
  input[type='file'].field__control {
    padding-block: 0.5rem;
    color: var(--text-mid);
  }

  input[type='file'].field__control::file-selector-button {
    margin-right: 0.8rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--rule-2);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 0.94rem;
    cursor: pointer;
  }

  /* The action row and the status line are shared by every form on the site. */
  .form-action {
    margin-top: clamp(1.5rem, 3vw, 2rem);
  }

  .form-status {
    margin-top: var(--sp-3);
    font-size: var(--step-0);
    color: var(--accent);
  }

  /* On the narrowest screens the action fills the card rather than sitting as a
     small target in the corner. */
  @media (max-width: 639px) {
    .form-action .btn {
      width: 100%;
      justify-content: center;
    }
  }

  /* Buttons ------------------------------------------------------------ */
  .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.85rem 1.35rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    /* A <button> does not inherit the page's type, so the site's face is named
       here; without it every button falls back to the browser's UI font. */
    font-family: inherit;
    font-size: 0.94rem;
    font-weight: 500;
    line-height: 1.2;
    cursor: pointer;
    transition: background-color 0.22s ease, color 0.22s ease, border-color 0.22s ease;
  }

  .btn__arrow {
    display: inline-block;
    transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .btn:hover .btn__arrow {
    transform: translateX(3px);
  }

  /* While a button's action is in flight the arrow gives way to a spinner, so
     the control itself shows that the waiting is ours and not the user's. */
  .btn__spinner {
    display: none;
    width: 1em;
    height: 1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
  }

  .btn.is-sending .btn__arrow {
    display: none;
  }

  .btn.is-sending .btn__spinner {
    display: inline-block;
  }

  /* The palette prescribes one accent for every call to action: amber. */
  .btn--primary {
    background: var(--fill);
    color: var(--fill-ink);
    border-color: var(--fill);
  }

  .btn--primary:hover {
    background: var(--fill-hover);
    border-color: var(--fill-hover);
    color: var(--fill-ink);
  }

  /* Modal ---------------------------------------------------------------- */
  /* One dialog serves every programme. A native <dialog> brings the focus
     trap, the Escape key and the inert background; only the shell and its
     heading are styled here, the fields being the site's own. */
  .modal {
    width: 100%;
    max-width: none;
    height: 100%;
    height: 100dvh;
    max-height: none;
    margin: 0;
    padding: clamp(1.5rem, 4vw, 3rem) var(--gutter);
    overflow: auto;
    border: 0;
    border-radius: 0;
    background: var(--surface-3);
    color: var(--text);
  }

  /* Full-bleed shell, but the form itself keeps a readable measure. */
  .modal > * {
    max-width: 56rem;
    margin-inline: auto;
  }

  .modal::backdrop {
    background: color-mix(in srgb, var(--p-indigo) 62%, transparent);
  }

  .modal__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-bottom: clamp(1.25rem, 2.5vw, 1.75rem);
  }

  .modal__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-2);
    letter-spacing: -0.02em;
    line-height: 1.15;
  }

  .modal__lede {
    margin-top: 0.4rem;
    color: var(--text-mid);
  }

  .modal__close {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid var(--rule-2);
    border-radius: var(--radius);
    background: none;
    color: var(--text-mid);
    font-family: inherit;
    font-size: 1.35rem;
    line-height: 1;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
  }

  .modal__close:hover {
    color: var(--text);
    border-color: var(--text-mid);
  }

  /* Hero --------------------------------------------------------------- */
  /* Fullscreen hero: the fold belongs to the statement and the plate. */
  .hero {
    display: grid;
    align-items: center;
    min-height: 100vh;
    min-height: 100svh;
    background: linear-gradient(160deg, #141b41 0%, #141b41 55%, #1b3d63 100%);
    padding-top: clamp(3rem, 6vw, 5rem);
    padding-bottom: clamp(3rem, 6vw, 5rem);
  }

  .hero__inner {
    display: grid;
    gap: clamp(2rem, 5vw, 3.5rem);
    align-items: center;
  }

  .hero__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-4);
    letter-spacing: -0.03em;
    line-height: 1.01;
    text-wrap: balance;
  }

  .hero__line {
    display: block;
  }

  /* Air between the two statements, so the accent line reads as a second
     sentence rather than a continuation of the first. */
  .hero__line + .hero__line {
    margin-top: clamp(0.7rem, 1.8vw, 1.35rem);
  }

  .hero__line--accent {
    color: var(--accent);
    font-style: italic;
  }

  .hero__figure {
    min-width: 0;
  }

  /* Plates (diagrams) --------------------------------------------------- */
  .plate {
    margin: 0;
  }

  .plate__canvas {
    position: relative;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: transparent;
  }

  .plate__canvas::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
      linear-gradient(to right, var(--rule) 1px, transparent 1px),
      linear-gradient(to bottom, var(--rule) 1px, transparent 1px);
    background-size: 56px 56px;
    opacity: 0.5;
    pointer-events: none;
  }

  .figure-svg {
    position: relative;
    width: 100%;
    height: auto;
  }

  .plate--hero .plate__canvas {
    aspect-ratio: 760 / 620;
  }

  /* The about page's figure is drawn on the page itself rather than inside a
     plate: no border, so it is free to run the full width of its column. This
     has to be declared here, in the components layer, because `.plate__canvas`
     lives here too and a layer outranks specificity in the layers below. */
  .plate--plain .plate__canvas {
    border: 0;
  }

  /* The about page's graphic --------------------------------------------- */
  /* Three rings leave a single origin and the markers sitting on them answer
     in turn. There is no card and no border, so the figure reads as part of
     the page rather than a framed picture on it; the movement itself lives in
     the motion layer, where it can be withheld. */
  .pulse-guide {
    fill: none;
    stroke: var(--rule-2);
    stroke-width: 1;
    stroke-dasharray: 3 5;
  }

  .pulse-node {
    fill: var(--text-faint);
  }

  .pulse-node--accent {
    fill: var(--accent);
  }

  /* The origin sits on top of the guide rings, so it carries a halo in the
     surface colour to keep its edge legible over them. */
  .pulse-origin {
    fill: var(--text);
    stroke: var(--surface);
    stroke-width: 1.5;
  }

  .pulse-ring {
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.1;
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
  }

  /* Diagram geometry: SVG strokes and nodes ----------------------------- */
  .g-tick {
    stroke: var(--rule-2);
    stroke-width: 1;
    stroke-dasharray: 2 5;
  }

  .g-tick--accent {
    stroke: var(--accent);
    stroke-width: 1.2;
    stroke-dasharray: none;
  }

  .g-edge {
    stroke: var(--rule-2);
    stroke-width: 1.2;
    fill: none;
  }

  .g-edge--accent {
    stroke: var(--accent);
    stroke-width: 1.7;
  }

  /* A second series inside the same plate. */
  .g-edge--alt {
    stroke: var(--series-2);
    stroke-width: 1.7;
  }

  /* A faint dashed guide: a ring, a boundary, an axis. */
  .g-guide {
    fill: none;
    stroke: var(--rule-2);
    stroke-width: 1;
    stroke-dasharray: 3 5;
  }

  /* Soft tints, so a plate carries colour without becoming loud. */
  .g-fill {
    stroke: none;
    fill: var(--accent);
    fill-opacity: 0.08;
  }

  .g-zone {
    stroke: none;
    fill: var(--accent);
    fill-opacity: 0.07;
  }

  .g-zone--alt {
    fill: var(--series-2);
  }

  .g-path {
    fill: none;
    stroke: var(--text-faint);
    stroke-width: 1.25;
    stroke-linecap: round;
  }

  .g-path--firm {
    stroke: var(--text-mid);
    stroke-width: 1.5;
  }

  .g-path--accent {
    stroke: var(--accent);
    stroke-width: 1.9;
  }

  .g-node {
    fill: var(--text-faint);
  }

  .g-node--hub {
    fill: var(--text);
  }

  .g-node--accent {
    fill: var(--accent);
  }

  /* The node the plate is about. Amber is the palette's action colour, but it
     only clears contrast on the deep band (about 2:1 on the page), so on a
     light plate the node is set in ink and amber becomes the ring around it. */
  .g-node--key {
    fill: var(--text);
    stroke: var(--fill);
    stroke-width: 2.5;
  }

  .g-node--alt {
    fill: var(--series-2);
  }

  .g-node--origin {
    fill: var(--accent);
    stroke: var(--surface);
    stroke-width: 1.5;
  }

  /* A slow ring that leaves the key node, so a static plate still breathes. */
  .g-ping {
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.5;
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
  }

  /* Interior page header. No ground of its own, so it reads as part of the
     page: the name in the header band's own ink, underlined in the amber the
     navigation uses, with the trail in the top-left corner. */
  .page-hero {
    padding-block: clamp(1.75rem, 3.5vw, 2.5rem);
  }

  /* The trail sits in the corner on every interior page and is given generous
     air beneath it, so it reads as belonging to the page rather than to the
     heading it introduces. */
  .page-hero .crumbs {
    margin-bottom: clamp(3rem, 7vw, 6rem);
    text-align: left;
  }

  /* Uppercased in CSS, not in the markup, so the heading still reads as words
     to a screen reader. The rule under it carries a weight that reads at any
     size without shouting. */
  .page-hero__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-4);
    letter-spacing: 0.03em;
    line-height: 1.15;
    color: var(--p-indigo);
    text-align: center;
    text-transform: uppercase;
    text-wrap: balance;
    text-decoration: underline;
    text-decoration-color: var(--fill);
    text-decoration-thickness: max(2px, 0.06em);
    text-underline-offset: 0.14em;
  }

  /* The one-line summary a page may supply under its title, at reading size and
     in the softer ink, so it reads as a continuation of the heading. */
  .page-hero__lede {
    margin-top: var(--sp-4);
    margin-inline: auto;
    max-width: 75ch;
    font-size: var(--step-1);
    line-height: 1.45;
    color: var(--text-2);
    text-align: center;
    text-wrap: pretty;
  }

  /* On the dark page the band ink would vanish, so the heading steps up. */
  @media (prefers-color-scheme: dark) {
    .page-hero__title {
      color: var(--dark-text);
    }
  }

  /* Breadcrumb: the same micro-label register as the site's other small text. */
  .crumbs__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    font-family: var(--font-sans);
    font-size: var(--step--1);
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .crumbs__item {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    color: var(--text);
  }

  .crumbs__item + .crumbs__item::before {
    content: '/';
    color: var(--rule-2);
  }

  .crumbs__link {
    color: var(--text-mid);
    transition: color 0.2s ease;
  }

  .crumbs__link:hover {
    color: var(--text);
  }

  /* Research areas ------------------------------------------------------ */
  .domains {
    counter-reset: domain;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
    gap: 1px;
    background: var(--rule);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    overflow: hidden;
  }

  .domain {
    counter-increment: domain;
    display: grid;
    align-content: start;
    gap: var(--sp-3);
    padding: clamp(1.4rem, 3vw, 2rem);
    background: var(--surface-3);
    transition: background-color 0.3s ease;
  }

  .domain::before {
    content: counter(domain, decimal-leading-zero);
    font-family: var(--font-sans);
    font-size: var(--step--2);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.12em;
    color: var(--text-faint);
  }

  .domain:hover {
    background: var(--surface-2);
  }

  .domain__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-2);
    letter-spacing: -0.02em;
    line-height: 1.1;
  }

  .domain__list {
    display: grid;
  }

  .domain__list li {
    padding-block: 0.55rem;
    border-top: 1px solid var(--rule);
    font-size: var(--step-0);
    line-height: 1.45;
    color: var(--text-mid);
    text-wrap: pretty;
  }

  /* Each method name on the homepage leads to that method's own page. */
  .domain__list a {
    transition: color 0.2s ease;
  }

  .domain__list a:hover {
    color: var(--text);
  }

  /* Numbered rows, in full ----------------------------------------------- */
  /* A description in three quarters of the row and a side column in the
     remaining quarter, ruled off from the row below. Two pages use it: the
     research page, whose side column lists the methods each domain covers, and
     the programmes page, whose side column carries the details of the next run.
     Each writes its own markup for the side column; the row itself is shared.
     On the research page the numbering continues the homepage grid's, so "01"
     means the same domain in both places. */
  .domains-full,
  .programmes {
    counter-reset: record;
  }

  .domain-full,
  .programme {
    counter-increment: record;
  }

  .domain-full + .domain-full,
  .programme + .programme {
    margin-top: clamp(2.5rem, 5vw, 4rem);
    padding-top: clamp(2.5rem, 5vw, 4rem);
    border-top: 1px solid var(--rule);
  }

  .domain-full__main::before,
  .programme__main::before {
    display: block;
    content: counter(record, decimal-leading-zero);
    margin-bottom: var(--sp-2);
    font-family: var(--font-sans);
    font-size: var(--step--2);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.12em;
    color: var(--text-faint);
  }

  .domain-full__title,
  .programme__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-3);
    letter-spacing: -0.02em;
    line-height: 1.1;
  }

  /* The one-line summary under the title, a step above the body copy below it. */
  .domain-full__lede,
  .programme__lede {
    margin-top: var(--sp-3);
    font-size: var(--step-2);
    line-height: 1.35;
    color: var(--text-2);
    text-wrap: pretty;
  }

  /* Body copy is set the way the rest of the site sets copy that shares its
     space with a column beside it: a step up from the prose size, no measure
     of its own, so it fills the three-quarter column it is given. */
  .domain-full__text,
  .programme__text {
    margin-top: var(--sp-4);
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--text-2);
    text-wrap: pretty;
  }

  .domain-full__aside,
  .programme__aside {
    margin-top: clamp(1.75rem, 4vw, 2.5rem);
  }

  .domain-full__heading,
  .programme__heading {
    font-family: var(--font-sans);
    font-size: var(--step-0);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-mid);
    text-wrap: balance;
  }

  /* The method list follows the homepage's, one step larger: the column beside
     a full description can afford the room the homepage's three-up cards
     cannot. */
  .domain-full__list {
    display: grid;
    margin-top: var(--sp-3);
  }

  .domain-full__list li {
    padding-block: 0.55rem;
    border-top: 1px solid var(--rule);
    font-size: var(--step-0);
    line-height: 1.45;
    color: var(--text-mid);
    text-wrap: pretty;
  }

  /* Each method name on the research page leads to that method's own page. */
  .domain-full__list a {
    transition: color 0.2s ease;
  }

  .domain-full__list a:hover {
    color: var(--text);
  }

  /* Two columns once there is room: three quarters to the description, one to
     the side column, divided by a hairline rather than by the gap alone. */
  @media (min-width: 900px) {
    .domain-full,
    .programme {
      display: grid;
      grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
      gap: 0;
      align-items: start;
    }

    .domain-full__main,
    .programme__main {
      padding-right: clamp(1.5rem, 3vw, 2.5rem);
    }

    .domain-full__aside,
    .programme__aside {
      margin-top: 0;
      padding-left: clamp(1.5rem, 3vw, 2.5rem);
      border-left: 1px solid var(--rule);
    }
  }

  /* Programmes ----------------------------------------------------------- */
  /* A programme's side column holds its practical details rather than a list:
     the same ruled register as a method's "at a glance", and a way to join. */
  .programme__details {
    display: grid;
    margin-top: var(--sp-3);
  }

  .programme__row {
    padding-block: 0.55rem;
    border-top: 1px solid var(--rule);
  }

  .programme__key {
    font-family: var(--font-sans);
    font-size: var(--step--2);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  .programme__value {
    margin-top: 0.3rem;
    font-size: var(--step--1);
    line-height: 1.45;
    color: var(--text-mid);
    text-wrap: pretty;
  }

  /* The date is the one thing a reader scans this column for, so it is set at
     reading size in the display face rather than as another detail line. */
  .programme__date {
    margin-top: var(--sp-3);
    padding-block: 0.55rem;
    border-top: 1px solid var(--rule);
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-1);
    letter-spacing: -0.014em;
    line-height: 1.25;
  }

  /* The column's one call to action, set as a real button rather than a line of
     text. It is a `.btn`, so it inherits the site's single amber action. */
  .programme__cta {
    margin-top: var(--sp-3);
  }

  /* A two-column row. Which fraction the first column takes is named by the
     modifier, because the page's two rows are deliberately different: the
     figure takes two thirds, the copy takes three quarters. */
  .method-split {
    display: grid;
    gap: clamp(1.75rem, 4vw, 3rem);
  }

  .method-split > * {
    min-width: 0;
  }

  @media (min-width: 900px) {
    .method-split--figure {
      grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
      align-items: start;
    }

    .method-split--body {
      grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
      gap: 0;
      align-items: start;
    }

    /* The hairline belongs to the inner edge of the narrow column, so both rows
       divide their columns the same way however wide the halves are. */
    .method-split--body > .method-body {
      padding-right: clamp(1.5rem, 3vw, 2.5rem);
    }

    .method-split--body > .method-index {
      padding-left: clamp(1.5rem, 3vw, 2.5rem);
      border-left: 1px solid var(--rule);
    }
  }

  .method-index__title {
    font-family: var(--font-sans);
    font-size: var(--step--1);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-mid);
  }

  /* Every method, so a reader can move sideways from any one of them. The
     current method is the only one set in full ink. */
  .method-links {
    display: grid;
    margin-top: var(--sp-3);
  }

  .method-link {
    display: block;
    padding-block: 0.55rem;
    border-top: 1px solid var(--rule);
    font-size: var(--step--1);
    line-height: 1.45;
    color: var(--text-mid);
    text-wrap: pretty;
    transition: color 0.2s ease;
  }

  .method-link:hover {
    color: var(--text);
  }

  .method-link[aria-current='page'] {
    color: var(--text);
    font-weight: 500;
  }

  /* Previous and next, at the foot of the page. Ruled rows rather than boxes:
     the rule closes the content above, each row is ruled off beneath, and the
     direction is carried on its own side — so the pair reads as a continuation
     of the page rather than as two buttons bolted to the end of it. */
  .method-pager {
    display: grid;
    gap: 0;
    margin-top: clamp(2rem, 4vw, 3rem);
    border-top: 1px solid var(--rule);
  }

  .method-pager__link {
    display: grid;
    gap: 0.2rem;
    padding-block: 1rem;
    border-bottom: 1px solid var(--rule);
    transition: color 0.2s ease;
  }

  .method-pager__link:hover {
    color: var(--accent);
  }

  .method-pager__link--next {
    text-align: right;
  }

  .method-pager__dir {
    font-family: var(--font-sans);
    font-size: var(--step--2);
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  .method-pager__name {
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-1);
    letter-spacing: -0.014em;
    line-height: 1.25;
  }

  /* Two columns once there is room. An empty cell stands in for a missing
     direction, so "next" keeps the right-hand column on the first method and
     "previous" keeps the left on the last. */
  @media (min-width: 900px) {
    .method-pager {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      column-gap: var(--sp-5);
    }
  }

  /* At a glance: the three questions worth asking of any method, as key/value
     pairs rather than prose. */
  /* The cone's bands are tints. On the deep band a 7% wash reads; on paper it
     fades into the page, so the figure asks for a little more of the accent. */
  .method-figure .g-zone {
    fill-opacity: 0.13;
  }

  .glance {
    border-top: 1px solid var(--rule);
  }

  .glance__title {
    padding-block: var(--sp-3);
    font-family: var(--font-sans);
    font-size: var(--step--1);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-mid);
  }

  .glance__row {
    padding-block: var(--sp-3);
    border-top: 1px solid var(--rule);
  }

  .glance__key {
    font-family: var(--font-sans);
    font-size: var(--step--2);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  .glance__value {
    margin-top: 0.4rem;
    font-size: var(--step-0);
    line-height: 1.5;
    color: var(--text-2);
    text-wrap: pretty;
  }

  /* The method's own copy: what it is, then how the lab works with it. The
     definition is set a step up, so it reads as the method's own statement
     rather than as the first paragraph of a section. */
  .method-body__definition {
    font-size: var(--step-2);
    line-height: 1.45;
    color: var(--text);
    text-wrap: pretty;
  }

  .method-body__heading {
    margin-top: clamp(2.25rem, 4.5vw, 3.5rem);
    font-family: var(--font-display);
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--step-2);
    letter-spacing: -0.015em;
    line-height: 1.2;
  }

  .method-body__intro {
    margin-top: var(--sp-3);
    max-width: 62ch;
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--text-2);
    text-wrap: pretty;
  }

  /* The points are hung off a short accent rule rather than a round bullet, so
     they stay in the same register as the rules the rest of the page uses. */
  .method-body__points {
    display: grid;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
    max-width: 62ch;
  }

  .method-body__points li {
    position: relative;
    padding-left: 1.6rem;
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--text-2);
    text-wrap: pretty;
  }

  .method-body__points li::before {
    content: '';
    position: absolute;
    top: 0.72em;
    left: 0;
    width: 0.7rem;
    height: 2px;
    background: var(--accent);
  }


  /* Footer -------------------------------------------------------------- */
  /* Not a band with a masthead any more, just one line of back matter: the
     legal links, the copyright and a way back to the top. */
  .site-footer {
    background: var(--dark-bg);
    color: var(--dark-text);
    padding-block: clamp(1.1rem, 2.2vw, 1.5rem);
  }

  .site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem clamp(1.25rem, 3vw, 2.25rem);
  }

  .site-footer__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem clamp(1rem, 2.5vw, 1.75rem);
  }

  .site-footer__link {
    font-family: var(--font-logo);
    font-size: var(--step-0);
    color: var(--dark-mid);
    transition: color 0.2s ease;
  }

  .site-footer__link:hover {
    color: var(--accent-on-dark);
  }

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

  /* An anchor, not a script: the document already scrolls smoothly (see the
     reset), and the browser treats a bare "#top" as the top of the page. It is
     pushed to the far edge, away from the back matter that sits on the left. */
  .site-footer__top-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-left: auto;
  }
}

/* ------------------------------------------------------------- responsive */
@layer responsive {
  @media (min-width: 1080px) {
    .hero__inner {
      grid-template-columns: minmax(0, 1.06fr) minmax(0, 0.94fr);
      gap: clamp(2.5rem, 5vw, 4.5rem);
    }

    /* Three columns: the five domains fill two rows and the plate completes
       the sixth cell, so the grid has no ragged hole left in it. */
    .domains {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  /* Stacked below the copy, the hero plate would otherwise dominate the fold
     at tablet widths, so it is capped and centred instead. */
  @media (max-width: 1079px) {
    .hero__figure {
      width: 100%;
      max-width: 34rem;
      margin-inline: auto;
    }
  }

  @media (max-width: 899px) {
    /* Unlike the desktop bar, which hides at the top of the page, the bar here
       is the only navigation on offer, so it stays put. */
    .js .site-header {
      opacity: 1;
      pointer-events: auto;
      transform: none;
    }

    .site-header__inner {
      min-height: auto;
      padding-block: 0.7rem;
    }

    /* The bar carries the wordmark on small screens, so the hero need not
       repeat it. */
    .brand--hero {
      display: none;
    }

    .site-nav {
      display: none;
    }

    .menu-toggle {
      display: inline-flex;
      margin-left: auto;
    }

    /* Without JavaScript the menu cannot open, so the plain links stay on show
       and the hamburger is not offered. */
    .no-js .site-nav {
      display: block;
      width: 100%;
      margin-left: 0;
    }

    .no-js .menu-toggle {
      display: none;
    }

    /* Clear the bar, which overlays the fold, with room to breathe beneath it. */
    .hero {
      padding-top: 7rem;
      padding-bottom: 5rem;
    }

    .hero__inner {
      gap: 2.75rem;
    }

    /* The headline scales with the viewport rather than sitting at its desktop
       floor, so it always fits the screen it is on. */
    .hero__title {
      font-size: clamp(1.55rem, 7.5vw, 2.5rem);
      line-height: 1.1;
      letter-spacing: -0.02em;
    }

    /* A little more air between the statement and the question it answers. */
    .hero__line + .hero__line {
      margin-top: 1.5rem;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      transition: none !important;
      animation: none !important;
    }
  }
}

/* ----------------------------------------------------------------- motion */
@layer motion {
  /* The contact form's submit spinner. Like every animation here it is stilled
     under reduced motion by the global rule in the responsive layer. */
  .btn.is-sending .btn__spinner {
    animation: spin 0.7s linear infinite;
  }

  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }

  @keyframes g-draw {
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes g-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  @keyframes g-ping {
    0% {
      transform: scale(1);
      opacity: 0.5;
    }
    70% {
      transform: scale(2.6);
      opacity: 0;
    }
    100% {
      transform: scale(2.6);
      opacity: 0;
    }
  }

  @media (prefers-reduced-motion: no-preference) {
    /* The hero plate draws itself as the page settles. */
    .g-path {
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
      animation: g-draw 1.5s cubic-bezier(0.33, 0.9, 0.4, 1) forwards;
      animation-delay: var(--d, 0s);
    }

    .g-ensemble {
      opacity: 0;
      animation: g-fade-in 1.5s ease forwards;
      animation-delay: 1.4s;
    }

    .g-markers .g-node {
      animation: g-fade-in 0.6s ease backwards;
      animation-delay: var(--delay, 0s);
    }

    /* Plates further down the page wait until they are in view. Nothing is
       hidden unless `.js` is present, so the plates are never lost. */
    .js .g-draw {
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
    }

    .js .g-plot .g-node {
      opacity: 0;
    }

    .js [data-reveal].is-revealed .g-draw {
      animation: g-draw 1.3s cubic-bezier(0.33, 0.9, 0.4, 1) forwards;
      animation-delay: var(--d, 0s);
    }

    .js [data-reveal].is-revealed .g-plot .g-node {
      animation: g-fade-in 0.55s ease forwards;
      animation-delay: var(--delay, 0s);
    }

    .js [data-reveal].is-revealed .g-ping {
      animation: g-ping 3s ease-out 1.6s infinite;
    }
  }

  /* Under reduced motion nothing hides and nothing moves. */
  @media (prefers-reduced-motion: reduce) {
    .js .g-draw {
      stroke-dasharray: none;
      stroke-dashoffset: 0;
    }

    .js .g-plot .g-node {
      opacity: 1;
    }
  }

  /* The about page's graphic -------------------------------------------- */
  /* Unlike the plates, which arrive once and settle, this one keeps moving: it
     is the only figure on the site that stays alive after it has been read.
     One ring leaves the origin every 1.8s, the markers brighten in the order
     the ring reaches them, and the guide rings drift underneath. */
  @keyframes pulse-out {
    0% {
      transform: scale(0.14);
      opacity: 0.55;
    }
    70% {
      opacity: 0.14;
    }
    100% {
      transform: scale(1);
      opacity: 0;
    }
  }

  @keyframes pulse-node {
    0%,
    100% {
      opacity: 0.3;
    }
    12% {
      opacity: 1;
    }
  }

  @keyframes pulse-drift {
    to {
      stroke-dashoffset: -160;
    }
  }

  @media (prefers-reduced-motion: no-preference) {
    .pulse-ring {
      animation: pulse-out 5.4s ease-out infinite;
      animation-delay: var(--delay, 0s);
    }

    .pulse-node,
    .pulse-node--accent {
      animation: pulse-node 5.4s ease-in-out infinite;
      animation-delay: var(--delay, 0s);
    }

    /* The offset is a whole number of dash periods (3 + 5), so the loop
       closes on itself with no visible jump. */
    .pulse-drift {
      animation: pulse-drift 40s linear infinite;
    }
  }
}
