  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-400-latin.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: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-500-latin.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: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-400-latin.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: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-500-latin.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: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-600-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/ibm-plex-sans-600-latin.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: 'Space Grotesk';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/space-grotesk-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/space-grotesk-400-latin.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: 'Space Grotesk';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/space-grotesk-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/space-grotesk-500-latin.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: 'Space Grotesk';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/space-grotesk-600-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/space-grotesk-600-latin.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: 'Space Grotesk';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/space-grotesk-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/space-grotesk-700-latin.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;
  }
  :root { --ax-px: 48px; --ax-py: 80px; --ax-cardpx: 52px; --ax-cardpy: 60px; }
  * { box-sizing: border-box; }
  body { margin: 0; background: #0e0f11; overflow-x: hidden; font-family: 'IBM Plex Sans', system-ui, sans-serif; }
  a { color: #cdd2d7; text-decoration: none; overflow-wrap: break-word; }
  a:hover { color: #2ea043; }
  ::selection { background: #2ea04355; }
  #process, #industries { scroll-margin-top: 84px; }
  .ax-skip-link { position: absolute; left: 12px; top: -60px; z-index: 200; background: #2ea043; color: #0a1b0f; font-family: 'IBM Plex Mono', monospace; font-size: 13px; font-weight: 600; padding: 12px 18px; border-radius: 0 0 6px 6px; transition: top .15s; }
  .ax-skip-link:focus { top: 0; }
  .ax-grid-2, .ax-grid-3, .ax-grid-4, .ax-grid-hero, .ax-grid-teaser, .ax-grid-footer, .ax-grid-row { display: grid; }
  .ax-grid-2 { grid-template-columns: 1fr 1fr; }
  .ax-grid-3 { grid-template-columns: repeat(3, 1fr); }
  .ax-grid-4 { grid-template-columns: repeat(4, 1fr); }
  .ax-grid-hero { grid-template-columns: 1.15fr 1fr; }
  .ax-grid-teaser { grid-template-columns: 1.2fr 1fr; }
  .ax-grid-footer { grid-template-columns: 1.4fr 1fr 1fr; }
  .ax-grid-row { grid-template-columns: 0.3fr 1.3fr 1fr; }
  .ax-grid-2 > *, .ax-grid-3 > *, .ax-grid-4 > *, .ax-grid-hero > *, .ax-grid-teaser > *, .ax-grid-footer > *, .ax-grid-row > * { min-width: 0; }
  /* Six service cards fill two rows of three exactly, which leaves the trailing
     "not sure what you need" card alone on a third. Spanning the row turns that
     stray cell into a deliberate closing band. Reset below the breakpoint: the
     grid is one column there, and a span of 2 would conjure an implicit second. */
  .ax-grid-3 > .ax-cell-full { grid-column: 1 / -1; }
  .ax-hero-col { padding: 92px var(--ax-cardpx) 96px; }
  .ax-nav-bar { flex-wrap: wrap; }
  .ax-nav-links { display: flex; gap: 32px; font-size: 14px; }
  /* 14px text is an 18px target. The padding takes it to 36px, which is under
     the 37px the "Start a project" CTA beside it already occupies — so the nav
     bar's own height is unchanged and only the hit area grows. */
  .ax-nav-links a { display: block; padding: 9px 0; }
  /* Covers both kinds of "current": aria-current="page" set at build time for a
     real page match, and aria-current="true" set by the scroll-spy in
     NAV_SCRIPT for an in-page section. The page-level link also carries the
     same colour inline, so this only adds behaviour, never changes it. */
  .ax-nav-links a[aria-current] { color: #2ea043; }
  .ax-nav-toggle { display: none; }
  @media (max-width: 860px) {
    :root { --ax-px: 20px; --ax-py: 40px; --ax-cardpx: 22px; --ax-cardpy: 32px; }
    .ax-grid-2, .ax-grid-3, .ax-grid-4, .ax-grid-hero, .ax-grid-teaser, .ax-grid-footer, .ax-grid-row {
      grid-template-columns: 1fr;
    }
    .ax-grid-row > div { border-right: none !important; }
    .ax-grid-3 > .ax-cell-full { grid-column: auto; }
    .ax-hero-col { padding: 40px var(--ax-cardpx) 48px; }
    /* Stacked, so there is room to reach the 44px touch guidance rather than
       just the 24px minimum. The 4px gap goes with it: the links' own padding
       now separates them, and a gap on top of it would only add dead space
       between targets. */
    .ax-nav-links { order: 5; width: 100%; display: none; flex-direction: column; gap: 0; padding-top: 8px; }
    .ax-nav-links a { padding: 13px 0; }
    .ax-nav-links.ax-open { display: flex; }
    .ax-nav-cta-group { order: 2; }
    /* The "Start a project" nav CTA is ~150px of a 320px-wide row, which is
       what wrapped the nav onto a second line and left the hamburger floating
       mid-row with a void beside it. Hidden below the breakpoint — Contact is
       still in the hamburger menu, which is keyboard- and AT-operable, so no
       route is lost. Note it is the *only* route on about/privacy/404: those
       three carry no in-body link to contact.html, unlike the other six. If
       that matters, give them one rather than restoring this. Matched by class,
       not `> a`, so a future second anchor in this group isn't hidden too. */
    .ax-nav-cta-group > .ax-nav-cta { display: none; }
    .ax-nav-toggle { display: inline-flex; }
  }
  .ax-eyebrow { font-family: 'IBM Plex Mono', monospace; font-size: 12px; }
  .ax-card { border: 1px solid #1e2124; border-radius: 10px; background: #0b0c0e; }
  /* Label/value rows built by feeds.py (component and provenance tables). The
     label column is fixed-width on desktop for alignment, which is exactly what
     overflows a phone once the value is a full sentence — so it stacks at the
     one breakpoint the rest of the site uses. */
  /* WCAG 2.2 AA 2.5.8 wants a pointer target of at least 24x24 CSS px, and the
     site's small mono links run 15-20px. Making them physically taller would
     move the layout around them, so instead the hit area is grown with padding
     and pulled straight back with an equal negative margin — the target gets
     bigger, nothing shifts. Links genuinely inside a sentence are exempt under
     the criterion's own "inline" exception and are deliberately left alone;
     applying this everywhere would be padding prose for no benefit.
     .ax-taplist is the same idea where the space already exists: those links
     sit in a line box taller than they are, so display:block claims the room
     the line-height has already reserved rather than adding any. */
  .ax-tap { display: inline-block; padding: 6px 0; margin: -6px 0; }
  .ax-taplist a { display: block; }
  /* The case-study hub's stat rows. Flex is right on desktop, where all three
     figures sit on one line; at 360px they break 2+1 with each column sized to
     its own content, so "640 tests" and "2 gated parts" end up wildly different
     widths and the row reads as ragged rather than tabular. An even two-column
     grid at the breakpoint fixes the alignment without touching desktop. */
  .ax-statrow { display: flex; gap: 28px; flex-wrap: wrap; }
  /* The case-study hub's two cards. Grid already stretches them to equal
     height, but their headings and ledes are different lengths, so the stat
     figures and the "Read the case study" link floated at different heights
     and the pair read as misaligned. Making the card a flex column and letting
     the lede absorb the slack pins both footers to a common baseline.

     This only holds while the two stat captions occupy the same number of
     lines — the figures sit above the captions, so an extra wrapped line in
     one card pushes that card's link down again. Both captions are currently
     two lines by construction. If you add a third case study, check it at
     1280px and 360px rather than assuming. */
  .ax-studycard { display: flex; flex-direction: column; }
  .ax-studycard-lede { flex: 1 1 auto; }
  /* The homepage's two large cards. Their vertical padding was a literal, so it
     could not shrink at the breakpoint the way --ax-cardpy does, and a phone got
     the desktop figure — which is the only reason these are classes.

     Desktop is back to the original 56/64. They were briefly raised to 80/88
     after the padding looked far too tight, but the tightness was the stale
     stylesheet bug (see STYLESHEET below) collapsing it to zero, not the values.
     Once the fingerprinted stylesheet shipped and the real spacing was visible,
     the originals were confirmed correct. Don't raise them again without looking
     at the rendered page with a fresh cache first. */
  .ax-teaser-card { padding: 56px var(--ax-cardpx); }
  .ax-cta-card { padding: 64px var(--ax-cardpx); }
  .ax-kv { display: flex; gap: 14px; padding: 10px 0; border-top: 1px solid #1e2124; }
  .ax-kv-label { flex: 0 0 190px; color: #8b9199; }
  .ax-kv-value { flex: 1; min-width: 0; color: #9aa0a6; overflow-wrap: anywhere; }
  /* Quoted scope statements. These are <blockquote>, not <q>: the publishers'
     statements run 600-1000 characters over several sentences, which is well
     past what <q> is for, and a <q> that long renders generated quote marks
     around what is visibly a paragraph. Switching loses those marks, so the
     left rule and the indent are what now say "this is a quotation" — without
     them the text reads as the site's own prose, which is the one thing these
     quotes exist to prevent. FinToolbox's 218-character statement stays inline
     in <q>, correctly: short quotation, phrasing content, inside a sentence.

     #4a4f55 is one of the two dim tokens that fail AA for text. That is fine
     here and only here: this is a decorative rule, which the palette note
     explicitly permits, and the quoted text itself is #9aa0a6. */
  .ax-quote-figure { margin: 14px 0 0; max-width: 820px; }
  .ax-quote-source { font-family: 'IBM Plex Mono', monospace; font-size: 12px;
                     color: #8b9199; margin-bottom: 6px; }
  .ax-quote { margin: 0; padding-left: 16px; border-left: 2px solid #4a4f55;
              font-size: 13px; line-height: 1.75; color: #9aa0a6; }
  @media (max-width: 860px) {
    .ax-quote { padding-left: 12px; }
    .ax-kv { flex-direction: column; gap: 4px; }
    .ax-kv-label { flex: none; }
    .ax-statrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; }
    /* A stat whose figure or caption is too wide for half of a 360px card —
       the hub's coverage stats carry a scope caption, and the two-part one
       broke as "69.5% ·" / "98.2%". Spanning the row gives it the full width
       instead. Desktop is flex, where grid-column simply doesn't apply. */
    .ax-statrow > .ax-stat-wide { grid-column: 1 / -1; }
    .ax-teaser-card { padding: 40px var(--ax-cardpx); }
    .ax-cta-card { padding: 44px var(--ax-cardpx); }
  }
  .axf { width:100%; background:#0b0c0e; border:1px solid #2a2e33; border-radius:6px; color:#e8eaed; font-family:'IBM Plex Sans',sans-serif; font-size:15px; padding:13px 15px; transition:border-color .15s, box-shadow .15s; }
  .axf:focus { border-color:#2ea043; box-shadow:0 0 0 3px #2ea04355; outline:2px solid transparent; outline-offset:2px; }
  .axf::placeholder { color:#7a8087; }
  textarea.axf { resize:vertical; min-height:130px; }
