/*! FivePaths microsite base, release 3.5.0
    https://cdn.fivepaths.com/microsite/v3/base-3.5.0.css

    A structural framework for small sites. One shape, many brands: the
    components, the spacing and type roles, the dark-band machinery and the
    accessibility floor are shared; the palette and the typeface are tokens a
    site replaces. The FivePaths colours below are the default, not the system.

      <link rel="preconnect" href="https://cdn.fivepaths.com" crossorigin>
      <link rel="preload" as="font" type="font/woff2" crossorigin
            href="https://cdn.fivepaths.com/microsite/v3/fonts/overpass-latin-wght-normal-2.woff2">
      <link rel="stylesheet" href="https://cdn.fivepaths.com/microsite/v3/base-3.5.0.css">
      <link rel="stylesheet" href="/assets/site.css">

    ── WHAT V3 CHANGES ───────────────────────────────────────────────────────
    v2 styled about sixty classes. v3 styles elements and their position, and
    keeps five. `<div class="card-grid">` told CSS three things and a screen
    reader nothing; `<ul data-list="cards">` tells CSS the same three and
    announces "list, 3 items". Every hook removed here was removed by reaching
    for the element that already meant it, so the markup is shorter and more
    informative at once.

    The trade is real and worth stating: structural selectors are brittle
    under hand editing. Wrap a heading in a div and `header > h2 + p` stops
    matching, with no error. This sheet is for generated markup, where the
    component set is closed and a checker fails a build whose structure does
    not match. A hand-authored site should link v2, which is unchanged and
    stays live.

    MARKUP.md in this repo is the full element-by-element contract. The short
    version:

      section                    a band. data-band="alt|dark|hero|proof|cta"
      section > header           the section head. p:first-child is an eyebrow
                                 when a heading follows; the p after a heading
                                 is the lead
      ul                         a tick list. data-list picks another shape:
                                 cards, logos
      ol                         a numbered list. data-list="steps" for the grid
      [data-layout]              split, feature, shots
      [data-actions]             a button group. The first link is the primary
      figure                     a screenshot, a table, a quotation, a listing,
                                 told apart by what is inside them
      details                    a disclosure
      aside                      a callout. data-severity carries the level
      small                      fine print

    The five classes that remain name something no element or position
    implies: ghost, ink and teal on an action link, tld inside a wordmark, and
    visually-hidden, which is a utility with no element of its own.

    Four more exist and are not design decisions: c, k, m and p on the spans
    inside a syntax-highlighted listing. Those are data about the code, and
    only a highlighter emitting different elements would remove them.

    ── WHAT 3.5.0 FIXES AND ADDS ─────────────────────────────────────────────
    Three things the first data application on the sheet found.

    A link on the warning wash measured 6.97:1, under the floor. --fp-accent
    was measured against the neutral grounds and nobody had paired it with
    --fp-amber-wash. It now takes --fp-accent-strong there, at 8.87, and the
    manifest measures everything a callout puts on the wash. Measuring it
    found one more: in a dark band the callout's label, in --fp-ink-mute, sat
    at 6.98. The dark wash moves one step, #241a00 to #231900, and clears.

    Nine spacing characters rendered with no width: the middle dot, the grave
    accent, and the spacing acute, diaeresis, cedilla, macron, circumflex,
    ring and tilde. Upstream Overpass files them in GDEF as combining marks,
    and a shaper gives a mark no advance of its own and attaches it to the
    letter before it, so "a·b" drew as "ab" and "a´b" as "áb". The face ships
    as overpass-latin-wght-normal-2.woff2 with those nine as base glyphs and
    out of the mark-attachment lookups. No other table changed. The old file
    stays, since the earlier releases name it. A site that preloads the font
    moves its preload with its pin, or the preload fetches a file nothing uses.

    a[data-target] marks a standalone link for the 44px target. The existing
    rule guesses from structure, and `:only-child` counts elements, not text,
    so a paragraph whose only element is a link in a sentence matched and its
    line grew to 44px. Generated markup knows which links stand alone. The
    guess stays as the fallback in 3.x because dropping it would move pages.

    ── WHAT 3.4.2 FIXES ──────────────────────────────────────────────────────
    Every footer link was an inline-flex box at least 44px tall, so it would
    meet the target size on its own. That is right for the byline and the
    version nav, where each link stands alone. It was wrong for a link in the
    fine print, where a 44px box in a sentence stretched every line it sat
    on to 44px and left the paragraph looking double-spaced. A link inside a
    sentence is the case the target-size criterion exempts, as the a11y layer
    already says of main. Fine-print links are now inline, keep the
    line-height of the text around them, and carry their underline at rest
    the way a body link does.

    ── WHAT 3.4.1 FIXES ──────────────────────────────────────────────────────
    3.4.0 made the button padding symmetric and let the flex box centre the
    label. Flex centres the line box, and the ink of a label does not sit in
    the middle of its line box. Overpass declares an ascender of 0.883em and
    a descender of 0.383em around a cap height of 0.70em, so the centre of a
    capital sits (0.383 - 0.883) / 2 + 0.70 / 2 = 0.10em above the centre of
    the line box, at any line-height. Browsers round the ascent and descent
    to whole pixels, and the offset as rendered at the sheet's sizes is 0.11
    to 0.13em: 1.8px in a button at 15.68px, 1.9px in a header pill at
    14.4px, 2.0px in the theme toggle at 17px. Overpass Mono has the same
    metrics and lining figures at cap height. The old 0.13em was that
    metric, not a guess, and removing it raised every label by 2px.

    The lean is back as a token, --fp-label-lean, so a site that swaps
    --fp-sans sets it with the face: (descent - ascent) / 2 + capHeight / 2,
    in em, from the font's hhea and OS/2 tables or from measureText in a
    console. It is applied in one shape wherever a label is centred in a
    padded box: buttons, header links and pills, chips, and the step and list
    numerals. The theme toggle is unpadded, so its text alone takes the lean.
    Top and bottom padding sum to what they did, so no box changes size.

    ── WHAT 3.4.0 FIXES ──────────────────────────────────────────────────────
    Three things that looked right in the source and were not.

    Shadows exist. --fp-shadow and --fp-shadow-lift wrapped light-dark() around
    a whole shadow, and that function takes colours: the value parsed inside the
    custom property and then failed wherever it was used, so every shadow in the
    framework computed to `none` in both schemes. Cards, framed images and lifted
    panels have been flat since light-dark() came in at 3.0.0. The contract test
    now refuses a light-dark() arm that starts with a number.

    A button centres its label by layout, not by a constant measured off
    Overpass. The old padding leaned 0.13em down the box — right for one
    typeface, wrong for every other, and wrong for Overpass itself on any page
    where the font failed to load. There were three copies of that nudge.

    A step counter likewise: place-items already centres a digit.

    ── WHAT 3.3.0 ADDED ──────────────────────────────────────────────────────
    A logo row runs full bleed and finds its own inset, because a band of
    marks is a band and a band goes to the edges.

    ── WHAT 3.2.0 ADDED ───────────────────────────────────────────────────────
    Logos get per-mark optical sizing, because marks nobody drew together have
    nothing in common but their height, and capping them all at one number
    makes a tall square mark tower over a wide wordmark. Each item carries its
    own --logo-h. The row spreads rather than packs, and centres when there is
    no room to spread.

    ul[data-list="chips"], for a list of short identifiers: module names, a
    jump list, a set of tags. Two sites had written it before it moved here.

    [data-layout="split"][data-frame="none"], two balanced columns with no
    panel chrome, for when the halves are one thought rather than a choice.

    A feature row with a single column now spans, instead of sitting in the
    first track with nothing beside it.

    ── WHAT 3.1.0 ADDED ──────────────────────────────────────────────────────
    [data-layout="hero"], the column pair an opening band wants when it
    carries a picture of the product beside its heading.

    ── COLOUR ────────────────────────────────────────────────────────────────
    Two roles, and the default palette gives each one hue.
      fill    a background with dark ink on it. Buttons, chips, highlights.
              Never a text colour on a light ground.
      accent  every text accent: links, eyebrows, list markers, step numbers,
              focus rings.
    Neutral inks and papers do everything else. Multicolour appears only as
    small data blips (--fp-route-*), never as chrome.

    Every text and background pairing defined here clears WCAG AAA (7:1),
    measured against the least favourable ground the token can land on.
    tokens.json beside this file carries the pairings and the repo's test
    measures them. A tint or wash is color-mix() against a token, never a new
    literal, so it follows a site's override and the dark-band remap.

    ── LIGHT / DARK ──────────────────────────────────────────────────────────
    One declaration per token, via light-dark(). v2 wrote the dark palette
    three times: a media query, a pinned [data-theme="dark"] block, and the
    board tokens. Every new token needed four coordinated edits and one of
    them was always forgotten. Here the scheme is chosen by color-scheme, and
    a site pins itself with <html data-theme="light"> or "dark".

    ── DARK BANDS ARE A TOKEN REMAP ──────────────────────────────────────────
    A section with data-band="dark", or hero, proof or cta, re-points the
    neutral tokens at the near-black board palette. Every component below is
    written purely in tokens, so each adapts on its own and there is no
    parallel set of dark rules. Corollary: never hardcode a colour.

    ── LAYERS ────────────────────────────────────────────────────────────────
    tokens, base, layout, components, a11y. v2 depended on source order and
    said so in a comment ("these rules sit at the end of the sheet"). Now it
    is declared. A site's own sheet is unlayered, so it always wins.
*/

@layer tokens, base, layout, components, a11y;

/* ── Type: Overpass / Overpass Mono (SIL OFL 1.1), variable, self-hosted ─── */

@font-face {
  font-family: "Overpass";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/overpass-latin-wght-normal-2.woff2") format("woff2-variations");
}
@font-face {
  font-family: "Overpass Mono";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/overpass-mono-latin-wght-normal.woff2") format("woff2-variations");
}

@layer tokens {
  :root {
    /* light dark: follow the reader's OS. A site pins itself with data-theme,
       which sets this to one keyword and freezes every light-dark() below. */
    color-scheme: light dark;

    /* ── Neutrals ─────────────────────────────────────────────────────── */
    --fp-bg: light-dark(#ffffff, #10141c);
    --fp-bg-alt: light-dark(#f8f9fb, #161b25);
    --fp-bg-card: light-dark(#ffffff, #1b212c);
    --fp-bg-blur: light-dark(rgb(255 255 255 / 0.92), rgb(16 20 28 / 0.92));
    --fp-ink: light-dark(#141821, #edf0f6);
    --fp-ink-soft: light-dark(#3d4350, #b9c0ce);
    --fp-ink-mute: light-dark(#505562, #a4abbb);
    --fp-rule: light-dark(#e5e7eb, #272e3c);
    /* light-dark() takes colours, not value lists. Wrapped around a whole
       shadow it parses inside the custom property — a custom property holds any
       token stream — and then fails when the property that uses it is parsed,
       which makes box-shadow compute to `none`. Every shadow in the framework
       was dead in both schemes, silently, and the contract test could not see
       it because it measures colours and a shadow is geometry. So the function
       goes where a colour goes, and the geometry sits outside it. One blur for
       both schemes: it was 14/40 against 18/44, a difference nobody can see. */
    --fp-shadow: 0 1px 2px light-dark(rgb(20 24 33 / 0.05), rgb(0 0 0 / 0.4));
    --fp-shadow-lift: 0 16px 42px light-dark(rgb(20 24 33 / 0.10), rgb(0 0 0 / 0.4));

    /* ── Accents ──────────────────────────────────────────────────────── */
    --fp-accent: light-dark(#006060, #4dd2d2);
    --fp-accent-strong: light-dark(#004f4f, #7fdede);
    --fp-bronze: light-dark(#6f5000, #ffc94d);         /* the fill family as text */
    --fp-bronze-strong: light-dark(#5a4100, #ffdd8a);
    --fp-amber-wash: light-dark(#fff8e4, #231900);
    /* --fp-link and --fp-link-hover are read as fallbacks and never declared.
       Leaving them unset defers the lookup to the link itself, so a link in a
       dark band picks up that band's accent. Setting one on :root pins links
       to a single colour everywhere, because a custom property containing
       var() is substituted where it is declared and inherited flat. Setting
       one on a section is the intended way to override deliberately. */

    /* ── Scheme-independent ───────────────────────────────────────────── */
    /* the fill reads the same either way: it is always a ground with dark ink */
    --fp-amber: #ffb300;
    --fp-amber-hover: #ffc94d;
    --fp-amber-ink: #1a1200;
    --fp-teal-solid: #006060;      /* the accent as a fill, with white ink */

    /* the board: near-black grounds for heroes, closing bands, footers, code */
    --fp-board: #10141c;
    --fp-board-2: #161b25;
    --fp-board-deep: #0b0f16;
    --fp-board-line: #272e3c;
    --fp-board-glow: #1e2431;
    --fp-board-ink: #edf0f6;
    --fp-board-soft: #d6dbe7;
    --fp-board-dim: #9fa5b7;
    --fp-board-accent: #4dd2d2;
    --fp-board-accent-2: #7fdede;

    /* data blips: route badges, series keys. Never chrome. */
    --fp-route-blue: #0d5b98;
    --fp-route-green: #006640;
    --fp-route-red: #a72925;
    --fp-route-orange: #f5851f;
    --fp-route-purple: #684b8b;
    --fp-route-ink: #ffffff;       /* white clears 7:1 on every blip but orange */
    --fp-route-ink-dark: #221200;  /* which takes dark ink, 7.2:1 */

    /* code listings, on the board ground */
    --fp-code-comment: #a0a5b4;
    --fp-code-path: #a7d8b6;
    --fp-code-key: #a9bfe0;

    /* ── Type scale ───────────────────────────────────────────────────── */
    --fp-sans: "Overpass", system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
    --fp-mono: "Overpass Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo,
               Consolas, "Liberation Mono", monospace;
    /* How far a label's cap centre sits above its line-box centre, in em:
       (descent - ascent) / 2 + capHeight / 2 for the face. Every padded label
       leans down by it. Overpass gives 0.10em from its tables and 0.12em as
       browsers render it; a face whose ascent and descent are symmetric about
       the cap height takes 0. Set it with --fp-sans. */
    --fp-label-lean: 0.12em;
    --fp-text: 1.0625rem;
    --fp-leading: 1.65;
    --fp-h1: clamp(2rem, 5.5vw, 3.25rem);
    --fp-h2: clamp(1.5rem, 3.5vw, 2.125rem);
    --fp-h3: 1.2rem;
    --fp-h4: 1.02rem;
    --fp-lead: clamp(1.1rem, 2.2vw, 1.3rem);
    --fp-tracking-h: -0.015em;
    --fp-weight-h1: 800;
    --fp-weight-h2: 750;
    --fp-weight-h: 700;
    --fp-weight-ui: 650;
    --fp-weight-nav: 550;

    /* WCAG 1.4.8 caps running text at 80 characters. In ch, so it tracks the
       face: v2 hard-coded 29em, derived from Overpass's average character
       width, which stopped being true the moment a site changed --fp-sans.

       52ch is measured, not assumed. The zero glyph is wider than an average
       lowercase letter, so a ch value buys more characters than it names: at
       this size a line of lowercase prose holds about 73, which is inside the
       cap and inside the 45-to-75 range that reads best. The repo's test
       measures it rather than trusting this comment, because the first value
       tried here named 68ch and held 88 characters. */
    --fp-measure: 52ch;

    /* ── Spacing and layout ───────────────────────────────────────────── */
    --fp-space-section: 4.5rem;
    --fp-space-section-tight: 3rem;
    --fp-space-head: 2.5rem;
    --fp-gap: 1.25rem;
    --fp-gap-wide: 3rem;
    --fp-pad-card: 1.5rem;
    --fp-pad-panel: 2rem;
    --fp-wrap: 70rem;              /* the content track */
    --fp-wrap-wide: 90rem;         /* the wide track, for figures */
    --fp-gutter: 1.5rem;
    --fp-radius: 12px;
    --fp-radius-sm: 8px;
    --fp-radius-xs: 6px;
    --fp-pill: 999px;
    --fp-target: 44px;             /* WCAG 2.5.5 */
    --fp-header-clear: 6.5rem;
    --fp-sticky-top: 5.5rem;       /* a sticky sub-element clears the header */
  }

  /* Pinning. One keyword decides every light-dark() above. */
  :root[data-theme="light"] { color-scheme: light; }
  :root[data-theme="dark"] { color-scheme: dark; }

  /* Dark grounds: the neutral tokens point at the board palette. Custom
     properties resolve after the cascade, so every component rule picks up the
     remapped values with no dark-specific rule anywhere. */
  :where([data-band="dark"], [data-band="hero"], [data-band="proof"], [data-band="cta"], body > footer) {
    color-scheme: dark;
    /* The remap alone is not enough. `color` inherits a computed value, so
       text inside a dark band keeps the ink body resolved from the light
       palette unless something re-reads the token here. Headings re-read it
       themselves (see the base layer), which is why they need no rule of
       their own per band. */
    color: var(--fp-ink-soft);
    --fp-bg: var(--fp-board);
    --fp-bg-alt: var(--fp-board-2);
    --fp-bg-card: var(--fp-board-2);
    --fp-bg-blur: rgb(16 20 28 / 0.92);
    --fp-ink: var(--fp-board-ink);
    --fp-ink-soft: var(--fp-board-soft);
    --fp-ink-mute: var(--fp-board-dim);
    --fp-rule: var(--fp-board-line);
    --fp-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
    --fp-shadow-lift: 0 16px 42px rgb(0 0 0 / 0.4);
    --fp-accent: var(--fp-board-accent);
    --fp-accent-strong: var(--fp-board-accent-2);
    --fp-bronze: var(--fp-amber);
    --fp-bronze-strong: var(--fp-amber-hover);
    --fp-amber-wash: #231900;
  }
}

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

  html {
    scroll-behavior: smooth;
    scroll-padding-top: var(--fp-header-clear);
    -webkit-text-size-adjust: 100%;
  }

  body {
    margin: 0;
    background: var(--fp-bg);
    color: var(--fp-ink);
    font-family: var(--fp-sans);
    font-size: var(--fp-text);
    line-height: var(--fp-leading);
    -webkit-font-smoothing: antialiased;
  }

  /* height:auto is not optional. Generated markup carries width and height on
     every image to stop the page reflowing as each one loads, and max-width
     alone then stretches them vertically. v2 lacked this and distorted every
     image outside a figure. */
  img, svg, video, canvas { max-width: 100%; height: auto; }

  a { color: var(--fp-link, var(--fp-accent)); }
  a:hover { color: var(--fp-link-hover, var(--fp-accent-strong)); }

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

  /* Headings read the ink token at the point of use rather than inheriting a
     computed colour, so a dark band recolours them by remapping the token and
     needs no heading rule of its own. */
  h1, h2, h3, h4 {
    margin: 0 0 0.5em;
    color: var(--fp-ink);
    line-height: 1.15;
    letter-spacing: var(--fp-tracking-h);
    text-wrap: balance;
  }
  h1 { font-size: var(--fp-h1); font-weight: var(--fp-weight-h1); }
  h2 { font-size: var(--fp-h2); font-weight: var(--fp-weight-h2); }
  h3 { font-size: var(--fp-h3); font-weight: var(--fp-weight-h); }
  h4 { font-size: var(--fp-h4); font-weight: var(--fp-weight-h); }

  p { margin: 0 0 1em; text-wrap: pretty; }
  p:last-child { margin-bottom: 0; }

  code, kbd, samp, pre { font-family: var(--fp-mono); }

  /* fine print. v2 painted this with .fine and .fineprint; the element for it
     already existed. */
  small { font-size: 0.9rem; color: var(--fp-ink-mute); }

  strong { color: var(--fp-ink); }

  hr { border: 0; border-top: 1px solid var(--fp-rule); margin: 2rem 0; }

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

@layer layout {
  /* ── The content column ────────────────────────────────────────────────
     Two mechanisms, because chrome and content need different things.

     A section is a grid with a centred content track and wider tracks either
     side, so a child sits in `content` by default and anything needing more
     room asks for `wide` or `full` by name. That one mechanism replaces v2's
     .wrap, .wrap-wide and .shot-bleed, and it removes the wrapper div that
     used to sit inside every section. A full-bleed figure in v2 had to break
     out with a negative margin and a 100vw width that overflowed by the
     scrollbar's width; a named grid line has neither problem.

     The header and footer have no full-bleed children, so they take the
     simpler form: padding that collapses to the gutter on a narrow screen and
     grows to centre a capped column on a wide one. That leaves the header
     free to be a flex row, which is what a brand beside a nav wants. */
  main > section {
    display: grid;
    /* The content track is sized with min() rather than minmax(0, ...) so it
       resolves before the tracks around it. With two flexible neighbours the
       grid algorithm shared the shortfall between them instead, and on a
       narrow viewport the breakout tracks took 90px each from the content
       column: the hero indented further than the header above it. */
    grid-template-columns:
      [full-start] minmax(var(--fp-gutter), 1fr)
      [wide-start] minmax(0, calc((var(--fp-wrap-wide) - var(--fp-wrap)) / 2))
      [content-start] min(100% - var(--fp-gutter) * 2, var(--fp-wrap)) [content-end]
      minmax(0, calc((var(--fp-wrap-wide) - var(--fp-wrap)) / 2)) [wide-end]
      minmax(var(--fp-gutter), 1fr) [full-end];
  }
  main > section > * { grid-column: content; }
  main > section > [data-width="wide"] { grid-column: wide; }
  main > section > [data-width="full"] { grid-column: full; }

  body > :is(header, footer) {
    padding-inline: max(var(--fp-gutter), (100% - var(--fp-wrap)) / 2);
  }

  /* ── Bands ────────────────────────────────────────────────────────────── */
  main > section { padding-block: var(--fp-space-section); }
  /* the head's gap to the content below it, the one place a section needs one */
  main > section > header { margin-bottom: var(--fp-space-head); }
  /* a heading that follows content starts a new group and needs air above it.
     A section carrying several components reads as one run without this. */
  main > section > * + :is(h2, h3) { margin-top: 2.5rem; }
  main > section[data-tight] { padding-block: var(--fp-space-section-tight); }
  main > section[data-band="alt"] { background: var(--fp-bg-alt); }
  main > section[data-band="dark"] { background: var(--fp-board); }

  /* a rule-bounded strip of short reassurances */
  main > section[data-band="strip"] {
    padding-block: 1.1rem;
    border-block: 1px solid var(--fp-rule);
  }

  /* ── Hero ─────────────────────────────────────────────────────────────── */
  main > section[data-band="hero"] {
    padding-block: 5rem 5.5rem;
    overflow: hidden;
    background:
      radial-gradient(1200px 500px at 75% -10%, var(--fp-board-glow) 0%, transparent 60%),
      var(--fp-board);
  }
  main > section[data-band="hero"] h1 { max-width: 16em; }
  main > section[data-band="hero"][data-tight] { padding-block: 4rem; }
  main > section[data-band="hero"][data-tight] h1 { max-width: 18em; }

  /* ── Closing band ─────────────────────────────────────────────────────── */
  main > section[data-band="cta"] {
    padding-block: var(--fp-space-section);
    text-align: center;
    background:
      radial-gradient(900px 400px at 20% 120%, var(--fp-board-glow) 0%, transparent 60%),
      var(--fp-board);
  }
  main > section[data-band="cta"] > * { max-width: 40rem; margin-inline: auto; }
  main > section[data-band="cta"] p { margin-inline: auto; margin-bottom: 1.8rem; }
  main > section[data-band="cta"] small { display: block; margin-top: 1.6rem; }

  /* ── Proof strip ──────────────────────────────────────────────────────
     One row rather than a stack, so it takes the chrome layout: no child of
     a proof strip runs full bleed, and a flex row is what a claim beside a
     badge wants. */
  main > section[data-band="proof"] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1.5rem;
    padding-block: 2.4rem;
    padding-inline: max(var(--fp-gutter), (100% - var(--fp-wrap)) / 2);
    background: var(--fp-board);
  }
  main > section[data-band="proof"] > p { margin: 0; font-size: 1.12rem; }

  /* ── The section head ─────────────────────────────────────────────────
     header > p:first-child is an eyebrow only when a heading follows it,
     which is what tells it apart from a lone paragraph of standfirst. */
  main > section > header { max-width: 46em; }
  main > section > header > :last-child { margin-bottom: 0; }
  main > section > header > p:first-child:has(+ :is(h1, h2, h3)) {
    display: inline-block;
    margin-bottom: 0.6rem;
    color: var(--fp-accent);
    font-weight: var(--fp-weight-h);
    font-size: 0.78rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
  }
  main > section > header > :is(h1, h2, h3) + p {
    font-size: var(--fp-lead);
    color: var(--fp-ink-soft);
    max-width: var(--fp-measure);
  }
  main > section > header > p:not(:first-child):not(:is(h1, h2, h3) + p) {
    color: var(--fp-ink-soft);
    font-size: 1.1rem;
  }

  /* A lead directly under a hero h1 is the same idea without a header. */
  main > section[data-band="hero"] > h1 + p,
  main > section[data-band="cta"] > h2 + p {
    font-size: var(--fp-lead);
    color: var(--fp-ink-soft);
    max-width: var(--fp-measure);
  }
  main > section[data-band="cta"] > h2 + p { margin-inline: auto; }
}

@layer components {
  /* ── Action groups ────────────────────────────────────────────────────
     The first link in the group is the page's primary ask and renders as the
     fill. A second primary now needs a second group, which is visible in the
     markup and in review, so the design guide's "one primary per view" is
     something the stylesheet enforces rather than advises. */
  [data-actions] {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    flex-wrap: wrap;
    margin-top: 1.8rem;
  }
  main > section[data-band="cta"] [data-actions] { justify-content: center; margin-top: 0; }

  [data-actions] > a,
  button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--fp-target);
    /* The flex box centres the line box, and the cap centre of the label sits
       --fp-label-lean above that (the 3.4.1 note in the header has the
       numbers), so the padding leans the label down by the same amount. Top
       and bottom still sum to 1.4rem: the button is the height it was. */
    padding-block: calc(0.7rem + var(--fp-label-lean)) calc(0.7rem - var(--fp-label-lean));
    padding-inline: 1.3rem;
    border: 1.5px solid transparent;
    border-radius: var(--fp-pill);
    font-family: inherit;
    font-size: 0.98rem;
    font-weight: var(--fp-weight-ui);
    line-height: 1.25;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
    background: var(--fp-amber);
    color: var(--fp-amber-ink);
  }
  [data-actions] > a:hover,
  button:hover {
    background: var(--fp-amber-hover);
    color: var(--fp-amber-ink);
    text-decoration: underline;
    text-underline-offset: 0.2em;
  }

  /* Everything after the first is secondary, so it does not spend the fill. */
  [data-actions] > a:not(:first-child) {
    background: transparent;
    border-color: var(--fp-ink-soft);
    color: var(--fp-ink);
  }
  [data-actions] > a:not(:first-child):hover {
    background: var(--fp-bg-alt);
    border-color: var(--fp-ink);
    color: var(--fp-ink);
  }

  /* Three named variants, because a secondary ask is a choice with no
     structural signal. They are the only classes on an action. */
  [data-actions] > a.ghost { background: transparent; border-color: var(--fp-ink-soft); color: var(--fp-ink); }
  [data-actions] > a.ghost:hover { background: var(--fp-bg-alt); border-color: var(--fp-ink); color: var(--fp-ink); }
  [data-actions] > a.ink, a.ink { background: var(--fp-ink); border-color: transparent; color: var(--fp-bg); }
  [data-actions] > a.ink:hover, a.ink:hover { background: var(--fp-ink-soft); color: var(--fp-bg); }
  [data-actions] > a.teal { background: var(--fp-teal-solid); border-color: transparent; color: #ffffff; }
  [data-actions] > a.teal:hover { background: var(--fp-accent-strong); color: #ffffff; }

  /* ── Lists ────────────────────────────────────────────────────────────
     A bullet list is the system's list: a drawn metro-stop dot, not a glyph,
     so nothing falls back to a box. An ordered list numbers in a ring. Both
     stay in flow; data-list picks a shape that needs a grid. */
  main :where(ul, ol):not([data-list]) {
    display: grid;
    gap: 0.6rem;
    list-style: none;
    margin: 0 0 1em;
    padding: 0;
  }
  main :where(ul, ol):not([data-list]) > li {
    position: relative;
    padding-left: 1.7rem;
    color: var(--fp-ink-soft);
  }
  main ul:not([data-list]) > li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.42em;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    border: 3.5px solid var(--fp-accent);
    background: var(--fp-bg);
  }
  /* "verified" rather than "a stop on a line": a drawn check. */
  main ul[data-marker="check"] > li { padding-left: 1.6rem; }
  main ul[data-marker="check"] > li::before {
    top: 0.38em;
    width: 0.85rem;
    height: 0.42rem;
    border: 0 solid var(--fp-accent);
    border-left-width: 3.5px;
    border-bottom-width: 3.5px;
    border-radius: 0;
    background: none;
    transform: rotate(-45deg);
  }
  main ul:not([data-list])[data-cols="3"] { gap: 1rem 2.5rem; }

  main ol:not([data-list]) { counter-reset: step; }
  main ol:not([data-list]) > li { counter-increment: step; padding-left: 2.6rem; }
  main ol:not([data-list]) > li::before {
    content: counter(step);
    position: absolute;
    left: 0;
    top: 0.1em;
    display: inline-grid;
    place-items: center;
    box-sizing: border-box;
    min-width: 1.9rem;
    height: 1.9rem;
    /* place-items centres the line box and the digit's ink sits
       --fp-label-lean above it, so the padding leans by that amount. The
       ring's fixed height absorbs the 0.3em base, which is there only so the
       lean has something to subtract from. The 0.5em is for a second digit. */
    padding-inline: 0.5em;
    padding-block: calc(0.3em + var(--fp-label-lean)) calc(0.3em - var(--fp-label-lean));
    border: 2.5px solid var(--fp-accent);
    border-radius: var(--fp-pill);
    font-family: var(--fp-mono);
    font-weight: var(--fp-weight-h);
    font-size: 0.9rem;
    line-height: 1;
    color: var(--fp-accent);
  }

  /* ── Steps: the same numbering, laid out as a grid ────────────────────── */
  ol[data-list="steps"] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--fp-gap);
    counter-reset: step;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  ol[data-list="steps"] > li {
    counter-increment: step;
    border-top: 1px solid var(--fp-rule);
    padding-top: 1.1rem;
  }
  ol[data-list="steps"] > li::before {
    content: counter(step);
    display: inline-grid;
    place-items: center;
    box-sizing: border-box;
    min-width: 1.9rem;
    height: 1.9rem;
    /* place-items centres the line box and the digit's ink sits
       --fp-label-lean above it, so the padding leans by that amount. The
       ring's fixed height absorbs the 0.3em base, which is there only so the
       lean has something to subtract from. The 0.5em is for a second digit. */
    padding-inline: 0.5em;
    padding-block: calc(0.3em + var(--fp-label-lean)) calc(0.3em - var(--fp-label-lean));
    border: 2.5px solid var(--fp-accent);
    border-radius: var(--fp-pill);
    font-family: var(--fp-mono);
    font-weight: var(--fp-weight-h);
    font-size: 0.9rem;
    line-height: 1;
    color: var(--fp-accent);
  }
  ol[data-list="steps"] > li > h3 { margin-top: 0.35rem; }
  ol[data-list="steps"] > li > p { color: var(--fp-ink-soft); font-size: 0.98rem; }
  ol[data-list="steps"] > li > :last-child { margin-bottom: 0; }

  /* ── Cards ────────────────────────────────────────────────────────────── */
  ul[data-list="cards"] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15.5rem, 1fr));
    gap: var(--fp-gap);
    list-style: none;
    margin: 0;
    padding: 0;
  }
  @media (min-width: 52rem) {
    ul[data-list="cards"][data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    ul[data-list="cards"][data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    ul[data-list="cards"][data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  @media (min-width: 68rem) {
    ul[data-list="cards"][data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }
  ul[data-list="cards"] > li {
    display: flex;
    background: var(--fp-bg-card);
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-radius);
  }
  section[data-band="alt"] ul[data-list="cards"] > li { box-shadow: var(--fp-shadow); }
  /* the card's interior belongs to whatever fills it, so the padding goes on
     the child: a whole-card link then covers the entire card. */
  ul[data-list="cards"] > li > * {
    flex: 1;
    padding: var(--fp-pad-card) var(--fp-pad-card) calc(var(--fp-pad-card) - 0.15rem);
  }
  ul[data-list="cards"] > li h3 { margin-top: 0; }
  ul[data-list="cards"] > li p { color: var(--fp-ink-soft); font-size: 0.98rem; }
  ul[data-list="cards"] > li p:last-of-type { margin-bottom: 0; }

  ul[data-list="cards"] > li > a {
    display: block;
    color: inherit;
    text-decoration: none;
    border-radius: inherit;
    transition: border-color 120ms ease, transform 120ms ease;
  }
  ul[data-list="cards"] > li:has(> a:hover) { border-color: var(--fp-ink-mute); transform: translateY(-2px); }
  @media (prefers-reduced-motion: reduce) {
    ul[data-list="cards"] > li:has(> a:hover) { transform: none; }
  }
  /* the affordance closing a card link */
  ul[data-list="cards"] > li > a > span:last-child {
    display: inline-block;
    margin-top: 0.9rem;
    color: var(--fp-accent);
    font-weight: 600;
    font-size: 0.92rem;
  }
  ul[data-list="cards"] > li > a:hover > span:last-child { color: var(--fp-accent-strong); text-decoration: underline; }

  /* a neutral icon chip; colour stays reserved for data */
  ul[data-list="cards"] > li svg:first-child {
    width: 42px;
    height: 42px;
    padding: 8px;
    border-radius: 10px;
    background: var(--fp-bg-alt);
    border: 1px solid var(--fp-rule);
    color: var(--fp-ink);
  }
  ul[data-list="cards"] > li svg:first-child + * { margin-top: 0.9rem; }

  /* ── Logos ────────────────────────────────────────────────────────────
     A row of marks nobody drew together. Their bounding boxes have nothing
     in common, so capping them all at one height makes a tall square mark
     tower over a wide wordmark and the row reads as a mistake. Optical
     weight is per-mark information that only the page can know, so each item
     carries its own height in --logo-h and the default is a starting point
     rather than an answer.

     Centred rather than spread. A spread row distributes each wrapped line
     independently, so nine marks on a line-and-a-bit leave the last two
     stranded at opposite edges and the set reads as a mistake. Centring makes
     a wrapped row look chosen at every width, which matters more than filling
     the line at one width. */
  ul[data-list="logos"] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 1.75rem 2rem;
    list-style: none;
    margin: 0;
    /* Full-bleed, so the inset is this row's own: the grid track it sits in
       runs to the viewport edges and hands it no gutter. It scales with the
       viewport because a set of marks spread across a wide screen wants more
       air at the ends than a phone can spare. */
    padding-inline: clamp(var(--fp-gutter), 6vw, 6rem);
  }
  @media (max-width: 56rem) {
    ul[data-list="logos"] { gap: 1.5rem 2rem; }
  }
  /* the trademark line under a logo row needs air, or it reads as a caption
     belonging to the last mark */
  ul[data-list="logos"] + small { display: block; margin-top: 2rem; }
  ul[data-list="logos"] img {
    display: block;
    width: auto;
    height: var(--logo-h, 1.7rem);
    max-height: none;
  }
  ul[data-list="logos"] a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--fp-target);
    min-width: var(--fp-target);
    padding: 0 0.25rem;
    /* a mark is a fact, not a call to action, so it sits back until pointed at */
    opacity: 0.8;
    transition: opacity 150ms ease;
  }
  ul[data-list="logos"] a:hover,
  ul[data-list="logos"] a:focus-visible { opacity: 1; }

  /* ── Chips ────────────────────────────────────────────────────────────
     A list of short identifiers: module names, a jump list, a set of tags.
     The dot marker is for a list of claims; a list of names wants to read as
     a set of objects, and wrapping into a block of pills does that. */
  ul[data-list="chips"] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    list-style: none;
    margin: 0 0 1em;
    padding: 0;
  }
  ul[data-list="chips"] > li { max-width: none; }
  ul[data-list="chips"] > li > a,
  ul[data-list="chips"] > li:not(:has(a)) {
    display: inline-flex;
    align-items: center;
    min-height: var(--fp-target);
    padding: 0.25rem 0.7rem;
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-pill);
    background: var(--fp-bg-card);
    font-family: var(--fp-mono);
    font-size: 0.85rem;
    color: var(--fp-ink-soft);
    text-decoration: none;
  }
  ul[data-list="chips"] > li > a:hover { border-color: var(--fp-accent); color: var(--fp-accent); }

  /* ── Split panels ─────────────────────────────────────────────────────── */
  [data-layout="split"] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: var(--fp-gap);
  }
  [data-layout="split"] > * {
    background: var(--fp-bg-card);
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-radius);
    padding: var(--fp-pad-panel);
  }
  [data-layout="split"] > * > :last-child { margin-bottom: 0; }
  /* Two balanced columns with no panel around them. The panel is right when
     the two are alternatives to choose between; it is wrong when they are
     simply two halves of one thought, and a border then draws a distinction
     the content is not making. */
  [data-layout="split"][data-frame="none"] > * {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
  }
  [data-layout="split"] [data-actions] { margin-top: 0.6rem; }

  /* ── A hero with something beside the copy ────────────────────────────
     An opening band often carries a picture of the product next to its
     heading. v2 had .hero-grid for this; the engine could not use it, because
     a hero component with no media slot can only put the picture in the block
     after, which reads as a different section. This is that column pair, kept
     out of [data-layout~="feature"] because a feature row carries its own
     vertical rhythm and a rule between siblings, and a hero wants neither. */
  [data-layout="hero"] {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 3.5rem;
    align-items: center;
  }
  [data-layout="hero"] > * { min-width: 0; }
  @media (max-width: 56rem) {
    [data-layout="hero"] { grid-template-columns: 1fr; gap: 2.5rem; }
  }

  /* ── Feature rows ─────────────────────────────────────────────────────── */
  [data-layout~="feature"] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--fp-gap-wide);
    align-items: center;
    padding-block: 2.75rem;
  }
  [data-layout~="feature"] + [data-layout~="feature"] { border-top: 1px solid var(--fp-rule); }
  [data-layout~="feature"][data-layout~="uneven"] { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  /* when the columns differ a lot in height, centring the short one against
     the tall one reads as a mistake */
  [data-layout~="feature"][data-layout~="top"] { align-items: start; }
  [data-layout~="feature"][data-layout~="flip"] > :first-child { order: 2; }
  [data-layout~="feature"] > * { min-width: 0; }
  /* A row with one column is one column. The template omits an empty side
     rather than emitting a blank div, and without this the survivor would sit
     in the first track at half width with nothing beside it. */
  [data-layout~="feature"] > :only-child { grid-column: 1 / -1; }
  [data-layout~="feature"] h2 { font-size: clamp(1.35rem, 3vw, 1.75rem); }
  [data-layout~="feature"] h3 { font-size: 1.45rem; }
  [data-layout~="feature"] > * > p { color: var(--fp-ink-soft); }

  /* Stacking. One breakpoint for every two-column arrangement, so nothing is
     half stacked at any width. 56rem is the convention's `stack` value; see
     tokens.json, which is where a site should read it from rather than
     guessing, because a media query cannot take a custom property. */
  @media (max-width: 56rem) {
    main > section[data-band="hero"] { padding-block: 3.5rem 4rem; }
    [data-layout~="feature"],
    [data-layout~="feature"][data-layout~="uneven"] {
      grid-template-columns: 1fr;
      gap: 1.5rem;
    }
    [data-layout~="feature"][data-layout~="flip"] > :first-child { order: 0; }
    [data-layout="shots"][data-uneven] { grid-template-columns: 1fr; }
  }

  /* ── Screenshots and figures ──────────────────────────────────────────
     Told apart by what is inside them, which is also what decides how each
     one is framed. */
  [data-layout="shots"] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    gap: 2rem;
    align-items: start;
  }
  [data-layout="shots"][data-uneven] { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }

  figure { margin: 0; min-width: 0; }
  /* The frame is on "an image that is a figure's content", which is a claim
     about the image's role. Widening it to `picture img` to catch a screenshot
     in a feature row looked equivalent — the media build emits <picture> for a
     raster and a bare <img> for a vector, so it reads as "frame photographs,
     not marks" — and it is not: a logo row happily carries raster marks, and
     they all came back with a 1px border. Role is not format. The feature row
     was the thing out of line, and it now wraps its media in a figure like
     every other component. */
  figure:has(> :is(img, picture, a > img)) img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-radius);
    box-shadow: var(--fp-shadow-lift);
  }
  figure figcaption {
    margin-top: 0.75rem;
    max-width: var(--fp-measure);
    font-size: 0.88rem;
    color: var(--fp-ink-mute);
  }
  /* a full-bleed figure loses the frame it no longer has room for */
  [data-width="full"] :is(img, figure > img) {
    border-radius: 0;
    border-inline: 0;
    box-shadow: none;
  }
  /* the caption is prose, so it keeps the content column even when the
     picture above it does not */
  [data-width="full"] figcaption {
    max-width: var(--fp-wrap);
    margin-inline: auto;
    padding-inline: var(--fp-gutter);
  }

  /* ── Code ─────────────────────────────────────────────────────────────── */
  pre {
    margin: 0;
    padding: 1.2rem 1.4rem;
    background: var(--fp-board);
    color: var(--fp-board-soft);
    border: 1px solid var(--fp-board-line);
    border-radius: var(--fp-radius);
    font-family: var(--fp-mono);
    font-size: 0.86rem;
    line-height: 1.7;
    overflow-x: auto;
  }
  pre .c { color: var(--fp-code-comment); }
  pre .m { color: var(--fp-amber); }
  pre .p { color: var(--fp-code-path); }
  pre .k { color: var(--fp-code-key); }

  /* A listing with a filename is a figure whose caption is the title bar.
     v2 drew three decorative dots with three empty <i> elements; they are a
     border-image's worth of decoration, so they belong in CSS. */
  figure:has(> pre) {
    background: var(--fp-bg-card);
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-radius);
    overflow: hidden;
    box-shadow: var(--fp-shadow-lift);
  }
  figure:has(> pre) > figcaption {
    /* The dot metrics and the space reserved for them are derived from each
       other, because they were written independently once and the filename
       sat on top of the third dot. */
    --dot: 0.65rem;
    --dot-step: 1.1rem;   /* a dot plus the gap after it */
    --dots-end: calc(0.9rem + 2 * var(--dot-step) + var(--dot));
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0.6rem 0.9rem 0.6rem calc(var(--dots-end) + 0.7rem);
    max-width: none;
    border-bottom: 1px solid var(--fp-rule);
    font-family: var(--fp-mono);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--fp-ink-soft);
    position: relative;
  }
  figure:has(> pre) > figcaption::before {
    content: "";
    position: absolute;
    left: 0.9rem;
    top: 50%;
    translate: 0 -50%;
    width: var(--dot);
    height: var(--dot);
    border-radius: 50%;
    background: var(--fp-rule);
    box-shadow: var(--dot-step) 0 var(--fp-rule), calc(2 * var(--dot-step)) 0 var(--fp-rule);
  }
  figure:has(> pre) > pre { border: 0; border-radius: 0; box-shadow: none; }

  /* ── Tables ───────────────────────────────────────────────────────────── */
  figure:has(> table) { overflow-x: auto; margin-block: 1.5rem; }
  table { border-collapse: collapse; width: 100%; min-width: 32rem; font-size: 0.95rem; }
  caption {
    text-align: left;
    font-family: var(--fp-mono);
    font-size: 0.72rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--fp-ink-mute);
    padding-bottom: 0.7rem;
  }
  th, td {
    text-align: left;
    padding: 0.7rem 1.2rem 0.7rem 0;
    border-bottom: 1px solid var(--fp-rule);
    vertical-align: top;
  }
  thead th {
    font-family: var(--fp-mono);
    font-size: 0.71rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--fp-ink-mute);
  }
  tbody th { font-weight: var(--fp-weight-ui); color: var(--fp-ink); }

  /* ── Quotations ───────────────────────────────────────────────────────── */
  figure:has(> blockquote) {
    margin-block: 1.75rem;
    padding-left: 1.15rem;
    border-left: 3px solid var(--fp-rule);
  }
  blockquote { margin: 0; font-size: 1.1rem; line-height: 1.55; color: var(--fp-ink); }
  figure:has(> blockquote) > figcaption { margin-top: 0.6rem; font-size: 0.9rem; }

  /* ── Callouts ─────────────────────────────────────────────────────────
     Severity is carried by the visible label as well as the colour, because
     colour alone is not information (WCAG 1.4.1). */
  aside {
    border-left: 3px solid var(--fp-accent);
    background: var(--fp-bg-alt);
    border-radius: 0 var(--fp-radius-sm) var(--fp-radius-sm) 0;
    padding: 1rem 1.15rem;
    margin-block: 1.5rem;
  }
  aside[data-severity="warning"] { border-left-color: var(--fp-bronze); background: var(--fp-amber-wash); }
  /* --fp-accent on the wash is 6.97:1 in light, under the floor. The strong
     accent is 8.87 there and the ink it darkens to on hover is 16.7. */
  aside[data-severity="warning"] a { color: var(--fp-link, var(--fp-accent-strong)); }
  aside[data-severity="warning"] a:hover { color: var(--fp-link-hover, var(--fp-ink)); }
  aside[data-severity="critical"] { border-left-color: var(--fp-route-red); }
  aside > p:first-child {
    font-family: var(--fp-mono);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--fp-ink-mute);
    margin-bottom: 0.4rem;
  }
  aside > :last-child { margin-bottom: 0; }

  /* ── Disclosure ───────────────────────────────────────────────────────── */
  details {
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-radius);
    padding: 1rem 1.15rem;
    background: var(--fp-bg-card);
    margin-block: 1rem;
  }
  details > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: var(--fp-target);
    font-weight: var(--fp-weight-ui);
    color: var(--fp-ink);
    cursor: pointer;
    /* display:flex on a summary drops the native marker, so the disclosure
       state has to be drawn back. Both spellings: the pseudo-element is
       WebKit's and list-style covers everyone else. */
    list-style: none;
  }
  details > summary::-webkit-details-marker { display: none; }
  /* A triangle pointing at what it will do: right when closed, down when open.
     Drawn with borders rather than a glyph, for the same reason the list
     markers are: there is nothing for a glyph to fall back to. */
  details > summary::after {
    content: "";
    flex: none;
    width: 0;
    height: 0;
    border-left: 0.36rem solid var(--fp-ink-mute);
    border-block: 0.3rem solid transparent;
    transition: rotate 120ms ease;
  }
  details[open] > summary::after { rotate: 90deg; }
  details > :not(summary):last-child { margin-bottom: 0; }

  /* ── Definition lists ─────────────────────────────────────────────────── */
  dl { margin-block: 1.5rem; }
  dt {
    font-weight: var(--fp-weight-ui);
    color: var(--fp-ink);
    margin-top: 1.25rem;
    scroll-margin-top: var(--fp-header-clear);
  }
  dt:first-child { margin-top: 0; }
  dd { margin: 0.35rem 0 0; color: var(--fp-ink-soft); max-width: var(--fp-measure); }

  /* ── Data blips ───────────────────────────────────────────────────────
     The one component that spends colour on meaning. v2 shipped the tokens
     with nothing that used them, so every site that wanted a route badge
     wrote its own. */
  [data-route] {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: var(--fp-radius-xs);
    font-family: var(--fp-mono);
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--fp-route-ink);
    background: var(--fp-ink-mute);
  }
  [data-route="blue"] { background: var(--fp-route-blue); }
  [data-route="green"] { background: var(--fp-route-green); }
  [data-route="red"] { background: var(--fp-route-red); }
  [data-route="purple"] { background: var(--fp-route-purple); }
  [data-route="orange"] { background: var(--fp-route-orange); color: var(--fp-route-ink-dark); }

  /* ── A mono chip ──────────────────────────────────────────────────────
     A jump-list entry, a module name, a proof-strip badge. Each had been
     written twice in site sheets before it moved here. */
  [data-chip] {
    display: inline-flex;
    align-items: center;
    min-height: var(--fp-target);
    padding-inline: 0.7rem;
    padding-block: calc(0.2rem + var(--fp-label-lean)) calc(0.2rem - var(--fp-label-lean));
    border: 1px solid var(--fp-rule);
    border-radius: var(--fp-pill);
    font-family: var(--fp-mono);
    font-size: 0.8rem;
    color: var(--fp-ink-soft);
    text-decoration: none;
  }
  a[data-chip]:hover { border-color: var(--fp-ink-mute); color: var(--fp-accent); }
  /* On the proof strip the chip carries the fill colour as text. The board is
     the one ground where that clears AAA, at 10.3:1. */
  section[data-band="proof"] [data-chip] { color: var(--fp-amber); white-space: nowrap; }

  /* ── Site header ──────────────────────────────────────────────────────── */
  body > header {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem 1.5rem;
    padding-block: 0.85rem;
    background: var(--fp-bg);
    border-bottom: 1px solid var(--fp-rule);
  }
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    body > header {
      background: var(--fp-bg-blur);
      -webkit-backdrop-filter: blur(10px);
      backdrop-filter: blur(10px);
    }
  }
  body > header > a:first-child {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    min-height: var(--fp-target);
    color: var(--fp-ink);
    font-weight: var(--fp-weight-h1);
    font-size: 1.2rem;
    letter-spacing: -0.02em;
    text-decoration: none;
  }
  body > header > a:first-child:hover { color: var(--fp-ink); }
  body > header > a:first-child :is(svg, img) { display: block; height: var(--fp-logo-height, 3.2rem); width: auto; }
  .tld { color: var(--fp-ink-mute); font-weight: 600; }

  body > header > nav {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.85rem 1.4rem;
  }
  body > header > nav a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--fp-target);
    min-width: var(--fp-target);
    /* The lean the buttons take, on every nav link and not only the pills, so
       the row's baselines stay on one line. Well inside the 44px target, so
       nothing grows. */
    padding-block: calc(0.45rem + var(--fp-label-lean)) calc(0.45rem - var(--fp-label-lean));
    color: var(--fp-ink-soft);
    font-weight: var(--fp-weight-nav);
    font-size: 0.95rem;
    text-decoration: none;
  }
  body > header > nav a:hover { color: var(--fp-ink); text-decoration: underline; }
  /* "page" marks the current page; "location" the section in view. Same mark. */
  body > header > nav a[aria-current] { color: var(--fp-ink); box-shadow: 0 2px 0 var(--fp-accent); }
  /* the header's call to action is the small pill; it needs no size class */
  body > header > nav a.ink,
  body > header > nav a.ghost {
    /* padding-block, and its lean, come from the nav link rule above */
    padding-inline: 1rem;
    border: 1.5px solid transparent;
    border-radius: var(--fp-pill);
    font-size: 0.9rem;
    font-weight: var(--fp-weight-ui);
  }
  body > header > nav a.ink:hover { text-decoration: underline; text-underline-offset: 0.2em; }

  @media (max-width: 45rem) {
    body > header { padding-block: 1.15rem 1.4rem; row-gap: 1.15rem; }
    body > header > nav { justify-content: flex-start; }
  }

  /* ── Site footer ──────────────────────────────────────────────────────── */
  body > footer {
    display: grid;
    row-gap: 1.25rem;
    justify-items: start;
    background: var(--fp-board-deep);
    padding-block: 2.25rem 2.75rem;
    font-size: 0.95rem;
  }
  body > footer > * { width: 100%; }
  body > footer :is(h2, h3) {
    margin-bottom: 0.9rem;
    color: var(--fp-ink);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
  }
  body > footer ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1.4rem;
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.85rem;
  }
  body > footer li { display: flex; }
  body > footer a {
    display: inline-flex;
    align-items: center;
    min-height: var(--fp-target);
    color: var(--fp-ink-soft);
    text-decoration: none;
  }
  body > footer a:hover { color: var(--fp-ink); text-decoration: underline; }
  /* Fine print is running prose. A link in a sentence keeps the line-height
     of the text around it and its underline at rest; the 44px box above is
     for the byline and the version nav, where a link stands alone. */
  body > footer > small a {
    display: inline;
    min-height: 0;
    text-decoration: underline;
  }
  /* the byline row: the site, then "is a FivePaths product." */
  body > footer > p:first-child {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0;
  }
  body > footer > p:first-child a {
    gap: 0.4rem;
    font-weight: var(--fp-weight-h);
    color: var(--fp-accent);
  }
  body > footer > p:first-child a:hover { color: var(--fp-accent-strong); }
  body > footer > p:first-child img { display: block; height: 1.5rem; width: auto; }
  /* the copyright sits at the far edge of the byline row */
  body > footer > p:first-child > :last-child:not(a) { margin-left: auto; }
  body > footer > small {
    display: block;
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--fp-rule);
    font-size: 0.85rem;
  }
  body > footer > small p { margin: 0 0 0.4rem; }
  body > footer > small p:last-child { margin-bottom: 0; }
}

@layer a11y {
  /* The skip link is the first thing in the body and the first thing a
     keyboard reaches. */
  body > a:first-child {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: 0.6rem 1rem;
    background: var(--fp-amber);
    color: var(--fp-amber-ink);
    border-radius: 0 var(--fp-radius-sm) 0 0;
    font-weight: var(--fp-weight-h);
    text-decoration: none;
  }
  body > a:first-child:focus { left: 0; }

  /* Target size, WCAG 2.5.5 AAA. A link that is the whole content of its list
     item or paragraph is a standalone control, not a link in a sentence, so
     it needs a real target. v2 enumerated the components this applied to and
     the list grew every time a component was added; stated structurally it
     covers the ones nobody has written yet.

     Links inside running prose are untouched: they keep the line-height of
     the text around them, which is what the criterion's exception is for.

     The :not() is load-bearing. A whole-card link is also the only child of
     its list item, and without the exclusion this rule made it inline-flex
     and laid its heading, prose and affordance out in a row. A link wrapping
     block content is already far larger than the minimum; the rule is for a
     link whose target is only as big as its text.

     The structural half is a guess. `:only-child` counts elements, not text,
     so "See the <a>record</a> for the full text" matches it, and CSS has no
     test for a text sibling. a[data-target] is the answer for markup that
     knows: the engine marks the links that stand alone. The guess stays in 3.x
     because removing it would move pages that rely on it. */
  main :is(li, p, dd) > a:only-child:not(:has(:is(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, div, figure, table))),
  main a[data-target] {
    display: inline-flex;
    align-items: center;
    min-height: var(--fp-target);
    min-width: var(--fp-target);
  }

  /* Measure, WCAG 1.4.8 AAA. A block of running text stays under 80
     characters. Headings, tables and code are not running text and keep the
     full column. */
  main p,
  main li,
  body > footer > small p {
    max-width: var(--fp-measure);
  }
  main :is(section[data-band="cta"], [data-centered]) p { margin-inline: auto; }
  /* a list already indents its text; the marker column is not part of the measure */
  main :where(ul, ol):not([data-list]) > li { max-width: calc(var(--fp-measure) + 1.7rem); }

  /* Text for assistive technology only. Carries the "opens in a new tab"
     note external links need (WCAG 3.2.5), among other things. */
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* The colour-scheme control WCAG 1.4.8 asks for. Behaviour is in
     theme-1.0.0.js beside this file; the glyphs show one at a time so the
     visible label always names the scheme it offers to move to. */
  body > header > nav button {
    min-height: var(--fp-target);
    padding: 0 0.15rem;
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 0.2em;
    gap: 0.45rem;
  }
  body > header > nav button:hover { background: none; color: var(--fp-accent); }
  body > header > nav button svg { width: 1.05em; height: 1.05em; flex: none; }
  /* unpadded, and the icon is centred by layout, so the text alone leans */
  body > header > nav button > span { translate: 0 var(--fp-label-lean); }
  [data-scheme="dark"] { display: inline; }
  [data-scheme="light"] { display: none; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) [data-scheme="dark"] { display: none; }
    :root:not([data-theme="light"]) [data-scheme="light"] { display: inline; }
  }
  :root[data-theme="dark"] [data-scheme="dark"] { display: none; }
  :root[data-theme="dark"] [data-scheme="light"] { display: inline; }
  :root[data-theme="light"] [data-scheme="dark"] { display: inline; }
  :root[data-theme="light"] [data-scheme="light"] { display: none; }
}
