/* ==========================================================================
   V1b · Minimal / Mono — Careers Hub case study
   Self-contained: does not depend on styles.css. Same typefaces and token
   naming as the homepage; monochrome by design (no chromatic value anywhere).
   ========================================================================== */

@layer tokens, base, layout, type, components, utilities;

/* Case studies read quietly: this pass narrows the SIZE scale and the chrome.
   It deliberately does not touch typefaces — Fraunces headings, Geist Mono
   labels and Caveat hand notes stand, here and on the homepage. */
@layer tokens {
  :root {
    color-scheme: light;

    /* Typefaces — identical roles to the homepage. */
    --font-display: "Fraunces", Georgia, serif;
    --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    /* alias for the body stack, used where a rule needs the sans voice by name */
    --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --font-hand: "Caveat", "Bradley Hand", "Segoe Script", cursive;
    /* Geist Mono, matching the homepage. Courier Prime read as a typewriter
       but lost legibility at body and label sizes; Geist has a normal
       x-height, so label weight drops back to the homepage's 500. */
    --font-mono: "Geist Mono", ui-monospace, "SF Mono", "SFMono-Regular", Menlo, monospace;
    --weight-label: 500;

    /* Ground and structure — eleven values, zero hue. */
    --bg: #fdfbf6;
    --surface: #f5f2ea;
    --tint: #efebe2;
    --rule: #e4dfd4;
    --rule-strong: #d5cfc2;

    --ink: #1c1b19;
    --ink-2: #4a4948;
    /* Was #918e89 — 2.9:1 on --tint, under the 4.5 AA floor, and it carries
       every label, caption and figure note. Darkened to clear 4.5 on bg,
       surface and tint alike. */
    --muted: #6c6966;

    /* Three tones, each with one job, all desaturated enough to sit on paper
       without shouting:
         blue  — orientation. Which feature is this, what is this card called.
         green — improvement. A number that went up, a thing that shipped.
         red   — shortfall. A baseline that was bad, a state that failed.
       Each has a --soft variant for the inverted bands and a --tint for
       pill grounds. Body copy, rules and captions stay monochrome. */
    --accent: #35508c;
    --accent-soft: #9fb0dc;

    --positive: #2f6a4d;
    --positive-soft: #93bfa3;
    --positive-tint: #edf3ef;

    --negative: #a04a35;
    --negative-soft: #d69b88;
    --negative-tint: #f7efec;

    /* The inverted bands: core problem, principle, impact. */
    --band-bg: #1c1b19;
    --band-ink: #fdfbf6;
    --band-ink-2: #b5b0a7;
    --band-muted: #8b877f;
    --band-rule: #33322f;

    /* Display ramp — stepped down twice from the comp, now roughly 60% of
       the original values, so the titles sit close to the prose rather than
       dominating the fold. Names are historical: --d148 is no longer 148px,
       it is still the top of the ramp. */
    --d148: clamp(2.25rem, 5.3vw, 4.75rem);
    --d96: clamp(1.875rem, 4.2vw, 3.75rem);
    --d88: clamp(1.875rem, 3.9vw, 3.5rem);
    --d76: clamp(1.8125rem, 3.9vw, 3.5rem);
    --d68: clamp(1.625rem, 3.05vw, 2.75rem);
    --d62: clamp(1.625rem, 3.2vw, 2.875rem);
    --d58: clamp(1.5rem, 2.9vw, 2.625rem);
    --d56: clamp(1.5rem, 2.8vw, 2.5rem);
    --d52: clamp(1.375rem, 2.65vw, 2.375rem);
    --d40: clamp(1.25rem, 2.22vw, 2rem);
    --d36: clamp(1.1875rem, 1.95vw, 1.75rem);
    --d34: clamp(1.1875rem, 1.95vw, 1.75rem);
    --d28: clamp(1.125rem, 1.67vw, 1.5rem);
    --d26: clamp(1.0625rem, 1.53vw, 1.375rem);
    --d22: 1.125rem;

    /* Supporting copy stays within a deliberate 16–20px desktop range.
       Display headings keep their separate fluid ramp above; this scale is
       for labels, captions, body copy and section introductions. */
    --text-2xs: 1rem;
    --text-xs: 1rem;
    --text-sm: 1rem;
    --text-base: 1.0625rem;
    --text-md: 1.125rem;
    --text-lg: 1.25rem;
    --text-card: 1.25rem;
    --text-step: 1.0625rem;

    /* Geist is narrower than Courier, so tracked uppercase labels need the
       homepage's full 0.08em to stay separable at 11–13px. */
    --tracking-label: 0.08em;
    --tracking-tight: -0.012em;
    --tracking-display: -0.022em;

    /* Two measures. Prose is set narrow to read well; media gets its own,
       wider one, because the screenshots are the evidence and shrinking them
       to the text measure was throwing away detail. */
    /* Nav tokens copied from styles.css so the case-study nav resolves to the
       identical pill. If the homepage's nav changes, these follow. */
    --nav-height: 64px;
    --nav-inset: 24px;
    --nav-inset-x: 16px;
    --nav-clear: calc(var(--nav-height) + var(--nav-inset));
    --radius-nav: 20px;
    --radius-pill: 999px;
    --radius-sm: 6px;
    --nav-glass: rgb(253 251 246 / 0.68);
    --nav-glass-strong: rgb(253 251 246 / 0.82);
    --nav-glass-border: rgb(28 27 25 / 0.08);
    --nav-backdrop: saturate(145%);
    --nav-accent: rgb(41 82 255);
    --nav-accent-wash: rgb(41 82 255 / 0.08);
    /* The nav sets its own text sizes: this page's --text-sm is a caption
       step, the homepage's is 13px UI. */
    --nav-text: 0.8125rem;
    --nav-micro: 0.6875rem;

    --col-wide: 62rem;  /* 992 — one measure for prose and media alike */
    --col-media: 62rem; /* figures follow it: click-to-zoom carries the detail */
    --gutter: 40px;
  }

  /* Phones get a slightly tighter scale than desktop, not a smaller one. The
     previous ramp here bottomed out at 12px labels, 13px captions and 14px body
     — sizes that read fine on a 27in display at arm's length and are genuinely
     hard work at phone distance. Body copy now lands on 16px, which is the floor
     most people can read comfortably one-handed, and captions on 15px.

     Desktop stays where it was (16-20px); this only ever narrows the gap. The
     display tokens are untouched, so headings still outrank body by the same
     ratio and the hierarchy reads the same — the whole ramp just moved up off
     the floor. */
  /* The four-up held its 2x2 only above 1100px, which meant a laptop window a
   little under that — the common case — got four full-width screens stacked
   down the page instead of the grid. At 820px the two columns are still ~380px
   each, wide enough for a product capture to read; below that they stack. */
@media (max-width: 820px) {
  .seq { grid-template-columns: minmax(0, 1fr); gap: 48px; }
  .pair { flex-direction: column; gap: 40px; }
}

@media (max-width: 720px) {
    :root {
      --text-2xs: 0.8125rem;   /* 13 — micro label, uppercase and tracked */
      --text-xs: 0.8125rem;    /* 13 — eyebrow, meta */
      --text-sm: 0.9375rem;    /* 15 — caption, figure note, seq note */
      --text-base: 1rem;       /* 16 — body copy */
      --text-md: 1rem;         /* 16 */
      --text-lg: 1.0625rem;    /* 17 — lead-in copy */
    }
  }
}

@layer tokens {
  /* Scaler blue is an orientation accent in CS01, never a body-copy colour. */
  :is(body.case-study--cs1, body.case-study--cs2, body.case-study--cs3, body.case-study--ai) {
    --accent: #0b63e5;
    --accent-soft: #9fbeff;
    --accent-tint: #edf4ff;
  }

  :root[data-theme="dark"] :is(body.case-study--cs1, body.case-study--cs2, body.case-study--cs3, body.case-study--ai) {
    --accent: #8cb5ff;
    --accent-soft: #0b63e5;
    --accent-tint: #17243a;
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) :is(body.case-study--cs1, body.case-study--cs2, body.case-study--cs3, body.case-study--ai) {
      --accent: #8cb5ff;
      --accent-soft: #0b63e5;
      --accent-tint: #17243a;
    }
  }
}
@layer tokens {
  /* --- Dark theme ------------------------------------------------------
     Set by the inline script in each page's head (same `portfolio-theme`
     key as the rest of the site), with a prefers-color-scheme fallback for
     no-JS. Keep the two blocks below identical. */
  :root[data-theme="dark"] {
    color-scheme: dark;

    --bg: #171613;
    --surface: #201f1b;
    --tint: #272621;
    --rule: #302e29;
    --rule-strong: #3e3b34;

    --ink: #f4f1e9;
    --ink-2: #cdc8bd;
    --muted: #948f87;

    /* The emphasis bands invert with the theme: on a dark page the band that
       stands out is the light one. Every band token flips, so the components
       using them need no dark-specific rules. */
    --band-bg: #f2efe6;
    --band-ink: #1c1b19;
    --band-ink-2: #4a4948;
    --band-muted: #6f6c64;  /* was #7d7970 — 3.6:1 on the light band */
    --band-rule: #ddd8cc;

    /* Tones lighten on dark ground; the --soft variants darken, because they
       are used on the bands, which are now light. */
    --accent: #93a8ea;
    --accent-soft: #35508c;
    --positive: #7fc0a0;
    --positive-soft: #2f6a4d;
    --positive-tint: #1c2822;
    --negative: #e09580;
    --negative-soft: #a04a35;
    --negative-tint: #2c1e19;

    --nav-glass: rgb(26 25 23 / 0.66);
    --nav-glass-strong: rgb(26 25 23 / 0.78);
    --nav-glass-border: rgb(241 239 235 / 0.1);
    --nav-backdrop: saturate(140%);
    --nav-accent: rgb(126 156 255);
    --nav-accent-wash: rgb(126 156 255 / 0.14);
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #171613;
    --surface: #201f1b;
    --tint: #272621;
    --rule: #302e29;
    --rule-strong: #3e3b34;

    --ink: #f4f1e9;
    --ink-2: #cdc8bd;
    --muted: #948f87;

    /* The emphasis bands invert with the theme: on a dark page the band that
       stands out is the light one. Every band token flips, so the components
       using them need no dark-specific rules. */
    --band-bg: #f2efe6;
    --band-ink: #1c1b19;
    --band-ink-2: #4a4948;
    --band-muted: #6f6c64;  /* was #7d7970 — 3.6:1 on the light band */
    --band-rule: #ddd8cc;

    /* Tones lighten on dark ground; the --soft variants darken, because they
       are used on the bands, which are now light. */
    --accent: #93a8ea;
    --accent-soft: #35508c;
    --positive: #7fc0a0;
    --positive-soft: #2f6a4d;
    --positive-tint: #1c2822;
    --negative: #e09580;
    --negative-soft: #a04a35;
    --negative-tint: #2c1e19;

    --nav-glass: rgb(26 25 23 / 0.66);
    --nav-glass-strong: rgb(26 25 23 / 0.78);
    --nav-glass-border: rgb(241 239 235 / 0.1);
    --nav-backdrop: saturate(140%);
    --nav-accent: rgb(126 156 255);
    --nav-accent-wash: rgb(126 156 255 / 0.14);
    }
  }
}

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

  html {
    -webkit-text-size-adjust: 100%;
    /* Clears the floating nav on every fragment jump. */
    scroll-padding-top: calc(var(--nav-clear) + 16px);
  }

  body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--text-md);
    line-height: 1.65;
    letter-spacing: var(--tracking-tight);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  h1,
  h2,
  h3,
  h4,
  h5,
  p,
  figure {
    margin: 0;
  }

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

  img,
  video {
    display: block;
    max-width: 100%;
  }

  ::selection {
    background: var(--ink);
    color: var(--bg);
  }
}

@layer layout {
  .wrap {
    width: 100%;
    max-width: var(--col-wide);
    margin-inline: auto;
  }

  .section {
    padding-inline: var(--gutter);
  }

  /* Sections that carry screenshots run to the media measure. Everything
     else — hero, core problem, principle, impact, takeaways — stays at the
     prose measure, so the page still reads as a narrow column of argument
     with wide evidence hung off it. */
  .section--media > .wrap,
  .section--glance > .wrap,
  .section--flow > .wrap,
  .section--feature > .wrap {
    max-width: var(--col-media);
  }

  /* ...and inside those, the text-bearing rows are pulled back in, so a
     caption never runs 1312px wide and the rules above them stay put. */
  .section--feature .feature-head,
  .section--feature .user-story,
  .section--feature .caption-row,
  .section--feature .problem-block > .card-head,
  .section--feature .stack-block > .cap,
  .section--feature .stack-block > .figure-note,
  .section--flow .flow-head,
  .section--glance .section-head,
  /* The AI entries set their narrative as beats and their captions as notes
     directly under the wrap; both are prose and follow the prose measure. A
     beat inside a beat-pair is excluded — it shares a media row. */
  .section--feature > .wrap > .beat,
  .section--feature > .wrap > .figure-note,
  .section--media > .wrap > .figure-note {
    /* Centred, so prose in a wide section shares its left edge with prose in
       a narrow one. width:100% is load-bearing: several of these sit in
       flex-column parents, where an auto inline margin would otherwise make
       the item shrink-to-fit and centre on its text instead of the measure. */
    width: 100%;
    max-width: var(--col-wide);
    margin-inline: auto;
  }

  /* A caption under a .pair belongs to the row above it, so it starts where
     that row starts. The centring above is for captions under a single
     full-width figure, where centring keeps them on the page's spine — but a
     pair spans the wider media measure, so a centred caption sits indented from
     the images it is describing. Measure is kept; only the centring goes. */
  .stack-block:has(> .pair) > .cap,
  .stack-block:has(> .pair) > .figure-note {
    margin-inline: 0;
  }

  .section--glance,
  .section--flow,
  .section--feature,
  .section--impact,
  .section--takeaways,
  .section--footer {
    padding-block: 0;
  }

  .section--hero {
    padding-block: calc(var(--nav-clear) + 24px) 56px;
  }
  /* The caption's descenders ended flush with the band edge below it, so the
     colour changed the moment the sentence stopped and the note read as part
     of the dark section rather than the media above it. A bottom pad gives
     the caption its own ground to sit on. */
  .section--media {
    padding-block: 0 44px;
  }
  /* padding-block, not the padding shorthand. The shorthand's 0 was overriding
     .section's padding-inline: var(--gutter), so this band alone ran edge to
     edge — invisible on desktop, where the wrap's 992px max-width keeps content
     off the sides anyway, and very visible on a 390px phone, where the project
     facts and the process list ended up flush against both edges while every
     other section kept its 20px gutter. */
  .section--glance {
    padding-block: 120px 96px;
  }
  .section--stats {
    padding-block: 120px;
  }
  /* Bottom only. This was the one section on the page with no vertical padding at
     either end, so the last process step sat flush against the top edge of the
     dark Core Problem band below it — the light copy and the inverted band met
     with no seam at all. The top needs nothing: .section--glance above already
     closes with 108px, and adding more here would double it. */
  .section--process {
    padding-bottom: 110px;
  }
  /* The rule above gives .section--process no top padding because on the AI
     entries .section--glance sits above it and already closes with 108px. On
     Academy this is the FIRST section after the hero, so that padding never
     arrives and the boundary came out at 34px against CS1's 84px. */
  .case-study--cs3 .section--process {
    padding-top: 50px;
  }


  .section--problem {
    padding-block: 110px;
    background: var(--band-bg);
  }
  .section--origin {
    padding-top: 120px;
  }
  .section--flow {
    padding-top: 120px;
  }
  .section--principle {
    padding-block: 100px 40px;
  }
  .section--feature {
    padding-top: 130px;
  }
  .section--principle + .section--feature {
    padding-top: 56px;
  }
  #feature-02 {
    padding-bottom: 110px;
  }
  #feature-03 {
    padding-bottom: 130px;
    background: var(--tint);
  }
  .section--impact,
  .section--takeaways,
  .section--footer {
    padding-top: 130px;
  }

  /* A frame-accurate vertical spacer, mirroring the design's spacer nodes. */
  .sp-16 { height: 16px; }
  .sp-22 { height: 22px; }
  .sp-56 { height: 56px; }

  .stack {
    display: flex;
    flex-direction: column;
  }
  .row {
    display: flex;
  }
}

@layer type {
  .eyebrow,
  .micro {
    display: block;
    font-family: var(--font-mono);
    font-weight: var(--weight-label);
    line-height: 1.5;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }
  .eyebrow { font-size: var(--text-xs); }
  .micro { font-size: var(--text-2xs); }

  /* Fraunces, same as the homepage's headings — the .d-* steps below drive
     its opsz axis per size. Every step sits at opsz 40 — the text cut, the
     same one the homepage h2 renders at. Fraunces' display cuts (96/144)
     thin the hairlines badly at these sizes. */
  .display {
    font-family: var(--font-display);
    font-weight: 400;
    letter-spacing: var(--tracking-display);
    font-variation-settings: "opsz" 40;
    color: var(--ink);
  }

  .d-hero { font-size: var(--d148); line-height: 0.92; font-variation-settings: "opsz" 40; }
  .d-96 { font-size: var(--d96); line-height: 0.92; font-variation-settings: "opsz" 40; }
  .d-88 { font-size: var(--d88); line-height: 0.95; font-variation-settings: "opsz" 40; }
  .d-76 { font-size: var(--d76); line-height: 1.02; font-variation-settings: "opsz" 40; }
  .d-76--figure { line-height: 0.95; }

  /* Underline for a phrase inside a display headline. Real text-decoration,
     not a border, so it follows the line break and skips descenders. Drawn in
     a wash of the accent rather than the accent itself — at hero size a full
     blue rule under two words competes with the figure the headline is
     actually about. */
  .hl-underline {
    text-decoration: underline;
    text-decoration-color: var(--rule-strong);
    text-decoration-color: color-mix(in oklab, var(--accent) 55%, transparent);
    text-decoration-thickness: 0.055em;
    text-underline-offset: 0.14em;
    text-decoration-skip-ink: auto;
  }
  .d-68 { font-size: var(--d68); line-height: 0.98; font-variation-settings: "opsz" 40; }
  .d-62 { font-size: var(--d62); line-height: 1; font-variation-settings: "opsz" 40; }
  .d-58 { font-size: var(--d58); line-height: 0.95; font-variation-settings: "opsz" 40; }
  .d-56 { font-size: var(--d56); line-height: 1; font-variation-settings: "opsz" 40; }
  .d-52 { font-size: var(--d52); line-height: 0.9; font-variation-settings: "opsz" 40; }
  .d-40 { font-size: var(--d40); line-height: 1.1; }
  .d-36 { font-size: var(--d36); line-height: 1.3; }
  .d-34 { font-size: var(--d34); line-height: 1.1; }
  .d-28 { font-size: var(--d28); line-height: 1.1; }
  .d-26 { font-size: var(--d26); line-height: 1.3; }
  .d-26--prose { line-height: 1.4; }
  .d-22 { font-size: var(--d22); line-height: 1.1; }

  .lede {
    font-size: var(--text-lg);
    line-height: 1.65;
    letter-spacing: var(--tracking-tight);
    color: var(--muted);
  }
  .prose {
    font-size: var(--text-md);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--muted);
  }
  .base {
    font-size: var(--text-base);
    line-height: 1.5;
    letter-spacing: 0;
    color: var(--muted);
  }
  .base--ink-2 { color: var(--ink-2); }
  .cap {
    font-size: var(--text-sm);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--muted);
  }
  .cap--italic { font-style: italic; }

  .h-card {
    font-size: var(--text-card);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .h-step {
    font-size: var(--text-step);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .h-sub {
    font-size: var(--text-md);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .label-med {
    font-size: var(--text-md);
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
}

@layer components {
  /* --- Nav ------------------------------------------------------------- */
  /* --- Nav -------------------------------------------------------------
     Ported from styles.css rather than approximated, so the case-study nav
     and the homepage nav are the same object: a transparent fixed shell with
     the glass pill as .nav > .wrap (the homepage calls it .container). Every
     value below is the homepage's. */
  .nav {
    position: fixed;
    inset: 0 0 auto;
    z-index: 100;
    padding: var(--nav-inset) var(--nav-inset-x) 16px;
    background: none;
    border: 0;
    pointer-events: none;
    transition:
      opacity 0.24s ease,
      transform 0.24s ease,
      visibility 0s;
  }

  /* v1b-nav.js adds this once .section--hero clears the viewport, and removes it
     on the way back up; .outline is the navigation past the fold. Mirrors
     .nav.is-retired in styles.css — keep the two in step.

     visibility, not opacity alone: an opacity-0 bar still takes tab focus, which
     would strand keyboard users on links they cannot see. Delayed by the fade
     duration on exit, instant on return (visibility does not interpolate).

     .nav > .wrap re-declares pointer-events: auto, so the shell's `none` above is
     not enough on its own to make this inert. */
  .nav.is-retired {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    pointer-events: none;
    transition:
      opacity 0.24s ease,
      transform 0.24s ease,
      visibility 0s linear 0.24s;
  }

  .nav.is-retired > .wrap {
    pointer-events: none;
  }

  @media (prefers-reduced-motion: reduce) {
    .nav,
    .nav.is-retired {
      transition: none;
    }
  }

  .nav > .wrap {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* .wrap follows this page's 62rem measure; the homepage nav sits on its
       66rem one. The pill has to match the homepage, not the body copy. */
    max-width: 66rem;
    gap: 24px;
    min-height: var(--nav-height);
    padding: 12px 24px;
    border-radius: var(--radius-nav);
    border: 1px solid var(--nav-glass-border);
    background: var(--nav-glass);
    -webkit-backdrop-filter: var(--nav-backdrop);
    backdrop-filter: var(--nav-backdrop);
    pointer-events: auto;
    transition: background-color 0.24s ease, border-color 0.24s ease;
  }
  .nav.is-scrolled > .wrap { background: var(--nav-glass-strong); }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .nav > .wrap { background: var(--bg); }
  }

  .nav-logo {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--nav-text);
    font-weight: 600;
    letter-spacing: 0.01em;
    text-transform: none;
  }
  .nav-logo span {
    display: inline-flex;
    align-items: center;
    min-height: 1.2em;
    padding-left: 8px;
    border-left: 1px solid var(--rule);
    color: var(--muted);
    font-family: var(--font-mono);
    font-size: var(--nav-micro);
    font-weight: 500;
    letter-spacing: calc(var(--tracking-label) * 0.92);
    text-transform: uppercase;
  }

  .nav-actions {
    display: flex;
    align-items: center;
    gap: 24px;
  }
  .nav-links {
    display: flex;
    align-items: center;
    gap: 20px;
  }
  .nav-links a {
    position: relative;
    color: color-mix(in srgb, var(--ink) 76%, var(--muted));
    font-family: var(--font-body);
    font-size: var(--nav-text);
    font-weight: 500;
    letter-spacing: 0.01em;
    text-transform: none;
    transition: color 0.16s ease;
  }
  .nav-links a::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.45rem;
    height: 1px;
    border-radius: 999px;
    background: currentColor;
    opacity: 0.56;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.24s ease, opacity 0.16s ease;
  }
  .nav-links a:hover,
  .nav-links a[aria-current="page"] { color: var(--ink); }
  .nav-links a:hover::after,
  .nav-links a[aria-current="page"]::after {
    opacity: 0.88;
    transform: scaleX(1);
  }

  /* Theme toggle — the homepage's segmented pill. */
  .theme-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 30px;
    padding: 0 8px 0 4px;
    border: 1px solid color-mix(in srgb, var(--nav-glass-border) 78%, transparent);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--nav-glass-strong) 86%, transparent);
    color: var(--ink-2);
    cursor: pointer;
    transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease;
  }
  .theme-toggle:hover {
    border-color: var(--rule-strong);
    background: color-mix(in srgb, var(--nav-glass-strong) 94%, transparent);
    color: var(--ink);
  }
  .theme-toggle-track {
    position: relative;
    flex: none;
    width: 26px;
    height: 15px;
    border-radius: var(--radius-pill);
    background: var(--nav-accent-wash);
    transition: background 0.24s ease;
  }
  .theme-toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--nav-accent);
    transition: transform 0.24s ease;
  }
  :root[data-theme="dark"] .theme-toggle-thumb { transform: translateX(11px); }
  .theme-toggle-text {
    min-width: 4ch;
    font-family: var(--font-mono);
    font-size: var(--nav-micro);
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-align: left;
    text-transform: uppercase;
  }
  @media (prefers-reduced-motion: reduce) {
    .theme-toggle-thumb,
    .nav-links a::after { transition: none; }
  }

  /* Mobile nav — hamburger and the panel that hangs off the pill. */
  .nav-toggle {
    display: none;
    position: relative;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: transparent;
    cursor: pointer;
  }
  .nav-toggle span,
  .nav-toggle::before,
  .nav-toggle::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 14px;
    height: 1.5px;
    margin-left: -7px;
    margin-top: -0.75px;
    border-radius: 2px;
    background: var(--ink);
    transition: transform 0.24s ease, opacity 0.16s ease;
  }
  /* Centre-relative, so the bars stay centred when the mobile pass grows this
     box from 32px to 44px. Fixed tops left them riding high in the larger box. */
  .nav-toggle::before { transform: translateY(-4px); }
  .nav-toggle::after { transform: translateY(4px); }
  .nav-toggle.is-open::before { transform: rotate(45deg); }
  .nav-toggle.is-open span { opacity: 0; }
  .nav-toggle.is-open::after { transform: rotate(-45deg); }

  /* --- Hero ------------------------------------------------------------ */
  .hero-eyebrow-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 22px;
  }
  .hero-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ink);
    flex: none;
  }
  .hero-headline {
    max-width: 19ch;
    margin: 0;
    font-size: clamp(2.75rem, 4.35vw, 4rem);
    line-height: 0.98;
    letter-spacing: -0.026em;
  }
  .hero-title-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 350px);
    align-items: center;
    gap: clamp(40px, 6vw, 96px);
    margin-top: 34px;
  }
  .hero-title-outcome {
    display: block;
  }
  .hero-title-line {
    display: block;
    white-space: nowrap;
  }
  .hero-title-context {
    position: relative;
    margin: 0;
    padding: 28px;
    border: 0;
    border-radius: 6px;
    background-color: #faf7ed;
    background-image:
      linear-gradient(to right, transparent 0 18px, rgb(176 84 82 / 0.16) 18px 19px, transparent 19px),
      repeating-linear-gradient(to bottom, transparent 0 25px, rgb(53 80 140 / 0.17) 25px 26px),
      radial-gradient(circle at 22% 31%, rgb(108 105 102 / 0.08) 0 0.55px, transparent 0.8px);
    background-size: auto, auto, 13px 15px;
    box-shadow: 0 5px 12px rgb(28 27 25 / 0.065), 0 1px 2px rgb(28 27 25 / 0.045);
    font-family: var(--font-body);
    font-size: clamp(1rem, 1.35vw, 1.2rem);
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -0.015em;
    color: var(--ink);
    transform: rotate(1.25deg);
  }
  .hero-title-context::before {
    display: none;
  }
  .hero-title-context::after {
    content: "";
    position: absolute;
    top: -14px;
    left: 50%;
    width: 118px;
    height: 30px;
    background: rgb(225 215 190 / 0.72);
    box-shadow: 0 2px 4px rgb(28 27 25 / 0.08);
    clip-path: polygon(2% 8%, 98% 0, 96% 92%, 1% 100%);
    transform: translateX(-50%) rotate(-2deg);
    pointer-events: none;
  }
  .hero-title-outcome strong {
    font-weight: 600;
    color: var(--accent);
  }
  .hero-title-period {
    color: var(--ink-2);
  }

  /* Single-line hero variant: one combined headline, no context note. */
  .hero-title-row--single {
    grid-template-columns: minmax(0, 1fr);
  }
  .hero-headline--single strong {
    font-weight: 600;
    color: var(--accent);
  }
  /* CS1's line is short enough to hold a tight measure; CS2's and Academy's run
     the full width of the page, so they take no cap. Without this Academy
     inherited .hero-headline's 19ch, which is a measure for a three-word
     stacked metric, not a sentence — it broke the title onto five lines. */
  .case-study--cs1 .hero-headline--single { max-width: 32ch; }
  .case-study--cs2 .hero-headline--single { max-width: none; }
  .case-study--cs3 .hero-headline--single { max-width: none; }

  /* The AI entries carry sentence-length titles, not the three-word kind the
     19ch measure was cut for — at that width they stack four lines deep and
     strand half the page. They take the full column instead. */
  .case-study--ai .hero-headline { max-width: none; }

  .hero-sub-row {
    display: flex;
    gap: 48px;
    margin-top: 44px;
  }
  .hero-metric {
    flex: 0 1 420px;
    min-width: 0;
    font-size: var(--d22);
    line-height: 1.35;
  }
  /* Bolded phrases inside the hero context copy — the nouns and the verbs
     a skimmer needs, at full ink so the contrast reads. */
  .hero-metric strong {
    font-weight: 700;
    color: var(--ink);
  }
  .hero-subtitle {
    flex: 1 1 0;
    min-width: 0;
    max-width: 48ch;
  }
  .hero-subtitle-intro {
    font-size: var(--text-base);
    line-height: 1.5;
  }
  .hero-fix-list {
    display: grid;
    gap: 8px;
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
  }
  .hero-fix-list li {
    position: relative;
    padding-left: 18px;
    font-size: var(--text-base);
    line-height: 1.45;
    color: var(--ink-2);
  }
  .hero-fix-list li::before {
    content: "";
    position: absolute;
    top: 0.62em;
    left: 1px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    transform: translateY(-50%);
  }

  /* Hero fact pointers — pod/timeline/role facts. The label used to sit
     inline with the value, 8px apart, so the eye had to re-find where each
     answer began on every row. Now it is a two-column rail: mono labels in a
     fixed column, values aligned on a shared left edge, hairlines between
     rows. Same ink, less work to read. */
  .hero-fix-list--facts {
    margin-top: 0;
    gap: 0;
    border-top: 1px solid var(--rule);
    border-top-color: color-mix(in oklab, var(--accent) 16%, var(--rule));
  }
  .hero-fix-list--facts li {
    display: grid;
    grid-template-columns: 6.5rem minmax(0, 1fr);
    align-items: baseline;
    gap: 0 28px;
    padding: 12px 0;
    border-bottom: 1px solid var(--rule);
    border-bottom-color: color-mix(in oklab, var(--accent) 16%, var(--rule));
    color: var(--ink);
  }
  .hero-fix-list--facts li::before {
    content: none;
  }
  .hero-fact-label {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    /* Blue is the system's orientation tone, and these three facts are pure
       orientation — who, how long, what I did. One hue, one job; the values
       stay full ink so the colour never competes with the answer. */
    color: var(--muted);
    color: color-mix(in oklab, var(--accent) 45%, var(--muted));
    margin-right: 0;
  }

  @media (max-width: 720px) {
    .hero-fix-list--facts li {
      grid-template-columns: minmax(0, 1fr);
      gap: 6px 0;
      padding: 14px 0;
    }
  }

  .process-strip {
    display: flex;
    margin-top: 34px;
  }
  .process-step {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 24px 0 0;
    border-top: 2px solid var(--rule-strong);
  }

  /* --- Process sequence --------------------------------------------------
     The right column of the stack block. Was a paragraph; a paragraph makes
     the reader reconstruct the order themselves, so it is now an ordered list
     with a hairline running through the step numbers. Numbers are real <ol>
     content so the order survives with styles off. */
  .process-seq {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .seq-step {
    position: relative;
    display: grid;
    grid-template-columns: 2rem minmax(0, 1fr);
    gap: 0 14px;
    padding-bottom: 20px;
  }

  .seq-step:last-child {
    padding-bottom: 0;
  }

  /* The thread. Starts below the number and stops at the next one, so it reads
     as a connection rather than a border down the column. */
  .seq-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 0.42rem;
    top: 1.35em;
    bottom: 0.35em;
    width: 1px;
    background: var(--rule-strong);
  }

  .seq-num {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: var(--weight-label);
    line-height: 1.5;
    letter-spacing: var(--tracking-label);
    color: var(--accent);
  }

  .seq-label {
    display: block;
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
  }

  .seq-text {
    margin: 2px 0 0;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }

  @media (max-width: 860px) {
    .seq-step { grid-template-columns: 1.7rem minmax(0, 1fr); }
  }

  /* --- Tool pills --------------------------------------------------------
     First-fold answer to "what did she work with". The marks are the official
     single-colour brand glyphs on one 24px grid, inheriting currentColor —
     the page carries no chromatic value anywhere, so full-colour logos would
     be the loudest thing on it. */
  .tools-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 30px;
  }

  .tools-row > .eyebrow {
    margin-right: 2px;
    color: var(--muted);
  }

  .tool-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* Optical, not equal: a glyph reads lighter than a cap-height letter, so
       the left inset is tighter than the right. */
    padding: 7px 15px 7px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: -0.005em;
    white-space: nowrap;
    transition: border-color 0.16s ease, background-color 0.16s ease;
  }

  /* Real brand marks vary a lot in ink coverage — a filled triangle reads far
     heavier than a hairline knot at the same box size. 14px with a slight
     knock-back evens them out against the label. */
  .tool-pill svg {
    width: 20px;
    height: 20px;
    flex: none;
    fill: currentColor;
  }

  /* No special ground for the AI pills any more: the marks carry their own
     brand colour now, so tinting three chips just made them look like a
     different material. One neutral chip, colour does the signalling. */



  /* Bolder marks. Several of these logos are intrinsically fine-lined — the
     Claude starburst, the OpenAI knot, the Metabase dot matrix — and at pill
     size they thin out to texture. Stroking each glyph in its own colour and
     painting the stroke *under* the fill thickens it without distorting the
     shape. stroke and paint-order are inheritable, which is what lets them
     reach inside the <use> shadow tree. */
  .tool-pill svg {
    stroke: currentColor;
    stroke-width: 0.55;
    stroke-linejoin: round;
    paint-order: stroke fill;
  }

  /* Figma carries its own five fills and is already solid, so an ink-coloured
     stroke would outline it in the wrong colour. */
  .tool-pill[data-tool="figma"] svg {
    stroke: none;
  }

  /* Per-mark tuning: ink coverage differs enormously between these logos, so a
     single stroke width leaves the fine ones weak and the solid ones blobby.
     Vercel and VS Code are already solid shapes and get none. */
  .tool-pill[data-tool="claude"]    svg { stroke-width: 1.05; }
  .tool-pill[data-tool="metabase"]  svg { stroke-width: 0.8; }  /* dots merge past ~1 */
  .tool-pill[data-tool="gpt"]       svg { stroke-width: 0.85; }
  .tool-pill[data-tool="mixpanel"]  svg { stroke-width: 0.7; }
  .tool-pill[data-tool="storyblok"] svg { stroke-width: 0.5; }
  .tool-pill[data-tool="vercel"]    svg,
  .tool-pill[data-tool="github"]    svg,
  .tool-pill[data-tool="vscode"]    svg { stroke-width: 0; }


  /* Official brand colours. Figma is excluded on purpose: its mark is five
     colours and carries its own fills inside the symbol. Vercel and VS Code are
     pure black, so they follow the ink token and flip with the theme instead
     of disappearing on a dark ground. */
  [data-tool="claude"]    svg { color: #D97757; }
  [data-tool="gpt"]       svg { color: #412991; }
  [data-tool="storyblok"] svg { color: #09B3AF; }
  [data-tool="mixpanel"]  svg { color: #7856FF; }
  [data-tool="metabase"]  svg { color: #509EE3; }
  [data-tool="vscode"]    svg { color: #0A84C9; }
  /* GitHub, Vercel and VS Code are all near-black, so they follow the ink token
     and flip with the theme rather than vanishing on a dark ground. */
  [data-tool="vercel"]    svg,
  [data-tool="github"]    svg { color: var(--ink); }

  :root[data-theme="dark"] [data-tool="gpt"] svg { color: #9B8AF0; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) [data-tool="gpt"] svg { color: #9B8AF0; }
  }

  /* Name on hover only: collapsed the pill is an icon chip, so a row of tools
     reads as marks rather than a wall of text. The label stays in the DOM
     (clipped, not display:none) so screen readers still announce it, and the
     title attribute covers the pointer case. */
  .tool-pill {
    gap: 0;
  }

  .tool-pill .tool-name {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition:
      max-width 0.3s var(--ease, ease),
      margin-left 0.3s var(--ease, ease),
      opacity 0.18s var(--ease, ease);
  }

  .tool-pill:hover .tool-name,
  .tool-pill:focus-visible .tool-name {
    max-width: 10rem;
    margin-left: 0.42rem;
    opacity: 1;
  }

  /* No hover on touch, so the names stay out permanently. */
  @media (hover: none) {
    .tool-pill .tool-name {
      max-width: 10rem;
      margin-left: 0.42rem;
      opacity: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .tool-pill .tool-name { transition: none; }
  }

  @media (max-width: 720px) {
    .tools-row { gap: 8px; margin-top: 24px; }
    .tool-pill { padding: 5px 11px 5px 9px; font-size: var(--text-sm); }
  }

  /* --- Process sequence --------------------------------------------------
     The right column of the stack block. Was a paragraph; a paragraph makes
     the reader reconstruct the order themselves, so it is now an ordered list
     with a hairline running through the step numbers. Numbers are real <ol>
     content so the order survives with styles off. */
  .process-seq {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .seq-step {
    position: relative;
    display: grid;
    grid-template-columns: 2rem minmax(0, 1fr);
    gap: 0 14px;
    padding-bottom: 20px;
  }

  .seq-step:last-child {
    padding-bottom: 0;
  }

  /* The thread. Starts below the number and stops at the next one, so it reads
     as a connection rather than a border down the column. */
  .seq-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 0.42rem;
    top: 1.35em;
    bottom: 0.35em;
    width: 1px;
    background: var(--rule-strong);
  }

  .seq-num {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: var(--weight-label);
    line-height: 1.5;
    letter-spacing: var(--tracking-label);
    color: var(--accent);
  }

  .seq-label {
    display: block;
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
  }

  .seq-text {
    margin: 2px 0 0;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }

  /* One merged sentence per step: the label and its restatement were saying
     the same thing twice, so each step is a single line now. */
  .seq-line {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--ink);
  }

  @media (max-width: 860px) {
    .seq-step { grid-template-columns: 1.7rem minmax(0, 1fr); }
  }

  /* --- Tool pills --------------------------------------------------------
     First-fold answer to "what did she work with". The marks are the official
     single-colour brand glyphs on one 24px grid, inheriting currentColor —
     the page carries no chromatic value anywhere, so full-colour logos would
     be the loudest thing on it. */
  .tools-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 30px;
  }

  .tools-row > .eyebrow {
    margin-right: 2px;
    color: var(--muted);
  }

  .tool-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* Optical, not equal: a glyph reads lighter than a cap-height letter, so
       the left inset is tighter than the right. */
    padding: 7px 15px 7px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: -0.005em;
    white-space: nowrap;
    transition: border-color 0.16s ease, background-color 0.16s ease;
  }

  /* Real brand marks vary a lot in ink coverage — a filled triangle reads far
     heavier than a hairline knot at the same box size. 14px with a slight
     knock-back evens them out against the label. */
  .tool-pill svg {
    width: 20px;
    height: 20px;
    flex: none;
    fill: currentColor;
  }

  /* No special ground for the AI pills any more: the marks carry their own
     brand colour now, so tinting three chips just made them look like a
     different material. One neutral chip, colour does the signalling. */



  /* Bolder marks. Several of these logos are intrinsically fine-lined — the
     Claude starburst, the OpenAI knot, the Metabase dot matrix — and at pill
     size they thin out to texture. Stroking each glyph in its own colour and
     painting the stroke *under* the fill thickens it without distorting the
     shape. stroke and paint-order are inheritable, which is what lets them
     reach inside the <use> shadow tree. */
  .tool-pill svg {
    stroke: currentColor;
    stroke-width: 0.55;
    stroke-linejoin: round;
    paint-order: stroke fill;
  }

  /* Figma carries its own five fills and is already solid, so an ink-coloured
     stroke would outline it in the wrong colour. */
  .tool-pill[data-tool="figma"] svg {
    stroke: none;
  }

  /* Per-mark tuning: ink coverage differs enormously between these logos, so a
     single stroke width leaves the fine ones weak and the solid ones blobby.
     Vercel and VS Code are already solid shapes and get none. */
  .tool-pill[data-tool="claude"]    svg { stroke-width: 1.05; }
  .tool-pill[data-tool="metabase"]  svg { stroke-width: 0.8; }  /* dots merge past ~1 */
  .tool-pill[data-tool="gpt"]       svg { stroke-width: 0.85; }
  .tool-pill[data-tool="mixpanel"]  svg { stroke-width: 0.7; }
  .tool-pill[data-tool="storyblok"] svg { stroke-width: 0.5; }
  .tool-pill[data-tool="vercel"]    svg,
  .tool-pill[data-tool="github"]    svg,
  .tool-pill[data-tool="vscode"]    svg { stroke-width: 0; }


  /* Official brand colours. Figma is excluded on purpose: its mark is five
     colours and carries its own fills inside the symbol. Vercel and VS Code are
     pure black, so they follow the ink token and flip with the theme instead
     of disappearing on a dark ground. */
  [data-tool="claude"]    svg { color: #D97757; }
  [data-tool="gpt"]       svg { color: #412991; }
  [data-tool="storyblok"] svg { color: #09B3AF; }
  [data-tool="mixpanel"]  svg { color: #7856FF; }
  [data-tool="metabase"]  svg { color: #509EE3; }
  [data-tool="vscode"]    svg { color: #0A84C9; }
  /* GitHub, Vercel and VS Code are all near-black, so they follow the ink token
     and flip with the theme rather than vanishing on a dark ground. */
  [data-tool="vercel"]    svg,
  [data-tool="github"]    svg { color: var(--ink); }

  :root[data-theme="dark"] [data-tool="gpt"] svg { color: #9B8AF0; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) [data-tool="gpt"] svg { color: #9B8AF0; }
  }

  /* Name on hover only: collapsed the pill is an icon chip, so a row of tools
     reads as marks rather than a wall of text. The label stays in the DOM
     (clipped, not display:none) so screen readers still announce it, and the
     title attribute covers the pointer case. */
  .tool-pill {
    gap: 0;
  }

  .tool-pill .tool-name {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition:
      max-width 0.3s var(--ease, ease),
      margin-left 0.3s var(--ease, ease),
      opacity 0.18s var(--ease, ease);
  }

  .tool-pill:hover .tool-name,
  .tool-pill:focus-visible .tool-name {
    max-width: 10rem;
    margin-left: 0.42rem;
    opacity: 1;
  }

  /* No hover on touch, so the names stay out permanently. */
  @media (hover: none) {
    .tool-pill .tool-name {
      max-width: 10rem;
      margin-left: 0.42rem;
      opacity: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .tool-pill .tool-name { transition: none; }
  }

  @media (max-width: 720px) {
    .tools-row { gap: 8px; margin-top: 24px; }
    .tool-pill { padding: 5px 11px 5px 9px; font-size: var(--text-sm); }
  }

  /* --- Process + stack ---------------------------------------------------
     Stack chips on the left, how the work ran on the right. The chips are
     grouped by role (design / data / AI / build) rather than listed flat, so
     the section answers "what did she work with" at a glance and "how did she
     work" on a read. Rows are a two-column grid: label, then values. */
  .proc-block {
    display: grid;
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    gap: 0 var(--space-16, 64px);
    margin-top: 34px;
    padding-top: 22px;
    border-top: 2px solid var(--rule-strong);
  }

  .proc-rows {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }

  .proc-row {
    display: grid;
    grid-template-columns: 4.75rem minmax(0, 1fr);
    gap: 12px;
    align-items: baseline;
  }

  .proc-row > .eyebrow {
    color: var(--muted);
  }

  /* Values sit in mono at body weight: this is reference information, not
     prose, and it lines up with the label column. */
  .proc-value {
    font-family: var(--font-mono);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--ink);
  }

  /* The one tool that is the story rather than an implement gets the accent —
     used sparingly, and only where AI actually shipped. */
  .proc-value em {
    font-style: normal;
    color: var(--accent);
  }

  .proc-note {
    max-width: 46ch;
    color: var(--ink-2);
  }

  .proc-block > .process-seq {
    max-width: 48ch;
  }

  /* The project facts scan as one row; the process below uses the same full
     measure so neither block leaves an empty label column. */
  .role-block {
    margin-top: 28px;
    padding-block: 10px 14px;
  }

  .role-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: stretch;
    gap: clamp(16px, 2vw, 28px);
    margin: 0;
  }

  .role-item {
    position: relative;
    min-width: 0;
    min-height: 128px;
    padding: 26px 22px 20px;
    border: 1px solid var(--rule-strong);
    border-radius: 6px;
    background-color: var(--surface);
    background-image:
      radial-gradient(circle at 18% 24%, rgb(53 80 140 / 0.035) 0 0.7px, transparent 0.9px),
      radial-gradient(circle at 72% 68%, rgb(28 27 25 / 0.03) 0 0.65px, transparent 0.85px),
      repeating-linear-gradient(0deg, transparent 0 23px, rgb(53 80 140 / 0.025) 24px);
    background-size: 17px 19px, 23px 21px, 100% 24px;
    box-shadow:
      0 14px 24px rgb(28 27 25 / 0.08),
      0 3px 6px rgb(28 27 25 / 0.07),
      0 1px 0 rgb(255 255 255 / 0.5) inset;
    transform: rotate(-0.35deg);
  }

  .role-item + .role-item {
    padding-left: 22px;
    border-left: 1px solid var(--rule-strong);
  }

  .role-item:nth-child(2) {
    background-color: var(--tint);
    transform: translateY(3px) rotate(0.3deg);
  }

  .role-item:nth-child(3) {
    transform: rotate(-0.2deg);
  }

  .role-item::before {
    display: none;
  }

  .role-item::after {
    display: none;
  }

  .role-item dd {
    margin: 8px 0 0;
    font-family: var(--font-body);
    font-size: var(--text-md);
    line-height: 1.45;
    color: var(--ink);
  }

  .proc-block {
    display: block;
    margin-top: 28px;
    padding-top: 0;
    border-top: 0;
  }

  .proc-block > .eyebrow {
    margin-bottom: 16px;
  }

  .proc-block > .process-seq {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    max-width: none;
  }

  .proc-block > .process-seq--five {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  .proc-block .seq-step {
    display: block;
    min-width: 0;
    padding: 18px 24px 0 0;
    border-top: 2px solid var(--rule-strong);
  }

  .proc-block .seq-step:not(:first-child) {
    padding-left: 24px;
  }

  .proc-block .seq-step::before {
    content: "";
    position: absolute;
    left: 0;
    top: -5px;
    bottom: auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
  }

  .proc-block .seq-step:not(:first-child)::before {
    left: 24px;
  }

  .proc-block .seq-step > div {
    display: grid;
    grid-template-rows: minmax(3.2em, auto) auto;
    align-content: start;
  }

  .proc-block .seq-label {
    margin-top: 12px;
  }

  .proc-block .seq-text {
    margin-top: 8px;
    font-size: var(--text-sm);
    line-height: 1.55;
  }

  .proc-block .seq-line {
    margin-top: 12px;
    font-size: var(--text-sm);
    line-height: 1.55;
  }

  @media (max-width: 860px) {
    .proc-block {
      overflow-x: auto;
      padding-bottom: 12px;
      scrollbar-width: thin;
    }

    .proc-block > .process-seq {
      grid-template-columns: repeat(4, minmax(12rem, 1fr));
      min-width: 48rem;
    }

    .proc-block > .process-seq--five {
      grid-template-columns: repeat(5, minmax(12rem, 1fr));
      min-width: 60rem;
    }
  }

  @media (max-width: 640px) {
    /* Stop side-scrolling the process sequence on phones. The 860px block above
       turns .proc-block into a horizontal scroller with a 48rem (or 60rem) grid,
       which on a 390px screen means dragging through 768-960px to read four or
       five short text steps — and the first step sits flush against the viewport
       edge with no gutter, so it reads as broken rather than as scrollable.

       A numbered sequence stacks perfectly well: each step already carries its
       own top rule, so down the page it reads as the list it always was. */
    .proc-block {
      overflow-x: visible;
      padding-bottom: 0;
    }

    .proc-block > .process-seq,
    .proc-block > .process-seq--five {
      grid-template-columns: minmax(0, 1fr);
      min-width: 0;
    }

    /* The 24px left indent and the dot offset are the gutter BETWEEN columns in
       the horizontal layout. Stacked, they only push steps 02+ right of step 01,
       so every step starts on a different left edge. Both go back to 0. */
    .proc-block .seq-step {
      padding-right: 0;
    }

    .proc-block .seq-step:not(:first-child) {
      padding-left: 0;
    }

    .proc-block .seq-step:not(:first-child)::before {
      left: 0;
    }

    .proc-block .seq-step > div { grid-template-rows: auto auto; }

    /* The head is a single flex row holding a long snake_case identifier, and
       .code-block clips on the x axis rather than scrolling — so on a phone the
       name was simply cut off mid-word with no way to read the rest. Let the row
       wrap and let the identifier break inside itself. */
    .code-head {
      flex-wrap: wrap;
      padding: 10px 14px;
    }

    .code-head > * {
      min-width: 0;
      overflow-wrap: anywhere;
    }
    .role-grid {
      grid-template-columns: minmax(0, 1fr);
      gap: 20px;
    }

    .role-item,
    .role-item + .role-item {
      min-height: 0;
      padding: 42px 20px 20px;
      border: 1px solid var(--rule-strong);
      transform: none;
    }
  }

  /* --- Figures --------------------------------------------------------- */
  .figure {
    border: 1px solid var(--rule);
    border-radius: 6px;
    overflow: hidden;
    background: var(--surface);
  }
  .figure > img,
  .figure > video {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .figure--hero { aspect-ratio: 1280 / 590; }
  .figure--wide-590 { aspect-ratio: 1280 / 590; }
  .figure--wide-487 { aspect-ratio: 1280 / 487; }
  .figure--tall-988 { aspect-ratio: 700 / 988; }
  .figure--tall-984 { aspect-ratio: 660 / 984; }
  .figure--tall-801 { aspect-ratio: 660 / 801; }
  .figure--step { aspect-ratio: 610 / 490; }
  .figure--edge-crop > img {
    transform: scale(1.02);
  }

  /* The display serif was sized for two or three words, so the plain-language
     kicker rides below it in the body sans instead of inside the headline. */
  .feature-kicker {
    margin-top: 10px;
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0;
    color: var(--ink-2);
  }

  /* --- Section head ---------------------------------------------------- */
  .section-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 60px;
    padding-bottom: 28px;
    border-bottom: 1px solid var(--rule);
  }
  .section-head-left {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }
  .section--origin .section-head { border-bottom: 0; }

  /* --- Rule grid: flush-left first column, hairline dividers ----------- */
  .rule-grid {
    display: flex;
  }
  .rule-grid > * {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
  }
  .rule-grid > * + * {
    border-left: 1px solid var(--rule);
  }

  .glance-inner {
    display: flex;
    flex-direction: column;
    gap: 0;
  }
  /* Columns of copy and columns of figures read badly at media width. */
  .glance-grid,
  .flow-strip,
  .note-band {
    width: 100%;
    max-width: var(--col-wide);
    margin-inline: auto;
  }
  .glance-grid > * { padding-inline: 32px; }
  .glance-grid > :first-child { padding-left: 0; }

  .stats-inner {
    display: flex;
    flex-direction: column;
    gap: 40px;
  }
  .stats-grid > * {
    gap: 20px;
    padding: 28px;
  }
  .stats-grid > :first-child { padding-left: 0; }

  /* --- Core problem (inverted) ----------------------------------------- */
  .problem-inner {
    display: flex;
    flex-direction: column;
    gap: 24px;
  }
  .problem-inner .eyebrow { color: var(--band-ink-2); }
  .problem-statement {
    max-width: 1000px;
    color: var(--band-ink);
  }
  .problem-before-flow {
    margin-top: 40px;
    padding-top: 56px;
    border-top: 1px solid var(--band-rule);
  }
  .problem-before-flow .display { color: var(--band-ink); }
  .problem-before-flow .feature-head-note {
    flex: 0 1 420px;
    color: var(--band-ink-2);
  }
  .problem-before-flow .filmstrip { margin-top: 24px; }
  .problem-before-flow .film-caption,
  .problem-before-flow .figure-note { color: var(--band-ink-2); }
  .problem-before-flow .film-caption .micro { color: var(--band-ink-2); }
  .problem-before-flow .scroll-hint {
    border-color: var(--band-rule);
    color: var(--band-ink-2);
  }
  .flow-inner--problem {
    --flow-card-bg: #262522;
    --flow-card-ink: #fdfbf6;
    --flow-card-rule: #3e3b34;
    --flow-card-accent: #9fb0dc;
    --flow-card-negative: #d69b88;
    --flow-card-negative-bg: #34231f;
    width: 100%;
    margin-top: 24px;
    padding-block: 24px 20px;
    border-top: 1px solid var(--band-rule);
  }
  .flow-inner--problem .flow-head .display { color: var(--band-ink); }
  .flow-inner--problem .flow-head .section-lede,
  .flow-inner--problem .flow-drops .cap { color: var(--band-ink-2); }
  .flow-inner--problem .flow-track > .fg-step {
    min-height: 84px;
    justify-content: center;
    gap: 0;
    padding: 16px;
    border-color: var(--flow-card-rule);
    background: var(--flow-card-bg);
  }
  .flow-inner--problem .fg-drop-marker {
    top: 14px;
    right: 14px;
  }
  .flow-inner--problem .flow-track > .fg-step + .fg-step::before {
    background: var(--band-muted);
  }
  .flow-inner--problem .flow-track > .fg-step + .fg-step::after {
    border-color: var(--band-muted);
  }
  .flow-inner--problem .flow-track > .fg-step--fail {
    border-color: var(--flow-card-negative);
    background: var(--flow-card-negative-bg);
  }
  /* Map funnel evidence to the exact journey step above it. Column two stays
     open because merely seeing a card has no standalone metric in this data. */
  .flow-inner--problem .stats-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 12px;
    margin-top: 12px;
  }
  .flow-inner--problem .stats-grid > * {
    position: relative;
    padding: 22px 16px 0;
    border-left: 0;
  }
  .flow-inner--problem .stat-item--alert { grid-column: 1; }
  .flow-inner--problem .flow-metric-cue {
    grid-column: 2;
    align-self: start;
    display: block;
    margin: 10px 4px 0;
    padding: 0;
    border-left: 0 !important;
    font-family: var(--font-hand);
    font-size: 1.125rem;
    font-weight: 500;
    line-height: 1.12;
    color: var(--flow-card-accent);
    transform: rotate(-2deg);
  }
  /* The arrow is drawn now, see .cue-arrow. */
  .flow-inner--problem .stat-item--opened { grid-column: 3; }
  .flow-inner--problem .stat-item--applied { grid-column: 4; }
  .flow-inner--problem .stat-item--rejected { grid-column: 5; }
  .flow-inner--problem .stat-item--opened::before,
  .flow-inner--problem .stat-item--applied::before,
  .flow-inner--problem .stat-item--rejected::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 100%;
    width: 1px;
    height: 46px;
    background: var(--flow-card-negative);
  }
  .flow-inner--problem .stat-item--opened::after,
  .flow-inner--problem .stat-item--applied::after,
  .flow-inner--problem .stat-item--rejected::after {
    content: "";
    position: absolute;
    left: calc(50% - 4px);
    top: 1px;
    width: 7px;
    height: 7px;
    border-right: 1px solid var(--flow-card-negative);
    border-bottom: 1px solid var(--flow-card-negative);
    transform: rotate(45deg);
  }
  .flow-inner--problem .stat-item--alert {
    border-top: 1px dashed var(--band-rule);
  }
  /* CS2 follows the same evidence-to-step mapping as CS1, using four journey
     columns instead of five. The note floats between the first two metrics so
     it does not displace evidence from its matching step. */
  .case-study--cs2 .flow-inner--problem .stats-grid {
    position: relative;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin-top: 0;
  }
  .case-study--cs2 .flow-inner--problem {
    gap: 28px;
  }
  .case-study--cs2 .flow-inner--problem .stats-grid > .stat-item {
    padding-top: 34px;
  }
  .case-study--cs2 .flow-inner--problem .stats-grid > .stat-item > .display {
    display: block;
    text-align: center;
  }
  .case-study--cs2 .flow-inner--problem .stat-item--readiness { grid-column: 1; }
  .case-study--cs2 .flow-inner--problem .stat-item--builder { grid-column: 2; }
  .case-study--cs2 .flow-inner--problem .stat-item--built { grid-column: 3; }
  .case-study--cs2 .flow-inner--problem .stat-item--complete { grid-column: 4; }
  /* CS3 carries four baseline figures against the default five-column track */
  .case-study--cs3 .flow-inner--problem .stats-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin-top: 0;
  }
  .case-study--cs3 .flow-inner--problem {
    gap: 28px;
  }
  .case-study--cs3 .flow-inner--problem .stats-grid > .stat-item {
    padding-top: 34px;
  }

  .case-study--cs2 .flow-inner--problem .flow-head {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
  }
  .case-study--cs2 .flow-inner--problem .flow-metric-cue--head {
    width: auto;
    max-width: none;
    margin: 0 12px 0 0;
    transform: rotate(-2deg);
    white-space: nowrap;
  }
  .case-study--cs2 .flow-inner--problem .flow-metric-cue--head::after {
    /* The note sits directly above the last flow card, so the arrow points
       straight down into the flow. The inherited "↘" aimed at the margin. */
    content: "↓";
    display: inline-block;
    margin: 0 0 0 10px;
    padding: 0;
    text-align: left;
  }
  .case-study--cs2 .flow-inner--problem .stat-item--builder::before,
  .case-study--cs2 .flow-inner--problem .stat-item--built::before,
  .case-study--cs2 .flow-inner--problem .stat-item--complete::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 100%;
    width: 1px;
    height: 27px;
    background: var(--flow-card-negative);
  }
  .case-study--cs2 .flow-inner--problem .stat-item--builder::after,
  .case-study--cs2 .flow-inner--problem .stat-item--built::after,
  .case-study--cs2 .flow-inner--problem .stat-item--complete::after {
    content: "";
    position: absolute;
    left: calc(50% - 4px);
    top: 1px;
    width: 7px;
    height: 7px;
    border-right: 1px solid var(--flow-card-negative);
    border-bottom: 1px solid var(--flow-card-negative);
    transform: rotate(45deg);
  }
  .case-study--cs2 .flow-inner--problem .stat-item--readiness {
    border-top: 1px dashed var(--band-rule);
  }
  .case-study--cs2 .problem-before-flow .sub-problems--cs2 {
    margin-top: 36px;
  }
  .sub-problems { margin-top: 8px; }
  .sub-problem {
    display: flex;
    gap: 48px;
    padding-block: 32px;
    border-top: 1px solid var(--band-rule);
  }
  .sub-problem-index {
    flex: 0 0 48px;
    color: var(--band-muted);
  }
  .sub-problem-title {
    flex: 0 1 380px;
    min-width: 0;
    color: var(--band-ink);
  }
  .sub-problem-body {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-md);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--band-ink-2);
  }

  /* --- Core problem, Format A: image-left / text-right cards ------------
     Hairline + 2px radius, matching the dark band's rule-based idiom
     rather than introducing a filled card surface. */
  .problem-cards {
    display: flex;
    flex-direction: column;
    gap: 32px;
    margin-top: 0;
  }
  .problem-card {
    display: grid;
    grid-template-columns: minmax(0, 58%) minmax(0, 1fr);
    align-items: center;
    gap: clamp(24px, 3.5vw, 48px);
    padding: 24px;
    border: 1px solid var(--band-rule);
    border-radius: 6px;
  }
  .problem-card-media > img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 6px;
  }
  .problem-card-text {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
  }
  .problem-card-text .sub-problem-index { flex: none; }
  .problem-card-points {
    margin: 0;
    padding-left: 1.3em;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: var(--text-md);
    line-height: 1.6;
    letter-spacing: 0;
    color: var(--band-ink);
  }

  /* --- Core problem, Format B: prose block ----------------------------- */
  .problem-prose {
    margin-top: 32px;
    padding-top: 32px;
    border-top: 1px solid var(--band-rule);
    max-width: 72ch;
  }
  /* Body copy on the dark band sits on --band-ink rather than --band-ink-2:
     the muted tone is fine for a caption but reads washed out over a block
     this long. Emphasis is carried by weight, since the colour is now shared. */
  .problem-prose-body {
    margin: 0;
    font-size: var(--text-md);
    line-height: 1.7;
    letter-spacing: 0;
    color: var(--band-ink);
  }
  .problem-prose-body strong {
    font-weight: 600;
    color: var(--band-ink);
  }
  .problem-prose-list {
    margin: 22px 0;
    padding-left: 1.4em;
    display: flex;
    flex-direction: column;
    gap: 12px;
    font-size: var(--text-md);
    line-height: 1.7;
    color: var(--band-ink);
  }

  /* --- Current flow ---------------------------------------------------- */
  .flow-inner {
    display: flex;
    flex-direction: column;
    gap: 56px;
  }
  .flow-head {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }
  .flow-strip {
    display: flex;
    gap: 10px;
  }
  .flow-step {
    flex: 1 1 0;
    min-width: 0;
    padding: 22px 20px 26px;
    background: var(--surface);
    border-top: 3px solid var(--rule);
    border-radius: 6px;
  }
  .flow-step-label { margin-top: 44px; }
  .flow-step-snippet { margin-top: 12px; }
  .flow-step.is-failure {
    background: var(--tint);
    border-top-color: var(--ink);
  }
  .flow-step.is-failure .flow-step-num { color: var(--ink); }

  /* --- Principle (inverted panel) -------------------------------------- */
  /* Light panel, accent rule: the dark treatment is spent on the core
     problem and the impact band, and a third inverted block in between was
     one too many. */
  .principle {
    display: flex;
    align-items: flex-start;
    gap: 56px;
    padding: 52px 52px 52px 48px;
    background: var(--surface);
    border-left: 4px solid var(--accent);
  }
  .principle-tag {
    flex: 0 0 120px;
  }
  .principle-body {
    flex: 1 1 0;
    min-width: 0;
    color: var(--ink);
  }

  .principle-body--sans {
    max-width: 54ch;
    font-family: var(--font-body);
    font-size: var(--d26);
    font-weight: 500;
    line-height: 1.45;
    letter-spacing: 0;
  }

  /* --- Feature block --------------------------------------------------- */
  .feature-head {
    padding-bottom: 40px;
    border-bottom: 0;
  }
  .feature-head-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 60px;
  }
  .feature-head-left {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .feature-intro {
    flex: 0 1 360px;
    min-width: 0;
  }

  .user-story {
    display: flex;
    gap: 40px;
    padding-block: 36px;
    border-bottom: 0;
  }
  .user-story-label { flex: 0 0 110px; }
  .user-story-body {
    flex: 1 1 0;
    min-width: 0;
    max-width: 820px;
    font-family: var(--font-body);
    font-size: clamp(1.125rem, 1.45vw, 1.375rem);
    font-style: normal;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: -0.01em;
    quotes: "“" "”";
    hanging-punctuation: first last;
  }
  .user-story-body::before { content: open-quote; }
  .user-story-body::after { content: close-quote; }

  .card-head {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding-bottom: 22px;
  }
  .card-head-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
  }

  .problem-block { padding-top: 72px; }

  /* Stacked, not two-up. This was a 28% copy column beside the figure, which
     never balanced: four numbered points in a ~250px column set about 25
     characters a line and ran roughly 400px taller than the screenshot next to
     them, so the section ended on a tall ragged column of text against dead space
     under the image — exactly the thing the split was meant to avoid.

     Full width in both directions instead: the head and caption lead, the four
     points run as a row beneath them, and the figure takes the full measure below.
     The points are the argument, so they get the horizontal room; the screenshot
     is wide UI and reads better large than boxed into 70% of the column. */
  .problem-block--split > .problem-split {
    display: flex;
    flex-direction: column;
    gap: 40px;
  }
  .problem-block--split .problem-split-copy,
  .problem-block--split .problem-split-figure {
    min-width: 0;
  }

  /* Numbered failings, four across — each one a short column under its own rule
     rather than a boxed card, which is how every other multi-column row on these
     pages is set (see .impact-grid).

     The rule sits on each item, not on the container: at the wrapped breakpoints
     below a container border would leave the second row of points hanging with no
     rule above it. */
  .problem-points {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 30px;
    padding-top: 30px;
    margin: 0;
    list-style: none;
  }
  .problem-points > li {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-top: 18px;
    border-top: 1px solid var(--rule);
  }
  .problem-points .problem-point-title {
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .problem-points .problem-point-body {
    font-size: var(--text-sm);
    line-height: 1.55;
    letter-spacing: 0;
    color: var(--muted);
  }

  /* The wrap caps at 62rem, so four columns hold to about 1000px. Below that each
     point drops under ~200px and the bodies set too narrow to read; two up, then
     one. */
  @media (max-width: 1000px) {
    .problem-points {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
  @media (max-width: 620px) {
    .problem-points {
      grid-template-columns: minmax(0, 1fr);
      gap: 22px;
    }
  }

  /* This is the only .card-head-row anywhere that carries a title as well as a
     pill — every other one holds a pill alone, where space-between does nothing.
     It read fine while the head was boxed into a 28% column; at full width the
     same rule strands "Replaced" against the right edge, a screen away from the
     title whose status it reports. Group them instead. */
  .problem-block--split .card-head-row {
    justify-content: flex-start;
    gap: 18px;
  }

  /* Title and status on one row across the whole column: the title keeps its
     own measure on the left, the pill sits at the column's right edge. The row
     lives outside .final-design-summary, whose 697px cap would otherwise pull
     the pill in short of the edge. */
  .final-design-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24px;
    width: 100%;
    margin-bottom: 20px;
  }
  .final-design-head > .pill { flex: none; }
  @media (max-width: 720px) {
    /* Column is too narrow to hold both across; status reads first instead. */
    .final-design-head {
      flex-direction: column-reverse;
      align-items: flex-start;
      gap: 12px;
    }
  }

  /* The copy column is already narrow, so the caption needs no hanging marker. */
  .problem-block--split .problem-split-copy > .caption-row {
    padding-top: 18px;
  }

  /* The marker is a hanging cue, not a column: it sits right beside the copy so
     the caption's left edge lines up with the card head above it. */
  .caption-row {
    display: flex;
    gap: 14px;
    padding-top: 26px;
  }

  /* --- Iteration ladder ---------------------------------------------------- */
  /* The rejected-version story used to be gated behind clicking a tab inside the
     embed, so most readers never saw it. This states it in the scroll flow:
     colour alone says which attempt failed before a word is read. */
  .iteration-head {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 24px;
    margin-top: 64px;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--rule-strong);
  }
  .ladder {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: stretch;
    gap: 12px;
    margin-top: 0;
  }
  /* The ladder now opens with a line naming what it is, so the 72px that
     separated it from the shipped design moves up to the heading and the
     tabs sit close under their own title. */
  .ladder-lede {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 72px;
  }
  .ladder-lede > .display {
    margin: 0;
    max-width: none;
  }
  .proto-embed > .ladder-lede,
  .wrap > .ladder-lede + .proto-embed { margin-top: 26px; }
  .ladder-with-cue {
    display: grid;
    grid-template-columns: 9rem minmax(0, 1fr);
    gap: 24px;
    align-items: center;
    margin-top: 26px;
  }
  .iteration-cue {
    margin: 0;
    font-family: var(--font-hand);
    font-size: 1.25rem;
    font-style: normal;
    font-weight: 500;
    line-height: 1.15;
    color: var(--accent);
    transform: rotate(-2deg);
    transform-origin: left center;
  }
  /* The arrow is drawn now, see .cue-arrow below. */
  .ladder--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The rungs are buttons now: each retunes the prototype below it. Reset the UA
     button styling so they still read as cards rather than controls. */
  .ladder > .rung {
    appearance: none;
    -webkit-appearance: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 160ms ease, box-shadow 160ms ease, opacity 160ms ease, transform 160ms ease;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 15px 18px;
    border: 1px solid var(--rule-strong);
    border-radius: 6px;
    background: var(--surface);
  }
  .ladder > .rung + .rung { border-left: 1px solid var(--rule-strong); }
  .rung-index {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    color: var(--muted);
  }
  .rung-label {
    display: flex;
    align-items: baseline;
    gap: 8px;
  }
  .rung-verdict {
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
  }
  .rung-title {
    max-width: 27ch;
    font-size: var(--text-base);
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .rung-body {
    font-size: var(--text-sm);
    line-height: 1.55;
    letter-spacing: 0;
    color: var(--muted);
  }
  .rung--rejected {
    --rung-state: var(--negative);
  }
  .rung--rejected .rung-verdict { color: var(--negative); }
  .rung--shipped {
    --rung-state: var(--positive);
  }
  .rung--explored { --rung-state: var(--muted); }
  .rung--shipped .rung-verdict { color: var(--positive); }
  .rung--explored .rung-verdict { color: var(--muted); }

  /* Unselected rungs step back so the active one reads as the current frame.
     Colour still says which attempt failed, in every state. */
  .ladder > .rung:not(.is-active) { opacity: 0.72; }
  .ladder > .rung:hover { opacity: 1; transform: translateY(-1px); }
  .ladder > .rung.is-active {
    opacity: 1;
    border-color: var(--rung-state);
    box-shadow: inset 0 -3px 0 var(--rung-state);
  }
  .ladder > .rung:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

  /* The panel the tabs drive. A min-height keeps the prototype below it from
     jumping as the copy length changes between versions. */
  .version-note {
    display: grid;
    grid-template-columns: 116px minmax(0, 1fr);
    gap: 24px;
    align-items: start;
    margin-top: 12px;
    padding: 18px 0;
    min-height: 96px;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
  }
  .version-note-label { padding-top: 4px; color: var(--accent); }
  .version-note-title { margin-bottom: 6px; color: var(--ink); }
  .version-note-body { max-width: 68ch; }
  .ladder > .rung.is-active.rung--rejected { border-bottom-color: var(--negative); }
  .ladder > .rung.is-active.rung--shipped { border-bottom-color: var(--positive); }

  @media (max-width: 720px) {
    .ladder-with-cue {
      grid-template-columns: minmax(0, 1fr);
      gap: 14px;
      margin-top: 44px;
    }
    .iteration-cue {
      max-width: none;
      transform: none;
    }
    .iteration-cue::after {
      display: inline;
      margin: 0 0 0 8px;
      font-size: 1rem;
    }
    .version-note { grid-template-columns: minmax(0, 1fr); gap: 10px; min-height: 0; }
  }

  /* The lead sits above the ladder, so it needs less than the section gap. */
  .note--lead { padding-top: 72px; }

  /* Why-blocks follow the constraints format: one serif heading with the label
     folded into the sentence, then the reasoning as peer cards on the same
     subtle accent wash. Was a hard-coded dark panel (#1c1b19 on a #171613
     page), which meant the panel all but vanished in dark mode and none of its
     colours tracked the theme. Every value here is a token. */
  .why-block {
    margin-top: 96px;
  }
  .why-title {
    margin: 0;
    max-width: 34ch;
    color: var(--ink);
  }
  .why-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: clamp(14px, 1.6vw, 22px);
    margin-top: 30px;
  }
  .why-card {
    display: flex;
    flex-direction: column;
    /* Same geometry as .constraint — these two blocks read as one format, so
       the radius and padding are shared rather than nearly-shared. */
    gap: 14px;
    padding: 28px 26px 30px;
    border-radius: 6px;
    background: var(--surface);
    background: color-mix(in oklab, var(--accent) 5%, var(--bg));
  }
  .why-card-label {
    color: var(--muted);
  }
  .why-card-body {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--ink-2);
  }
  .ant-rationale {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    gap: 28px;
    align-items: baseline;
    margin: 30px 0 0 116px;
    padding: 22px 0;
    border-top: 1px solid color-mix(in srgb, #8cb5ff 48%, #3e3b34);
    border-bottom: 1px solid #3e3b34;
  }
  .ant-rationale > .eyebrow {
    color: #8cb5ff;
  }
  .ant-rationale > p {
    max-width: 64ch;
  }
  @media (max-width: 720px) {
    .why-block { margin-top: 64px; }
    .why-title { max-width: none; }
    .why-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; margin-top: 22px; }
    .why-card { padding: 22px 20px 24px; }
    .ant-rationale {
      grid-template-columns: minmax(0, 1fr);
      gap: 8px;
      margin-left: 0;
      padding: 18px 0;
    }
  }

  /* --- Interactive wireframe embed ----------------------------------------- */

  .proto-embed {
    margin-top: 24px;
  }

  .proto-embed-frame {
    background: var(--surface);
    border-radius: 6px;
    overflow: clip;
    isolation: isolate;
  }

  .proto-embed iframe {
    display: block;
    width: 100%;
    height: 700px;
    border: 0;
    background: #fff;
  }

  .proto-embed .caption-row {
    padding-top: 18px;
  }

  .proto-open {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    padding-bottom: 2px;
    color: var(--ink-2);
    border-bottom: 1px solid currentColor;
    text-decoration: none;
    transition: color 160ms ease, gap 160ms ease;
  }

  /* The wireframe embed only works at desktop width, so phones get a still of
     the shipped state instead. Interactive on desktop, static on mobile. */
  .proto-static { display: none; }
  .proto-static-note {
    margin-top: 10px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }

  @media (max-width: 720px) {
    .proto-embed-frame { display: none; }
    /* With no live frame, the version tabs would change nothing visible and
       the caption's "pick a version above" would be false, so both go. The
       version note stays: it carries the rationale for the shipped state the
       still shows. */
    .proto-embed .caption-row,
    .case-study--cs1 .ladder-with-cue { display: none; }
    .proto-static {
      display: block;
      margin: 0;
    }
    .proto-static > img {
      display: block;
      width: 100%;
      height: auto;
      border: 1px solid var(--rule);
      border-radius: 6px;
    }
  }

  .proto-open:hover { color: var(--ink); gap: 12px; }
  .proto-open:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

  @media (max-width: 760px) {
    .proto-embed { margin-top: 18px; }
    .proto-embed iframe { height: 540px; }
  }

  .caption-marker {
    flex: 0 0 auto;
  }
  .caption-row .cap {
    flex: 1 1 0;
    min-width: 0;
  }

  /* Stacked, not side by side. At this measure a copy column beside a
     screenshot squeezed to ~130px and set one word per line, so the two now
     run one below the other: the reasoning, then the evidence at full width. */
  .shipped-block {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 36px;
    padding-top: 96px;
    border-top: 1px solid var(--rule);
  }
  .shipped-block--flush {
    padding-top: 72px;
    border-top: 0;
  }
  .shipped-copy {
    flex: none;
    min-width: 0;
    /* Copy leads regardless of source order — some blocks list the figure
       first for a side-by-side reading that no longer applies. */
    order: -1;
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding-top: 0;
  }
  .final-design-summary {
    display: flex;
    flex-direction: column;
    gap: 16px;
    max-width: 65ch;
  }
  .final-design-summary .prose {
    max-width: 62ch;
  }
  .detail-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 24px;
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--ink-2);
    font-size: var(--text-base);
    line-height: 1.5;
  }
  .detail-list li {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .detail-list li::before {
    content: "";
    width: 5px;
    height: 5px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--accent);
  }
  .final-design-title--sans {
    max-width: 38ch;
    font-family: var(--font-body);
    font-size: var(--d34);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .final-design-title--serif-line {
    font-family: var(--font-display);
    font-weight: 400;
  }
  .shipped-copy--tight {
    gap: 10px;
  }
  .shipped-copy--tight > .card-head {
    padding-bottom: 0;
  }
  .shipped-figure {
    flex: none;
    min-width: 0;
    padding-top: 0;
  }
  .shipped-figure--700,
  .shipped-figure--660 { max-width: none; }

  .step-row {
    display: flex;
    align-items: flex-start;
    gap: 56px;
    padding-top: 96px;
    border-top: 1px solid var(--rule);
  }
  .step-card {
    flex: 1 1 0;
    min-width: 0;
    padding-top: 40px;
  }
  .step-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 0 22px;
  }

  /* --- Pills — solid / outline / soft, no hue -------------------------- */
  .pill {
    flex: none;
    display: inline-flex;
    align-items: center;
    padding: 5px 11px;
    border: 1px solid var(--ink);
    border-radius: 20px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: var(--weight-label);
    line-height: 1.5;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    white-space: nowrap;
  }
  .pill--solid {
    background: var(--ink);
    color: var(--bg);
  }
  .pill--outline {
    background: var(--bg);
    color: var(--ink);
  }
  .pill--soft {
    background: var(--bg);
    border-color: var(--rule);
    color: var(--muted);
  }

  .tag-states {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 28px;
  }
  .tag-row {
    display: contents;
  }

  /* --- A/B results ----------------------------------------------------- */
  .outcome-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
  }
  /* Two up. The container was unstyled, so its two rows stacked full width and
     each figure sat alone on a line with the rest of the measure empty beside it.
     Set like .outcome-grid above — same rules, same dividers — just at two
     columns instead of three, because it holds exactly two results. */
  .ab-results {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
  }
  .ab-results > * + * { border-left: 1px solid var(--rule); }

  /* Below this the label and the figure-plus-caption cannot share ~230px without
     the caption breaking to three lines under a number. One column. */
  @media (max-width: 620px) {
    .ab-results {
      grid-template-columns: minmax(0, 1fr);
    }
    .ab-results > * + * {
      border-left: 0;
      border-top: 1px solid var(--rule);
    }
  }

  .ab-row {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 20px;
  }
  .ab-row > .micro,
  .outcome-grid .net > .micro {
    font-size: 0.75rem;
  }
  .outcome-grid > * + * { border-left: 1px solid var(--rule); }
  .ab-values {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
  }
  .ab-from {
    color: var(--muted);
  }

  .net {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 20px;
    background: var(--surface);
    border-left: 3px solid var(--ink);
    border-radius: 6px;
  }
  .outcome-grid .net {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    padding: 20px;
    border-left-width: 1px;
    border-radius: 0;
  }
  .outcome-grid .net-label { flex: none; }
  @media (max-width: 720px) {
    .outcome-grid { grid-template-columns: minmax(0, 1fr); }
    .outcome-grid > * + * {
      border-left: 0;
      border-top: 1px solid var(--rule);
    }
  }
  .net-label {
    flex: 1 1 0;
    min-width: 0;
  }

  /* The relative lift sits with the pair it describes rather than stranded
     under the whole row, matching the caption under the total. */
  .ab-note {
    margin: 10px 0 0;
    max-width: 34ch;
  }

  /* --- Fit scores ------------------------------------------------------ */
  .fit-scores { padding-top: 6px; }
  .fit-row {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-block: 18px;
    border-top: 1px solid var(--rule);
  }
  .fit-row-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
  }
  .fit-name {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    color: var(--ink);
  }
  .fit-track {
    height: 6px;
    background: var(--tint);
    border-radius: 6px;
    overflow: hidden;
  }
  .fit-bar {
    width: var(--fit, 0%);
    height: 6px;
    border-radius: 6px;
    background: var(--ink);
  }
  .fit-bar--muted { background: var(--muted); }
  .fit-val--muted { color: var(--muted); }

  /* --- Note band ------------------------------------------------------- */
  .note { padding-top: 96px; }
  .note-band {
    display: flex;
    flex-direction: column;
    gap: 44px;
    padding: 48px;
    background: var(--surface);
    border-radius: 6px;
  }
  .note-grid > * {
    gap: 12px;
    padding-inline: 28px;
  }
  .note-grid .micro {
    font-size: 0.75rem;
    line-height: 1.4;
  }
  .note-grid .h-sub {
    font-size: 1.125rem;
    line-height: 1.3;
  }
  .note-grid .base {
    max-width: 34ch;
    font-size: 1rem;
    line-height: 1.55;
  }
  .note-grid > :first-child { padding-left: 0; }
  .note-grid > * + * { border-left-color: var(--rule-strong); }

  /* --- Impact (inverted band) ------------------------------------------ */
  .impact-band {
    padding: 64px 56px;
    background: var(--band-bg);
    border-radius: 6px;
  }
  .impact-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 60px;
    padding-bottom: 52px;
  }
  .impact-head-left {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .impact-head .eyebrow { color: var(--band-muted); }
  .impact-band .display { color: var(--band-ink); }

  /* Reads as a deck under "What moved": states how the figures were measured
     before the figures themselves. --band-ink, not --band-ink-2, matching the
     other body copy on this band. */
  .impact-method {
    margin: 6px 0 0;
    max-width: 74ch;
    font-size: var(--text-md);
    line-height: 1.7;
    letter-spacing: 0;
    color: var(--band-ink);
  }

  .impact-grid {
    display: flex;
    border-top: 1px solid var(--band-rule);
  }
  .impact-grid > * {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 36px 26px 0;
  }
  .impact-grid > :first-child { padding-left: 0; }
  .impact-grid > * + * { border-left: 1px solid var(--band-rule); }

  /* Lead figure as a full-width strip above the row. The headline number was one
     of four equal cells, which set the biggest figure on the page in a quarter of
     the band and wrapped its label to six lines. On its own strip the number sits
     beside its label instead of above it, and the three supporting figures below
     divide the width evenly between them.

     Rules on the strip and the row both, so the band still reads as banded even
     though the strip is not part of the grid. */
  .impact-lead {
    display: flex;
    align-items: baseline;
    gap: 40px;
    border-top: 1px solid var(--band-rule);
    padding: 36px 0 44px;
  }
  .impact-lead .display {
    flex: none;
  }
  .impact-lead .impact-label {
    max-width: 54ch;
    margin: 0;
  }

  /* Baseline alignment needs a shared line to sit on; once the label wraps under
     the figure there is none, so stack and drop back to the row's own spacing. */
  @media (max-width: 720px) {
    .impact-lead {
      flex-direction: column;
      align-items: stretch;
      gap: 18px;
      padding-bottom: 36px;
    }
  }
  .impact-label {
    font-size: var(--text-base);
    line-height: 1.5;
    letter-spacing: 0;
    color: var(--band-ink-2);
  }

  /* The relative figure beside the absolute one. The headline claims a 15%
     lift and Impact reported only +65, so a reader had to do the division
     themselves. Set smaller and in the quieter ink so the absolute number
     still leads and the percentage reads as its restatement, not a second
     result. */
  .impact-figure-rel {
    font-size: 0.44em;
    letter-spacing: 0;
    color: var(--band-muted);
    white-space: nowrap;
  }

  .impact-survey {
    display: grid;
    grid-template-columns: minmax(220px, 0.34fr) minmax(0, 1fr);
    gap: clamp(32px, 5vw, 72px);
    align-items: start;
    margin-top: 56px;
    padding-top: 48px;
    border-top: 1px solid var(--band-rule);
  }
  .impact-survey-head {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .impact-survey-head .eyebrow { color: var(--accent-soft); }
  .impact-survey-head h3 {
    max-width: 15ch;
    font-size: var(--text-lg);
    font-weight: 600;
    letter-spacing: var(--tracking-tight);
    line-height: 1.15;
    color: var(--band-ink);
  }
  .impact-survey-head p {
    max-width: 28ch;
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--band-ink-2);
  }
  .impact-survey-figure {
    overflow: hidden;
    background: var(--surface);
    border-radius: 6px;
  }
  .impact-survey-figure img {
    display: block;
    width: 100%;
    height: auto;
  }

  .impact-notes {
    display: flex;
    gap: 56px;
    padding-top: 56px;
  }
  .impact-notes > p {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-sm);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--band-muted);
  }

  /* --- Takeaways ------------------------------------------------------- */
  .takeaways-head {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding-bottom: 44px;
  }
  .reflection-list {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-top: 1px solid var(--rule);
  }
  .reflection {
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding: 32px 28px 0;
    border-top: 0;
  }
  .reflection + .reflection { border-left: 1px solid var(--rule); }
  .reflection:first-child { padding-left: 0; }
  .reflection:last-child { padding-right: 0; }
  .reflection-num { flex: none; }
  .reflection-body {
    flex: 1 1 0;
    min-width: 0;
    max-width: 32ch;
  }

  /* --- Footer ---------------------------------------------------------- */
  .footer-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 36px 0 64px;
    border-top: 2px solid var(--ink);
  }
  .footer-next { color: var(--ink); }

  /* A slim identity strip under the nav row. Case studies get linked to
     directly, so each one has to end with a way to get in touch rather than
     relying on the reader going back to the homepage. */
  .case-foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px 32px;
    flex-wrap: wrap;
    padding: 20px 0 56px;
  }
  .case-foot-note { color: var(--muted); }
  .case-foot-links {
    display: flex;
    gap: 24px;
    flex-wrap: wrap;
  }
  .case-foot-links a {
    color: var(--ink-2);
    text-decoration: none;
    padding-bottom: 2px;
    border-bottom: 1px solid transparent;
    transition: color 120ms ease, border-color 120ms ease;
  }
  .case-foot-links a:hover,
  .case-foot-links a:focus-visible {
    color: var(--ink);
    border-bottom-color: var(--ink);
  }

  /* The nav row owned the whole footer's bottom space; the strip takes it now. */
  .footer-row { padding-bottom: 24px; }

  @media (max-width: 720px) {
    .case-foot { padding: 16px 0 40px; }
    .case-foot-links { gap: 18px; }
  }
}

/* ==========================================================================
   V1b · Extensions — components the other case studies need.
   Same tokens, same ramp, still no hue. Nothing here overrides the
   Careers Hub / Application page; it only adds.
   ========================================================================== */

@layer components {
  /* --- Figure variants ------------------------------------------------- */
  /* Screenshots whose aspect ratio isn't pinned in the comp. */
  /* Screenshots sit on the page, not in a box: no fill, no frame. Images that
     carry their own transparency (the research captures) then read against the
     page colour instead of a white rectangle. */
  .figure--natural {
    background: transparent;
    border: 0;
    border-radius: 6px;
  }
  .figure--natural > img,
  .figure--natural > video {
    height: auto;
    object-fit: contain;
  }

  /* AI entries only. Those two pages are almost entirely light product UI on a
     near-white page, so a capture's outer edge has nothing to land against and
     the screen bleeds into the ground. A narrow mat of the warm gradient sits
     behind each one purely to give that edge a boundary.

     Deliberately thin: this is a contrast device, not a frame. A wide mat would
     turn every screenshot into a matted print and start competing with the
     capture for attention, which is the opposite of the point. The clamp tops
     out at 14px, so it stays a band at every width. */
  .case-study--ai .figure--natural {
    padding: clamp(7px, 0.8vw, 14px);
    border-radius: 6px;
    background-color: var(--surface);
    background-image: url("../assets/shared/ai-figure-backdrop.webp");
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
  }

  .case-study--ai .figure--natural > img,
  .case-study--ai .figure--natural > video {
    border-radius: 6px;
    /* drop-shadow, not box-shadow: object-fit: contain leaves the element box
       the full padded area, so a box-shadow would draw around the mat rather
       than around the capture. A filter follows what is actually painted. */
    filter:
      drop-shadow(0 1px 2px rgb(24 16 28 / 0.18))
      drop-shadow(0 10px 22px rgb(24 16 28 / 0.16));
  }

  @media (max-width: 720px) {
    .case-study--ai .figure--natural { padding: 6px; }
  }

  /* Full-page captures: scroll the page inside its own frame. */
  .figure--scroll {
    max-height: 760px;
    overflow-y: auto;
    background: var(--bg);
    overscroll-behavior: contain;
  }
  .figure--scroll > img {
    height: auto;
    object-fit: contain;
  }

  .figure-note {
    display: block;
    padding-top: 16px;
    font-size: var(--text-sm);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--muted);
  }
  .figure-note strong {
    font-weight: 500;
    color: var(--ink);
  }
  .scroll-hint {
    display: inline-block;
    margin-right: 8px;
    padding: 2px 8px;
    border: 1px solid var(--rule);
    border-radius: 20px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }

  /* --- Two-up pair ----------------------------------------------------- */
  .pair {
    display: flex;
    align-items: flex-start;
    gap: 44px;
  }
  .pair > * {
    flex: 1 1 0;
    min-width: 0;
  }
  .pair-label {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-bottom: 14px;
  }
  .pair-role {
    padding: 4px 10px;
    background: var(--ink);
    border-radius: 20px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--bg);
  }
  .pair-role--outline {
    background: var(--bg);
    border: 1px solid var(--ink);
    color: var(--ink);
  }
  .pair-title {
    font-size: var(--text-base);
    font-weight: 500;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }

  /* --- Filmstrip: a numbered run of steps, scrolled sideways ----------- */
  .filmstrip {
    display: flex;
    gap: 20px;
    padding-bottom: 14px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
  }
  .filmstrip > .film-step {
    flex: 0 0 420px;
    min-width: 0;
    scroll-snap-align: start;
  }
  #feature-02 .filmstrip > .film-step {
    flex-basis: min(620px, 78vw);
  }
  .film-step > .figure {
    background: transparent;
    border: 0;
    border-radius: 6px;
  }
  .film-step > .figure > img {
    height: auto;
    object-fit: contain;
  }
  .film-caption {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding-top: 14px;
    font-size: var(--text-sm);
    line-height: 1.55;
    letter-spacing: 0;
    color: var(--muted);
  }

  /* --- Stacked block: full-width problem / shipped, no side column ----- */
  .stack-block {
    padding-top: 72px;
    border-top: 1px solid var(--rule);
  }
  .stack-block--flush {
    padding-top: 72px;
    border-top: 0;
  }
  .stack-block > * + * { margin-top: 22px; }
  .stack-block > .card-head + * { margin-top: 0; }

  /* CS3's shipped curriculum capture sits on the sky the live section carries,
     as a narrow mat rather than a card ground: the photo gives the screenshot's
     white edge a boundary and nothing else. Same idiom as the AI figures, and
     deliberately thin so it stays a band, not a matted print. Kept off the
     card's copy so the caption and A/B numbers read on the page colour. */
  .case-study--cs3 .stack-block--sky > .figure--natural {
    padding: clamp(8px, 0.9vw, 16px);
    border-radius: 6px;
    background-color: var(--surface);
    background-image: url("../assets/academy/11-curriculum-sky.webp");
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
  }

  /* --- Before → after deltas ------------------------------------------- */
  .delta-grid > * {
    gap: 18px;
    padding: 28px;
  }
  .delta-grid > :first-child { padding-left: 0; }
  .delta-values {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
  }
  .delta-from {
    color: var(--muted);
    text-decoration: line-through;
  }
  .delta-name {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }

  /* --- Labelled notes inside the impact band --------------------------- */
  .impact-notes > .impact-note {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .impact-note > .micro { color: var(--band-ink); }
  .impact-note > p {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--band-muted);
  }

  /* --- Framing note (the AI entries' standing caveat) ------------------ */
  .framing {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 0;
    margin-top: 20px;
  }
  .framing > p {
    font-size: var(--text-base);
    line-height: 1.6;
    letter-spacing: 0;
    color: var(--ink-2);
  }
  /* The iterations tag is a label, not a full-width band. */
  .framing > .pill { align-self: flex-start; }
  .framing > .h-sub { color: var(--ink); }
  .framing > .framing-sub {
    font-size: var(--text-base);
    line-height: 1.6;
    letter-spacing: 0;
    color: var(--ink);
  }

  .pilot-proof {
    display: grid;
    gap: 18px;
    margin-top: 12px;
    padding-top: 20px;
    border-top: 1px solid var(--rule);
  }
  .pilot-cohort {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: baseline;
    gap: 10px;
  }
  .pilot-cohort .micro { color: var(--muted); }
  .pilot-cohort strong,
  .pilot-result-head strong {
    font-family: var(--font-display);
    font-weight: 500;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
  }
  .pilot-cohort strong { font-size: var(--d34); }
  .pilot-cohort > span:last-child {
    font-size: var(--text-sm);
    color: var(--muted);
  }
  .pilot-result {
    display: grid;
    gap: 8px;
  }
  .pilot-result-head {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr);
    align-items: baseline;
    gap: 12px;
  }
  .pilot-result-head strong {
    font-size: var(--d34);
    line-height: 1;
  }
  .pilot-result-head span {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--ink-2);
  }
  .pilot-track {
    height: 8px;
    overflow: hidden;
    background: var(--rule);
  }
  .pilot-track > span {
    display: block;
    width: var(--pilot-value);
    height: 100%;
    background: var(--accent);
  }
  .framing > .pilot-conclusion {
    margin-top: 4px;
    font-weight: 500;
    color: var(--ink);
  }

  /* --- Beats: the narrative unit of the AI entries --------------------- */
  .beat {
    display: flex;
    gap: 48px;
    padding-block: 44px;
    border-top: 1px solid var(--rule);
  }
  .beat-index { flex: 0 0 110px; }
  .beat-body {
    flex: 1 1 0;
    min-width: 0;
    max-width: 780px;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  .beat-body > p {
    font-size: var(--text-md);
    line-height: 1.7;
    letter-spacing: 0;
    color: var(--ink-2);
  }
  .beat-body em { font-style: italic; }

  /* --- Beat row: three decisions across, not down --------------------
     Only the Conversations entry uses .beat-row; MountBlue's beats stay in the
     stacked row layout above, which is why every rule here is scoped to the row
     rather than to .beat. Inside a column the index sits ABOVE the copy — the
     110px index gutter that works across a full-width row would eat a third of
     a 300px column. */
  .beat-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0 40px;
    border-top: 1px solid var(--rule);
  }

  .beat-row > .beat {
    flex-direction: column;
    gap: 14px;
    /* The row above draws the single rule; a per-beat top border would redraw it
       three times with the column gaps punched through it. */
    border-top: 0;
  }

  .beat-row > .beat > .beat-index {
    flex: none;
  }

  .beat-row .beat-body {
    /* 780px is the single-column reading measure; in a third of the page the
       column is already narrower than that, so the cap only ever fights the
       grid. */
    max-width: none;
    gap: 14px;
  }

  .beat-row .beat-body > p {
    /* One step down from the full-width beats: --text-md at a 300px measure runs
       about 34 characters a line, which is too few for comfortable prose. */
    font-size: var(--text-base);
  }

  /* Two-up before stacking: three 300px columns hold, three 200px ones do not. */
  @media (max-width: 1000px) {
    .beat-row {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 0 32px;
    }
  }

  @media (max-width: 680px) {
    .beat-row {
      grid-template-columns: minmax(0, 1fr);
      gap: 0;
      border-top: 0;
    }

    /* Stacked, the rule between beats has to come back — there is no column gap
       left to separate them. */
    .beat-row > .beat {
      border-top: 1px solid var(--rule);
    }
  }

  .beat-pair {
    display: flex;
    align-items: flex-start;
    gap: 40px;
    border-top: 1px solid var(--rule);
  }
  .beat-pair > .beat {
    flex: 1 1 0;
    min-width: 0;
    border-top: 0;
  }
  .beat-pair > .code-block {
    flex: 0 1 460px;
    min-width: 0;
    margin-top: 44px;
  }
  .beat-pair--evidence > .figure {
    flex: 1 1 0;
    min-width: 0;
    margin-top: 44px;
  }
  .beat-pair--evidence > .figure img { width: 100%; }

  /* Two beats sharing one media row: they stack in the text column instead of
     each claiming a full-width row of their own. The pair draws the rule above
     the first beat, so only the beats after it keep their own. */
  .beat-stack {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
  }
  .beat-stack > .beat:first-child { border-top: 0; }

  /* --- Section lede: one line of substance under a section heading ------ */
  .section-lede {
    margin-top: 14px;
    max-width: 62ch;
    font-size: var(--text-base, 1rem);
    line-height: 1.62;
    color: var(--ink-2);
  }
  .problem-inner .section-lede,
  .impact-head + .section-lede { margin-top: 18px; }
  .problem-inner .section-lede { color: var(--band-ink-2); }
  .impact-band .section-lede { color: var(--muted); }

  /* --- Proof split: the session on the left, the prompt on the right ---- */
  .proof-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 28px;
    align-items: start;
    margin-top: 44px;
  }
  /* The screenshot carries its own card edge and transparency, so the wrapper
     adds no second frame around it. */
  .proof-shot {
    margin: 0;
    border: 0;
    border-radius: 6px;
    overflow: hidden;
    background: transparent;
  }
  .proof-shot img {
    display: block;
    width: 100%;
    height: auto;
  }
  .proof-shot figcaption {
    padding: 14px 18px 18px;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--muted);
  }
  .proof-split .code-block { margin-top: 0; }
  .proof-split > .framing { margin-top: 0; }
  .proof-prompt {
    display: flex;
    flex-direction: column;
    gap: 18px;
    min-width: 0;
  }
  /* --- Code fit: an excerpt sized by what sits beside it ----------------
     A code block that runs to its full length leaves the column next to it
     stranded against half a page of code. .code-fit caps it at the height of
     its neighbour and scrolls the excerpt inside that.

     The absolute positioning is what makes it work. A grid or flex item sizes
     its track from its own content, so a tall <pre> grows the row no matter
     what height is set on it — a percentage resolves against a track the item
     itself defined. Out of flow, the block stops contributing to that sizing:
     the track becomes the neighbour's height, and .code-fit, which stretches to
     it, is the containing block the excerpt measures against.

     inset with `bottom: auto` plus max-height, rather than a full `inset: 0`:
     the block should be CAPPED by its neighbour, not stretched to it. A four
     line plan excerpt next to a long beat keeps its own height instead of
     opening a card of dead space. */
  .code-fit {
    position: relative;
    align-self: stretch;
    min-width: 0;
  }

  .code-fit > .code-block {
    position: absolute;
    inset: 0 0 auto 0;
    max-height: 100%;
    height: auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  /* The filename bar keeps its height; only the excerpt below it scrolls. */
  .code-fit > .code-block > .code-head { flex: 0 0 auto; }

  .code-fit > .code-block > pre {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow: auto;
    overscroll-behavior: contain;
  }

  .code-fit > .code-block > .code-foot { flex: 0 0 auto; }

  /* Paired with a screenshot, the excerpt always outruns it, so the column
     needs a floor for the reverse case — a short image must not squeeze the
     code to nothing. */
  .proof-split > .code-fit { min-height: 320px; }

  /* The pair put its 44px offset on the code block; the wrapper carries it now,
     so the block can sit flush inside a box that starts below the rule. */
  .beat-pair > .code-fit {
    flex: 0 1 460px;
    margin-top: 44px;
  }

  /* Inside the prompt column the excerpt takes the room the caption below it
     does not. The column itself has to stretch first: left at align-items:start
     it would size to its own content, and the excerpt would cap against its own
     minimum instead of against the panel beside it. */
  .proof-split > .proof-prompt { align-self: stretch; }
  .proof-prompt > .code-fit { flex: 1 1 auto; min-height: 240px; }

  .proof-foot {
    grid-column: 1 / -1;
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--muted);
    max-width: 78ch;
  }
  @media (max-width: 900px) {
    .proof-split { grid-template-columns: minmax(0, 1fr); gap: 32px; }

    /* Stacked, there is no column left to match, and an out-of-flow block in a
       collapsed wrapper would have no height at all. Every .code-fit goes back
       into flow, with a viewport-relative cap so the excerpt still scrolls
       rather than running for several screens. */
    .proof-split > .code-fit { min-height: 0; }

    .code-fit { position: static; }

    .code-fit > .code-block {
      position: static;
      max-height: none;
    }

    .code-fit > .code-block > pre { max-height: 60vh; }
  }

  /* --- Code / plan excerpt --------------------------------------------- */
  /* A neutral dark-grey editor surface rather than the blue-black card it was:
     less of a floating object on the page, closer to the IDE the excerpts came
     from. Radius and shadow both come down with it. */
  .code-block {
    background: #1f1f1f;
    border: 1px solid #333333;
    border-radius: 6px;
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.08);
    overflow: hidden;
  }
  .code-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 18px;
    border-bottom: 1px solid #333333;
    background: #262626;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: #a6a6a6;
  }
  .code-dot {
    width: 8px;
    height: 8px;
    margin-right: 28px;
    border-radius: 50%;
    background: #ff5f57;
    box-shadow: 14px 0 #febc2e, 28px 0 #28c840;
    flex: none;
  }
  .code-block > pre {
    margin: 0;
    padding: 22px 18px;
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    line-height: 1.7;
    letter-spacing: 0;
    color: #d4d4d4;
    white-space: pre;
  }
  .code-block--syntax > pre code {
    font: inherit;
    color: inherit;
  }
  .code-block--syntax .code-key {
    color: #82aaff;
    font-weight: 600;
  }
  .code-block--syntax .code-fn {
    color: #c792ea;
  }
  .code-block--syntax .code-var {
    color: #d4d4d4;
  }
  .code-block--syntax .code-op {
    color: #89ddff;
  }
  .code-block--syntax .code-num {
    color: #f78c6c;
    font-variant-numeric: tabular-nums;
  }
  .code-block--syntax .code-string {
    color: #c3e88d;
  }
  .code-block--syntax .code-comment {
    color: #8f8f8f;
  }
  .code-foot {
    padding: 0 18px 20px;
    font-size: var(--text-sm);
    line-height: 1.6;
    letter-spacing: 0;
    color: #9a9a9a;
  }

  @media (max-width: 720px) {
    .code-block > pre { font-size: 0.75rem; }
  }

  /* --- Screen sequence: four stills, each with its own reading --------- */
  .seq {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 56px 44px;
  }
  .seq-label {
    display: flex;
    align-items: center;
    gap: 12px;
    padding-bottom: 14px;
  }
  .seq-note {
    padding-top: 16px;
    font-size: var(--text-sm);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--muted);
  }

  /* One box for all four stills. The sources run 1440x713 to 1440x1088 — a 1.5x
     spread in height — so at natural aspect the 2x2 came out ragged: 02 and 03
     towered over 01 and 04, and the notes under them stopped lining up across a
     row. Pinning the box to 3:2 (the same ratio the home page's entry cards use,
     so the two surfaces agree) makes the four read as one set of feature tiles.

     contain, never cover: cover would crop, and on 02 — the tallest capture, a
     full instructions screen — cropping is exactly what loses the content the
     still is there to show. The capture letterboxes instead and the gradient mat
     fills whatever is left over, which is what the mat is for. */
  .seq .figure--natural {
    aspect-ratio: 3 / 2;
  }

  /* Block, not grid, and the height stated on the image rather than left to the
     row. As a grid item the image sat in an auto-sized row, so height:100% had
     no definite height to resolve against and fell back to intrinsic — 02, the
     only capture taller than the box, then stretched the row past the
     aspect-ratio and got clipped by the figure's overflow:hidden. Against the
     block figure the content height is definite, so 100% resolves and contain
     letterboxes all four the same way. */
  .seq .figure--natural > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  /* A pair is a comparison, so both figures share one box: the two captions
     then start on the same line and the eye moves across the row instead of
     stepping down it. Conversations already boxes every still at 8:5 below, so
     this only needs to cover the pages that do not — MountBlue, whose standalone
     stills stay at their natural ratio because they are read one at a time. */
  .case-study--ai:not(.entry--conversations) .pair .figure--natural {
    aspect-ratio: 3 / 2;
  }

  .case-study--ai:not(.entry--conversations) .pair .figure--natural > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  /* Conversations does the same thing, at 8:5. Every still on that page is a
     1600x1000 capture, so the admin/learner pair and the stacked figures already
     come out level — stating the ratio makes that structural instead of
     incidental: swap in a capture at another size and the boxes stay locked
     rather than the row going ragged again.

     Scoped to .entry--conversations, not .case-study--ai, because MountBlue's
     standalone stills run 1.40 to 0.94 — tall product screens that an 8:5 box
     would letterbox badly. Its four-up sequence has its own ratio above.

     :has(img) holds the hero video out of it. That capture is a 1.27 recording
     of a flow, not an 8:5 screen, and boxing it would pillarbox the whole
     walkthrough for nothing. */
  .entry--conversations .figure--natural:has(img) {
    aspect-ratio: 8 / 5;
  }

  .entry--conversations .figure--natural:has(img) > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  /* --- Exploration band: the open questions, inverted ------------------ */
  .explore-band {
    padding: 56px;
    background: var(--band-bg);
    border-radius: 6px;
  }
  .explore-head {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding-bottom: 40px;
  }
  .explore-head .eyebrow { color: var(--band-muted); }
  .explore-head .display { color: var(--band-ink); }
  .explore-item {
    display: flex;
    gap: 48px;
    padding-block: 32px;
    border-top: 1px solid var(--band-rule);
  }
  .explore-marker {
    flex: 0 0 48px;
    color: var(--band-muted);
  }
  .explore-q {
    flex: 0 1 420px;
    min-width: 0;
    font-size: var(--text-md);
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: var(--tracking-tight);
    color: var(--band-ink);
  }
  .explore-a {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-md);
    line-height: 1.65;
    letter-spacing: 0;
    color: var(--band-ink-2);
  }
  .explore-item.is-open .explore-marker { color: var(--band-ink); }
  .explore-item.is-open .explore-q::after {
    content: "Still open";
    display: block;
    margin-top: 10px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--band-muted);
  }

  /* --- Attribution note ------------------------------------------------ */
  .attribution {
    display: flex;
    gap: 40px;
    padding: 26px 0;
    border-block: 1px solid var(--rule);
  }
  .attribution > .eyebrow { flex: 0 0 110px; }
  .attribution > p {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-base);
    line-height: 1.6;
    letter-spacing: 0;
    color: var(--ink-2);
  }

  .footer-links {
    display: flex;
    align-items: center;
    gap: 28px;
    flex-wrap: wrap;
  }

  /* --- Accent placement ------------------------------------------------
     Deliberately short. The accent marks things that orient you — which
     feature this is, what a card is called, which takeaway you're on — and
     the rust marks a number or a step that failed. Body copy, rules and
     captions stay monochrome, so the colour never carries meaning alone. */

  /* Section meta: the right-hand half of a section head. The left eyebrow stays
     muted — it labels, the meta qualifies. (The impact band used to carry a
     release-window line here too; it was removed along with .impact-window.) */
  .section-head > .eyebrow { color: var(--accent); }
  .feature-head-left > .eyebrow { color: var(--accent); }
  /* ...except the pre-redesign tag, which sits on the dark band where accent
     blue reads as a live link rather than a caption. Matches CS1's tag. */
  .problem-before-flow .feature-head-left > .cards-tag { color: var(--band-muted); }
  .glance-grid .h-sub { color: var(--accent); }
  .reflection-num { color: var(--accent); }
  .principle-tag { color: var(--accent); }
  .impact-grid > :first-child .display { color: var(--accent-soft); }
  /* All three headline figures come from the same A/B split, so they carry the
     same weight: accent on every one, not just the first. Same layer as the
     :first-child rule above and more specific, so it wins. */
  .case-study--cs1 .impact-grid .display { color: var(--accent-soft); }
  .impact-lead .display { color: var(--accent-soft); }
  /* The accent marks the headline number. Where a band has a lead strip that
     number has left the grid, so the rule above would hand the accent to whatever
     is now first in the row and the band would carry two. Put it back to plain. */
  .impact-grid--rest > :first-child .display { color: var(--band-ink); }
  .explore-head .eyebrow { color: var(--accent-soft); }

  /* A figure that reports a failure, applied per page rather than by
     position: only the author knows which number is the bad one. */
  .d--neg { color: var(--negative); }
  .d--pos { color: var(--positive); }

  /* The Core Problem band inverts against the page, so the page-level coral
     lands at 2.89:1 on it, under the 3.0 large-text floor. --negative-soft is
     the band-safe pair and already flips with the theme: 7.3:1 on the dark band
     in light mode, 5.18:1 on the light band in dark mode. */
  .section--problem .d--neg { color: var(--negative-soft); }
  .section--problem .d--pos { color: var(--positive-soft, var(--positive)); }

  /* The baseline stats moved into the inverted band (item 4). A plain .display
     inherits the page ink, which lands at 1:1 on the band and disappears, so the
     neutral figures need the band ink explicitly. */
  .problem-inner .stats-grid .display:not(.d--neg):not(.d--pos) { color: var(--band-ink); }
  .problem-inner .stats-grid .base { color: var(--band-ink-2); }
  .problem-inner .stats-grid > * + * { border-left-color: var(--band-rule); }

  /* Pills carry the状 state, so they carry the tone. Solid was already the
     shipped/eligible pill and outline the rejected/prior one, so the meaning
     was there before the colour: green and red only make it visible.
     Tints rather than fills keep it quiet. */
  .pill--solid {
    background: var(--positive-tint);
    border-color: var(--positive);
    color: var(--positive);
  }
  .pill--outline {
    background: var(--negative-tint);
    border-color: var(--negative);
    color: var(--negative);
  }
  /* 'Explored' stays neutral: it is neither a win nor a failure. */

  /* Improvement: the baseline stays muted, so the eye reads grey → green. */
  .ab-values > .display:not(.ab-from),
  .ab-values > .eyebrow:not([aria-hidden]) { color: var(--positive); }
  .delta-values > .display:not(.delta-from) { color: var(--positive); }
  .net {
    border-left-color: var(--positive);
    background: var(--positive-tint);
  }
  .net > .display { color: var(--positive); }
  .fit-bar { background: var(--positive); }

  /* The step where the old flow ended. */
  .flow-step.is-failure {
    border-top-color: var(--negative);
    background: var(--negative-tint);
  }
  .flow-step.is-failure .flow-step-num,
  .flow-step.is-failure .flow-step-label { color: var(--negative); }

  /* Paired before/after labels, where the pairing is a judgement. */
  .pair-role--pos {
    background: var(--positive-tint);
    border: 1px solid var(--positive);
    color: var(--positive);
  }
  .pair-role--neg {
    background: var(--negative-tint);
    border: 1px solid var(--negative);
    color: var(--negative);
  }

  /* The step where the old flow ended. */
  .flow-step.is-failure {
    border-top-color: var(--negative);
    background: var(--surface);
  }
  .flow-step.is-failure .flow-step-num,
  .flow-step.is-failure .flow-step-label { color: var(--negative); }

  /* --- Index list: the reflection row, made clickable ------------------ */
  a.reflection {
    align-items: center;
    transition: background-color 0.2s ease, padding-left 0.2s ease;
  }
  a.reflection .reflection-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  a.reflection:hover,
  a.reflection:focus-visible {
    background: var(--surface);
    padding-left: 20px;
  }
  a.reflection:hover .set-meta,
  a.reflection:focus-visible .set-meta { color: var(--ink-2); }
  .set-meta { display: block; }

  /* --- Figure zoom -----------------------------------------------------
     Driven by js/v1b-zoom.js. The affordance is deliberately quiet: a
     cursor change and a corner mark, no permanent overlay furniture. */
  .figure.is-zoomable {
    cursor: zoom-in;
    position: relative;
  }
  .figure.is-zoomable::after {
    content: "⤢";
    position: absolute;
    right: 10px;
    bottom: 8px;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    border: 1px solid var(--rule-strong);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--ink-2);
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  .figure.is-zoomable:hover::after,
  .figure.is-zoomable:focus-visible::after { opacity: 1; }
  .figure.is-zoomable:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

  .zoom {
    position: fixed;
    inset: 0;
    z-index: 200;
    overflow: auto;
    padding: 56px 32px;
    background: color-mix(in srgb, #14130f 94%, transparent);
    cursor: zoom-out;
    overscroll-behavior: contain;
  }
  /* Wide enough to read UI text, and free to run taller than the viewport so
     full-page captures can be scrolled rather than shrunk. */
  .zoom-img {
    width: min(1600px, 100%);
    height: auto;
    margin-inline: auto;
    border-radius: 6px;
    background: var(--bg);
  }
  .zoom-hint,
  .zoom-close {
    /* Fixed ink: the overlay ground is dark in both themes. */
    color: #8b877f;
    position: fixed;
    top: 18px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--band-muted);
  }
  .zoom-hint { left: 32px; margin: 0; }
  .zoom-close {
    right: 32px;
    padding: 4px 10px;
    background: none;
    border: 1px solid #33322f;
    border-radius: 20px;
    color: #b5b0a7;
    cursor: pointer;
  }

  /* On a phone the overlay was barely a zoom at all: width:min(1600px,100%)
     resolves to the viewport minus padding, so tapping a 1440px capture opened
     it at ~360px — the same unreadable size it already was inline. Here the
     image runs past the viewport and .zoom's overflow:auto pans it, so the UI
     text inside comes back to something close to native resolution. 300vw is
     the ceiling: enough to read a dense product screen, not so much that
     panning to the far edge becomes a chore. */
  @media (max-width: 720px) {
    .zoom { padding: 44px 0 28px; }
    .zoom-hint { display: none; }
    .zoom-img {
      width: min(1500px, 300vw);
      max-width: none;
      border-radius: 0;
    }
    /* 44px of tappable area; the ring itself stays visually small. */
    .zoom-close {
      top: 8px;
      right: 10px;
      min-width: 44px;
      min-height: 44px;
      padding: 4px 10px;
    }
  }

  /* Touch has no hover and no :focus-visible, so on a phone the corner mark
     never appeared and nothing said these captures could be opened — which
     matters more here than on desktop, since the inline rendering is where the
     screenshots are least legible. Show it permanently instead, and give it
     enough size to read as a control rather than a speck. */
  @media (hover: none) {
    .figure.is-zoomable::after {
      opacity: 1;
      width: 32px;
      height: 32px;
      right: 8px;
      bottom: 8px;
    }
  }

  /* --- Mobile tap targets ----------------------------------------------
     Standalone controls (not links inside a sentence) need a 44px-tall hit
     area. These all measured 18-23px, which is a coin-flip to hit one-handed.
     Padding rather than height, so the text keeps its own size and the extra
     area is centred on it; negative inline margin keeps the left edge aligned
     with the copy above rather than indenting by the new padding. */
  @media (max-width: 720px) {
    .back-link,
    .footer-next,
    .proto-open,
    a.eyebrow,
    .nav-logo,
    .nav-links a {
      display: inline-flex;
      align-items: center;
      min-height: 44px;
      padding-block: 10px;
    }

    /* These two carry their own box, so the box grows rather than the padding.
       Both sit inside the 64px nav pill, which has the room. */
    .nav-toggle {
      width: 44px;
      height: 44px;
    }

    .theme-toggle {
      height: 44px;
      min-width: 44px;
      justify-content: center;
    }
  }

  /* --- Section rail ----------------------------------------------------
     Lives in the left margin the narrower measure frees up. Only the core-
     problem band bleeds full width, so that is the one place the rail sits
     on dark ground: it flips to band ink there via .is-inverted, set by
     js/v1b-outline.js from the sections marked [data-rail-invert]. */
  .outline {
    position: fixed;
    top: 50%;
    /* Parked in the margin beside the column: its own width plus a 20px
       breath, so it never overlaps the content. */
    left: max(16px, calc((100vw - var(--col-wide)) / 2 - 150px));
    transform: translateY(-50%);
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: auto;
    max-width: 130px;
  }
  .outline-item {
    display: flex;
    align-items: center;
    min-width: 0;
  }
  .outline-label {
    min-width: 0;
    font-family: var(--font-mono);
    /* The rail is navigation, not page copy; keep it compact even though the
       case-study body scale is deliberately larger. */
    font-size: 0.75rem;
    font-weight: var(--weight-label);
    line-height: 1.4;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--rail-dim, var(--muted));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color 0.2s ease;
  }
  .outline-item:hover .outline-label,
  .outline-item:focus-visible .outline-label {
    color: var(--rail-live, var(--ink));
  }
  .outline-item.is-active .outline-label {
    color: var(--rail-accent, var(--accent));
  }
  .outline-top {
    margin-top: 6px;
    padding-top: 11px;
    border-top: 1px solid var(--rail-rule, var(--rule-strong));
  }

  /* Leaving the case study was only possible from the top of the page or the
     footer. The rail is on screen the whole way down, so the way out lives
     there too, under the same rule as Top. */
  .outline-back { margin-top: 2px; }
  .outline.is-inverted {
    --rail-dim: var(--band-muted);
    --rail-live: var(--band-ink);
    --rail-rule: var(--band-rule);
    --rail-accent: var(--accent-soft);
  }
}

/* All-or-nothing: the list needs ~150px of margin (992 + 300), so it shows
   above 1300 and is absent below. No dash-only mode — a nav you have to hover
   to read is not a nav. */
@media (max-width: 1300px) {
  .outline { display: none; }
}

/* Mobile nav, matching styles.css: the links become a panel hanging off the
   pill rather than a full-bleed sheet. */
@media (max-width: 640px) {
  .nav-toggle { display: block; }

  .nav-links {
    position: fixed;
    inset: calc(var(--nav-clear) + 8px) var(--nav-inset-x) auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
    padding: 24px;
    border: 1px solid var(--nav-glass-border);
    border-radius: var(--radius-nav);
    background: var(--nav-glass-strong);
    -webkit-backdrop-filter: var(--nav-backdrop);
    backdrop-filter: var(--nav-backdrop);
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.24s ease, transform 0.24s ease;
  }
  .nav-links a { font-size: var(--text-base); }
  .nav-links.is-open {
    transform: none;
    opacity: 1;
    pointer-events: auto;
  }
}

@media (max-width: 1100px) {
  /* .pair is not in here with .beat-pair: a beat pair puts a column of prose
     beside a code block and genuinely needs 1100px, while a .pair is two images
     compared against each other and reads fine at half of a 1000px window. It
     stacks at 820px instead, with the four-up. */
  .beat-pair { flex-direction: column; gap: 40px; }
  .beat-pair > .code-block { flex: none; align-self: stretch; margin-top: 0; }
  /* The pair is a column here, so there is no neighbour to measure against —
     back into flow before the 900px block below catches every other .code-fit. */
  .beat-pair > .code-fit {
    flex: none;
    align-self: stretch;
    margin-top: 0;
    position: static;
  }

  .beat-pair > .code-fit > .code-block {
    position: static;
    max-height: none;
  }

  .beat-pair > .code-fit > .code-block > pre { max-height: 60vh; }
  /* flex:none is load-bearing. The desktop rule sets flex:1 1 0 for the side-by
     -side row; once this block flips the pair to a column, that flex-basis:0
     starts applying to HEIGHT, collapsing the figure to its 12px of padding
     while the image inside stays 300px+ and gets clipped by .figure's
     overflow:hidden. The code-block on the line above already resets it; the
     figure was missed. */
  .beat-pair--evidence > .figure { flex: none; align-self: stretch; margin-top: 0; }
  .beat-pair > .beat { padding-bottom: 0; }
  .beat-stack { flex: none; align-self: stretch; }
  .beat-stack > .beat:last-child { padding-bottom: 0; }
  .delta-grid { flex-wrap: wrap; }
  .delta-grid > * { flex: 1 1 46%; }
  .delta-grid > :nth-child(3) { border-left: 0; padding-left: 0; }
  .delta-grid > :nth-child(n + 3) { padding-top: 32px; border-top: 1px solid var(--rule); }
  .impact-notes > .impact-note { flex: none; }
}

@media (max-width: 720px) {
  .beat,
  .explore-item,
  .attribution { flex-direction: column; gap: 16px; align-items: stretch; }
  .beat-index,
  .explore-marker,
  .explore-q,
  .attribution > .eyebrow { flex: none; }
  .explore-band { padding: 32px 24px; }
  /* 300px left only a 20px peek of the next card on a 360px phone, which reads
     as a cropped image rather than a scroller. 260px keeps the cue visible. */
  .filmstrip > .film-step { flex: 0 0 260px; }
  .figure--scroll { max-height: 440px; }
  .delta-grid > * {
    flex: 1 1 100%;
    padding-inline: 0;
    border-left: 0 !important;
  }
  .delta-grid > :nth-child(n + 2) { padding-top: 28px; border-top: 1px solid var(--rule); }
  .stack-block,
  .stack-block--flush { padding-top: 56px; }
}

@layer utilities {
  /* Reveal-on-scroll, driven by the same [data-reveal] hook as the site. */
  [data-reveal] {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.6s ease, transform 0.6s ease;
  }
  [data-reveal].is-visible,
  .no-js [data-reveal] {
    opacity: 1;
    transform: none;
  }
  @media (prefers-reduced-motion: reduce) {
    [data-reveal] {
      opacity: 1;
      transform: none;
      transition: none;
    }
  }
}

/* The desktop body scale grew to 16–20px, so its surrounding rhythm moves up
   one restrained step too. These adjustments deliberately stop at tablet:
   mobile uses the denser 12–14px scale and keeps its compact spacing. */
@media (min-width: 721px) {
  .section--glance { padding-block: 132px 108px; }
  /* The AI entries open on .section--glance, which lands 132px under the hero
     against CS1's 84px. Declared here rather than in @layer components, where
     an earlier attempt lost to the line above — unlayered beats layered. */
  .case-study--ai .section--glance:first-of-type { padding-top: 84px; }
  .section--stats { padding-block: 132px; }
  .section--problem { padding-block: 122px; }
  .section--origin,
  .section--flow { padding-top: 132px; }
  .section--principle { padding-block: 110px 48px; }
  .section--feature { padding-top: 144px; }
  .section--principle + .section--feature { padding-top: 64px; }
  #feature-02 { padding-bottom: 122px; }
  #feature-03 { padding-bottom: 144px; }
  .section--impact,
  .section--takeaways,
  .section--footer { padding-top: 144px; }

  /* The hero stacks four separate things — headline, the two-column context
     row, the tools strip, then the media. At 36px apart they read as one
     block; each step now gets its own interval so the eye knows where one
     ends. */
  .hero-sub-row {
    gap: 56px;
    margin-top: 60px;
  }
  .tools-row { margin-top: 56px; }

  .section-head {
    gap: 68px;
    padding-bottom: 34px;
  }
  .section-head-left { gap: 20px; }
  .stats-inner { gap: 48px; }
  .role-block { margin-top: 34px; }
  .proc-block { margin-top: 34px; }
  .proc-block > .eyebrow { margin-bottom: 20px; }

  .problem-inner { gap: 28px; }
  .problem-before-flow {
    margin-top: 48px;
    padding-top: 64px;
  }
  .problem-before-flow .filmstrip { margin-top: 28px; }
  .flow-inner--problem { margin-top: 40px; }
  .sub-problem { padding-block: 36px; }
  .flow-inner { gap: 64px; }
  .flow-head { gap: 20px; }

  .principle {
    gap: 64px;
    padding: 60px 60px 60px 56px;
  }
  .feature-head { padding-bottom: 48px; }
  .feature-head-row { gap: 68px; }
  .feature-head-left { gap: 18px; }
  .user-story {
    gap: 48px;
    padding-block: 44px;
  }
  .card-head {
    gap: 18px;
    padding-bottom: 28px;
  }
  .problem-block { padding-top: 84px; }
}

/* ==========================================================================
   Responsive — the comp is a 1440 desktop layout; these are the collapses.
   ========================================================================== */

@media (max-width: 1100px) {
  :root { --gutter: 28px; }

  .hero-title-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
  }
  .hero-title-context {
    width: min(100%, 360px);
    justify-self: end;
  }

  .hero-sub-row {
    flex-direction: column;
    gap: 28px;
  }
  .hero-metric { flex: none; }

  .glance-grid,
  .stats-grid,
  .impact-grid {
    flex-wrap: wrap;
  }
  .glance-grid > *,
  .stats-grid > *,
  .impact-grid > * {
    flex: 1 1 46%;
  }
  .glance-grid > :nth-child(3),
  .stats-grid > :nth-child(3),
  .impact-grid > :nth-child(3) {
    border-left: 0;
    padding-left: 0;
  }
  .glance-grid > :nth-child(n + 3),
  .stats-grid > :nth-child(n + 3) {
    padding-top: 32px;
    border-top: 1px solid var(--rule);
  }
  .impact-grid > :nth-child(n + 3) { border-top: 1px solid var(--band-rule); }

  .flow-strip { flex-wrap: wrap; }
  .flow-step { flex: 1 1 30%; }

  .step-row {
    flex-direction: column;
    gap: 56px;
  }

  .impact-notes { flex-direction: column; gap: 28px; }
  .note-grid { flex-direction: column; gap: 32px; }
  .note-grid > * {
    padding-left: 0;
    padding-right: 0;
    border-left: 0 !important;
  }
}

@media (max-width: 720px) {
  :root { --gutter: 20px; }

  .hero-title-line { white-space: normal; }

  /* 24px of card padding inside a 20px page gutter left the screenshot
     barely wider than half the viewport. The frame only needs to read as a
     frame down here. */
  .problem-card {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
    padding: 14px;
  }
  .problem-cards { gap: 22px; }

  .origin-step--split {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }

  .section--hero { padding-block: calc(var(--nav-clear) + 16px) 40px; }
  .section--glance,
  .section--flow,
  .section--feature,
  .section--impact,
  .section--takeaways,
  .section--footer { padding-top: 80px; }
  .section--stats { padding-block: 80px; }
  .section--problem { padding-block: 72px; }
  .section--origin { padding-top: 80px; }
  .section--principle { padding-block: 64px 36px; }
  .section--glance { padding-bottom: 72px; }
  #feature-02 { padding-bottom: 72px; }


  .process-strip { flex-direction: column; gap: 20px; }
  .process-step { padding-right: 0; }

  .glance-grid > *,
  .stats-grid > *,
  .impact-grid > *,
  .flow-step {
    flex: 1 1 100%;
    padding-left: 0;
    border-left: 0 !important;
  }
  .glance-grid > :nth-child(n + 2),
  .stats-grid > :nth-child(n + 2) {
    padding-top: 28px;
    border-top: 1px solid var(--rule);
  }
  .impact-grid > :nth-child(n + 2) { border-top: 1px solid var(--band-rule); }
  .stats-grid > * { padding-inline: 0; }

  .impact-survey {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    margin-top: 40px;
    padding-top: 36px;
  }
  .impact-survey-head h3,
  .impact-survey-head p { max-width: none; }

  .sub-problem,
  .user-story,
  .caption-row,
  .principle,
  .reflection,
  .feature-head-row,
  .section-head,
  .impact-head {
    flex-direction: column;
    gap: 16px;
    align-items: stretch;
  }
  .reflection-list {
    grid-template-columns: minmax(0, 1fr);
  }
  .reflection,
  .reflection:first-child,
  .reflection:last-child {
    padding: 24px 0;
  }
  .reflection + .reflection {
    border-top: 1px solid var(--rule);
    border-left: 0;
  }
  .sub-problem-index,
  .sub-problem-title,
  .user-story-label,
  .caption-marker,
  .principle-tag,
  .reflection-num,
  .feature-intro { flex: none; }

  .principle { padding: 32px 24px; }
  .note-band { padding: 28px; }
  .impact-band { padding: 40px 24px; }
  .note { padding-top: 64px; }
  .problem-block { padding-top: 56px; }
  /* Three rungs side by side would set two words a line here. */
  .ladder { grid-template-columns: minmax(0, 1fr); }
  .ladder > .rung + .rung { border-left: 0; }
  /* The figure and its caveat cannot share a row here: the label was landing in
     a 138px column and setting about 20 characters a line. */
  .net {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .net-label { flex: none; }
  .shipped-block { padding-top: 64px; }
  .shipped-block--flush { padding-top: 48px; }
  .step-row { padding-top: 64px; }
}

/* Mobile nav — the homepage's 640px rules, verbatim, so the pill collapses
   identically on both. (This page's other mobile rules break at 720px; the
   nav deliberately does not follow them.) */
@media (max-width: 640px) {
  .nav-logo span { display: none; }
  .nav > .wrap {
    gap: 16px;
    padding-inline: 16px;
  }
  .nav-actions { gap: 12px; }
  .theme-toggle-text { display: none; }
  .theme-toggle { padding: 0 4px; }
}

@layer components {
  /* Item 9: at two-line sentence length the display serif is harder to read
     than the body sans used for every other paragraph. The numerals stay serif,
     since two high-contrast characters are an anchor, not a reading load. */
  .reflection-body--sans {
    font-family: var(--font-body);
    font-size: var(--text-lg);
    font-weight: 400;
    line-height: 1.6;
    letter-spacing: 0;
    color: var(--ink);
    flex: 1 1 0;
    min-width: 0;
    max-width: 820px;
  }

  /* Item 9: a plain nav link undersold the fact there is a whole second case
     study behind it, so it becomes a hook plus one small card. */
  .section--next > .wrap {
    display: flex;
    flex-direction: column;
    gap: 16px;
    max-width: var(--col-wide);
  }
  .section--next {
    position: relative;
    isolation: isolate;
    overflow: clip;
    padding-top: 96px;
    background: transparent;
  }
  .next-card {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: stretch;
    gap: 28px;
    padding: 24px;
    border: 1px solid color-mix(in srgb, white 58%, var(--rule));
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface) 68%, transparent);
    box-shadow:
      0 1px 0 rgb(255 255 255 / 0.7) inset,
      0 18px 48px rgb(28 27 25 / 0.09),
      0 4px 14px rgb(28 27 25 / 0.05);
    -webkit-backdrop-filter: blur(18px) saturate(115%);
    backdrop-filter: blur(18px) saturate(115%);
    text-decoration: none;
    transform: translateZ(0);
    transition:
      border-color 220ms cubic-bezier(0.25, 1, 0.5, 1),
      box-shadow 260ms cubic-bezier(0.25, 1, 0.5, 1),
      transform 260ms cubic-bezier(0.25, 1, 0.5, 1);
  }
  .next-card::after {
    display: none;
  }
  .next-card:hover,
  .next-card:focus-visible {
    border-color: color-mix(in srgb, var(--accent) 38%, white);
    box-shadow:
      0 1px 0 rgb(255 255 255 / 0.8) inset,
      0 24px 56px rgb(28 27 25 / 0.13),
      0 6px 18px rgb(28 27 25 / 0.06);
    transform: translateY(-7px) scale(1.006);
  }
  .next-card:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
  .next-card:active {
    transform: translateY(-2px) scale(0.998);
    transition-duration: 120ms;
  }
  .next-card-media { flex: 0 0 28%; min-width: 0; }
  .next-card-placeholder {
    position: relative;
    overflow: hidden;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 1px solid rgb(255 255 255 / 0.6);
    border-radius: 6px;
    background: color-mix(in srgb, var(--bg) 62%, transparent);
    box-shadow: 0 1px 0 rgb(255 255 255 / 0.72) inset;
  }
  .next-card-placeholder::before,
  .next-card-placeholder::after {
    content: "";
    position: absolute;
    border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--rule));
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface) 72%, transparent);
    box-shadow: 0 10px 22px rgb(28 27 25 / 0.07);
  }
  .next-card-placeholder::before {
    inset: 18% 16% 24% 22%;
    transform: rotate(-3deg);
  }
  .next-card-placeholder::after {
    inset: 28% 22% 14% 14%;
    transform: rotate(3deg);
  }
  .next-card-body {
    position: relative;
    z-index: 1;
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .next-card-title { color: var(--ink); }
  .next-card-arrow { margin-top: auto; color: var(--ink-2); }
  .next-card:hover .next-card-arrow { color: var(--ink); }

  @media (max-width: 720px) {
    .section--next { padding-top: 64px; }
    .next-card { flex-direction: column; gap: 20px; padding: 18px; border-radius: 6px; }
    .next-card-media { flex: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .next-card,
    .next-card:hover,
    .next-card:focus-visible,
    .next-card:active {
      transform: none;
      transition: border-color 0.01ms, box-shadow 0.01ms;
    }
  }

  /* Figma node 3170:53465. Cream card, blue "Explore next" pill, the CS2
     headline with its two figures in accent, and the shipped screen bleeding
     off the right edge. */
  .next-card--spotlight {
    position: relative;
    overflow: hidden;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
    align-items: stretch;
    gap: 0;
    /* The generic card pads itself and frosts its background; this variant
       pads inside each column and paints its own cream instead. */
    padding: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 0;
    border-radius: 6px;
    background: #f8f2e9;
    text-decoration: none;
    transform: translateZ(0);
    /* The homepage's --aura-pink hue, borrowed so the card's lift reads as the
       same light as the hero paper rather than a grey drop shadow. */
    --next-glow: 255 188 212;
    box-shadow:
      0 10px 26px rgb(var(--next-glow) / 0.24),
      0 2px 7px rgb(var(--next-glow) / 0.16),
      0 1px 2px rgb(28 27 25 / 0.035);
    /* Spring: overshoot then settle. The bezier is the fallback for engines
       without linear(); the linear() below is the real damped curve. */
    transition:
      transform 560ms cubic-bezier(0.34, 1.7, 0.64, 1),
      box-shadow 380ms cubic-bezier(0.25, 1, 0.5, 1);
    transition-timing-function:
      linear(0, 0.352 11%, 0.812 22%, 1.093 32%, 1.168 41%, 1.084 52%, 0.958 65%, 0.991 80%, 1),
      cubic-bezier(0.25, 1, 0.5, 1);
  }
  .next-card--spotlight::after { display: none; }
  @media (hover: hover) and (pointer: fine) {
    .next-card--spotlight:hover {
      transform: translateY(-18px);
      box-shadow:
        0 28px 54px rgb(var(--next-glow) / 0.38),
        0 8px 18px rgb(var(--next-glow) / 0.22),
        0 2px 4px rgb(28 27 25 / 0.045);
    }
  }
  .next-card--spotlight:focus-visible {
    transform: translateY(-18px);
    box-shadow:
      0 28px 54px rgb(var(--next-glow) / 0.38),
      0 8px 18px rgb(var(--next-glow) / 0.22),
      0 2px 4px rgb(28 27 25 / 0.045);
  }
  .next-card--spotlight:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

  :root[data-theme="dark"] .next-card--spotlight { --next-glow: 255 150 195; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .next-card--spotlight { --next-glow: 255 150 195; }
  }
  .next-card--spotlight:active { transform: translateY(-4px); transition-duration: 140ms; }

  .next-card--spotlight .next-card-body {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
    padding: 40px clamp(24px, 3vw, 44px) 44px;
  }

  /* The pill: geekblue in the comp, mapped to the case-study accent tokens. */
  .next-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 11px;
    border: 1px solid var(--accent-soft);
    border-radius: 6px;
    background: var(--accent-tint);
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    white-space: nowrap;
  }
  .next-tag-icon {
    width: 11px;
    height: 9px;
    flex: none;
    transition: transform 520ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  /* The double chevron is the one moving part — it runs ahead of the card. */
  @media (hover: hover) and (pointer: fine) {
    .next-card--spotlight:hover .next-tag-icon { transform: translateX(4px); }
  }
  .next-card--spotlight:focus-visible .next-tag-icon { transform: translateX(4px); }

  .next-card--spotlight .next-card-title {
    margin: 0;
    max-width: 22ch;
    /* Face, size and leading come from the case-study h3 step — this card
       carried its own 30px scale, which was the last h3 outside the ramp. */
    font-weight: 600;
    color: var(--ink);
  }
  .next-card--spotlight .next-card-line {
    margin: 0;
    max-width: 34ch;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }
  .next-figure { color: var(--accent); }
  .next-figure--bold { font-weight: 600; white-space: nowrap; }

  /* The screen sits flush to the bottom and right, inset from the top, the
     way the comp crops it. */
  .next-card--spotlight .next-card-media {
    position: relative;
    min-width: 0;
    padding-top: 28px;
    overflow: hidden;
  }
  .next-card--spotlight .next-card-media > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: left top;
    border-radius: 6px 0 0 0;
    transform: scale(1);
    transition: transform 640ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  @media (hover: hover) and (pointer: fine) {
    .next-card--spotlight:hover .next-card-media > img { transform: scale(1.03); }
  }
  .next-card--spotlight:focus-visible .next-card-media > img { transform: scale(1.03); }

  @media (max-width: 860px) {
    .next-card--spotlight { grid-template-columns: minmax(0, 1fr); }
    .next-card--spotlight .next-card-body { padding: 26px 22px 22px; gap: 13px; }
    .next-card--spotlight .next-card-title,
    .next-card--spotlight .next-card-line { max-width: none; }
    /* A whole desktop screen scaled into a phone column is unreadable, so the
       strip shows the top-left corner at a legible size instead — a peek at
       the product, not a thumbnail of the page. */
    .next-card--spotlight .next-card-media {
      padding-top: 0;
      aspect-ratio: 16 / 9;
    }
    .next-card--spotlight .next-card-media > img {
      border-radius: 0;
      /* Taller than the strip it sits in, so `cover` crops width instead of
         height and the frame lands on the screen's top-left corner at ~1.5x.
         Width stays 100% — the global img max-width:100% caps it anyway. */
      height: 155%;
      object-position: left top;
    }
  }
  @media (max-width: 720px) {
    .section--next { padding-top: 64px; }
    /* Smaller card, lighter halo — the desktop spread reads as fog at 390px. */
    .next-card--spotlight {
      border-radius: 6px;
      box-shadow:
        0 7px 18px rgb(var(--next-glow) / 0.2),
        0 2px 5px rgb(var(--next-glow) / 0.13),
        0 1px 2px rgb(28 27 25 / 0.03);
    }
    .next-card--spotlight .next-card-title { font-size: 1.375rem; }
  }
  @media (prefers-reduced-motion: reduce) {
    .next-card--spotlight,
    .next-card--spotlight:hover,
    .next-card--spotlight:focus-visible,
    .next-card--spotlight:active,
    .next-card--spotlight:hover .next-tag-icon,
    .next-card--spotlight:focus-visible .next-tag-icon,
    .next-card--spotlight:hover .next-card-media > img,
    .next-card--spotlight:focus-visible .next-card-media > img {
      transform: none;
      transition: border-color 0.01ms, box-shadow 0.01ms;
    }
  }
}

@layer components {
  /* Item 5: the flow reads as a diagram, not five paragraphs. Deliberately
     carries no percentages, so nothing here depends on the funnel figures. */
  .flow-graph {
    display: flex;
    flex-direction: column;
    gap: 32px;
  }
  .flow-track {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    align-items: stretch;
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .flow-track--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .flow-track > .fg-step {
    position: relative;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 24px;
    min-height: 116px;
    padding: 18px 18px 20px;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface);
  }
  /* The arrow between steps, drawn rather than typed so it never reflows. */
  .flow-track > .fg-step + .fg-step { margin-left: 0; }
  .flow-track > .fg-step + .fg-step::before {
    content: "";
    position: absolute;
    left: -13px;
    top: 50%;
    width: 12px;
    height: 1px;
    background: var(--rule-strong);
  }
  .flow-track > .fg-step + .fg-step::after {
    content: "";
    position: absolute;
    left: -5px;
    top: 50%;
    width: 5px;
    height: 5px;
    border-top: 1px solid var(--rule-strong);
    border-right: 1px solid var(--rule-strong);
    transform: translateY(-50%) rotate(45deg);
  }
  .fg-label {
    max-width: 12ch;
    font-size: var(--text-md);
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: var(--tracking-tight);
    color: var(--flow-card-ink, var(--ink));
  }
  .fg-note {
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--band-ink-2, var(--muted));
  }
  .flow-inner--problem .fg-num { color: var(--flow-card-accent); }
  .fg-drop-marker {
    position: absolute;
    top: 20px;
    right: 18px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--flow-card-negative);
    box-shadow: 0 0 0 4px rgb(214 155 136 / 0.12);
  }
  .stat-stage {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 14px;
    font-size: 0.8125rem;
    color: var(--band-muted);
  }
  .problem-inner .stat-stage { color: var(--band-ink-2); }
  @media (max-width: 720px) {
    .stat-stage { font-size: 0.75rem; }
  }
  .stat-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--negative-soft);
    flex: none;
  }
  .fg-step--fail {
    border-color: var(--negative);
    background: var(--negative-tint);
  }
  .fg-step--fail .fg-num { color: var(--flow-card-negative, var(--negative)); }
  .fg-step--fail .fg-label { color: var(--flow-card-negative, var(--negative)); }

  .flow-drops {
    display: flex;
    gap: 28px;
  }
  .flow-drops > .drop {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
  }
  .drop-where { color: var(--negative); }
  /* Dashed until the screenshot lands, so an empty frame reads as reserved
     space rather than a broken image. */
  .drop-frame {
    width: 100%;
    aspect-ratio: 16 / 10;
    border: 1px dashed var(--negative);
    border-radius: 6px;
    background: var(--negative-tint);
  }
  /* Once the screenshot lands the frame stops being a placeholder: it drops the
     reserved 16/10 ratio and takes the image's own, so nothing is cropped. */
  .drop-frame:has(img) {
    aspect-ratio: auto;
    border: 0;
    background: transparent;
  }
  .drop-frame > img { width: 100%; height: auto; display: block; }

  @media (max-width: 900px) {
    /* The flow stacked into one column on small screens, which turned a
       left-to-right sequence into a vertical list and dropped the connectors
       that made it a flow at all. It scrolls sideways instead, so the reading
       direction survives and the arrows stay. The last card is trailed by a
       scroll-padding so it can reach the left edge. */
    .flow-track {
      display: flex;
      grid-template-columns: none;
      gap: 22px;
      padding-bottom: 14px;
      overflow-x: auto;
      overscroll-behavior-x: contain;
      scroll-snap-type: x proximity;
    }
    .flow-track > .fg-step {
      flex: 0 0 min(260px, 68vw);
      min-height: 0;
      margin-left: 0;
      scroll-snap-align: start;
    }
    /* The connector sits in the 22px gutter, same geometry as desktop. */
    .flow-track > .fg-step + .fg-step::before { left: -22px; width: 21px; }
    .flow-track > .fg-step + .fg-step::after { left: -8px; }
    .flow-inner--problem .stats-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 0;
      margin-top: 0;
    }
    .case-study--cs2 .flow-inner--problem .stats-grid,
    .case-study--cs3 .flow-inner--problem .stats-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .case-study--cs2 .flow-inner--problem .stats-grid > .stat-item {
      padding-top: 28px;
    }
    .case-study--cs2 .flow-inner--problem .stats-grid > .stat-item > .display {
      text-align: left;
    }
    .flow-inner--problem .stat-item--alert,
    .flow-inner--problem .flow-metric-cue,
    .flow-inner--problem .stat-item--opened,
    .flow-inner--problem .stat-item--applied,
    .flow-inner--problem .stat-item--rejected { grid-column: auto; }
    .case-study--cs2 .flow-inner--problem .stat-item--readiness,
    .case-study--cs2 .flow-inner--problem .stat-item--builder,
    .case-study--cs2 .flow-inner--problem .stat-item--built,
    .case-study--cs2 .flow-inner--problem .stat-item--complete { grid-column: auto; }
    .flow-inner--problem .stat-item::before,
    .flow-inner--problem .stat-item::after { display: none; }
    .flow-inner--problem .flow-metric-cue { display: none; }
    .case-study--cs2 .flow-inner--problem .stat-item::before,
    .case-study--cs2 .flow-inner--problem .stat-item::after { display: none; }
    .flow-inner--problem .stat-item--alert { border-top: 0; }
    .flow-drops { flex-direction: column; }
  }

  @media (max-width: 560px) {
    .flow-inner--problem .stats-grid { grid-template-columns: minmax(0, 1fr); }
  }
}

@layer components {
  /* --- How we began: research, competitor scan, sketches ------------------- */
  /* Three equal steps. The frames stay dashed until their image lands, so an
     empty one reads as reserved space rather than a broken figure, the same
     way the flow drop-offs do. */
  /* Bookends: the PRD it started from, the AI prototypes it became. Those two
     take content width so the three research steps keep the room. */
  .origin-steps {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: stretch;
    gap: 28px;
    margin: 20px 0 0;
    padding: 0;
    list-style: none;
  }
  .origin-step {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
  }
  .origin-num { color: var(--muted); }
  .origin-title { color: var(--ink); }
  .origin-frame {
    width: 100%;
    aspect-ratio: 4 / 3;
    margin-top: 4px;
    border: 1px dashed var(--rule-strong);
    border-radius: 6px;
    background: var(--surface);
  }
  /* Once a screenshot is in, the frame takes the image's own ratio. */
  .origin-frame:has(img) {
    margin: 4px 0 0;
    aspect-ratio: auto;
    border: 0;
    background: transparent;
  }
  .origin-frame > img { width: 100%; height: auto; display: block; }

  /* --- Approach, split variant: image left / text right, stacked rows ----
     Same 40/60 split as the Core Problem Format A cards. */
  .origin-steps--split {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
    margin-top: 12px;
  }
  .origin-step--split {
    display: grid;
    grid-template-columns: minmax(0, 58%) minmax(0, 1fr);
    align-items: center;
    gap: clamp(24px, 3.5vw, 48px);
  }
  .origin-step--split > .origin-frame { margin-top: 0; }
  .origin-step-text {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
  }
  /* Feature sections: a one-line statement of the problem, set as body copy
     above the "What shipped" block, and a prose fallback for the tab ladder. */
  .feature-problem-line {
    margin: 28px 0 0;
    max-width: 62ch;
    color: var(--ink-2);
  }
  .iteration-summary {
    margin: 18px 0 0;
    max-width: 78ch;
    color: var(--ink-2);
  }
  /* Scope notes under the pilot numbers: what the score does and does not claim. */
  .pilot-scope {
    margin: 16px 0 0;
    color: var(--ink-2);
  }

  /* --- Screenshot annotations -------------------------------------------
     Labels pinned to a screenshot in percentage coordinates, so they track
     the image at any width. Each annotation is a single box: its borders
     draw the leader, a dot marks the target end, and the text hangs off the
     opposite end. Hidden on phones, where there is no room to place them
     without covering the UI they point at. */
  .shot-wrap {
    position: relative;
    display: block;
  }
  /* .figure--edge-crop scales its direct child image; with the image wrapped,
     the scale moves to the wrapper so annotation coordinates stay in step. */
  .figure--edge-crop > .shot-wrap {
    transform: scale(1.02);
  }
  .shot-wrap > img {
    display: block;
    width: 100%;
    height: auto;
  }
  .shot-annots {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Opt-in extra leader length, set per figure via an inline --annot-drop.
       Default 0: most shots have their --h tuned to land the label in a
       specific empty band, and a blanket drop pushes it back onto the UI. */
    --annot-drop: 0%;
  }
  .shot-annot {
    position: absolute;
    left: var(--x);
    top: var(--top);
    height: calc(var(--h) + var(--annot-drop));
    width: 0;
    border-left: 1px solid var(--ink-2);
  }
  .shot-annot > span {
    position: absolute;
    left: 50%;
    width: max-content;
    max-width: var(--w, 160px);
    /* --lx slides a label off its leader when the space directly under the
       line is occupied — the leader still lands on the target, the words move
       to the nearest empty ground. */
    transform: translateX(calc(-50% + var(--lx, 0px)));
    font-family: var(--font-mono);
    font-size: 10px;
    line-height: 1.45;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    text-align: center;
    color: var(--ink-2);
  }
  /* The dot marks the element being pointed at. */
  .shot-annot::before {
    content: "";
    position: absolute;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
  }

  /* Target above, label below. */
  .shot-annot--up > span { top: 100%; margin-top: 7px; }
  .shot-annot--up::before { top: -3px; left: -3px; }

  /* Target below, label above. */
  .shot-annot--down > span { bottom: 100%; margin-bottom: 7px; }
  .shot-annot--down::before { bottom: -3px; left: -3px; }

  /* Elbow: the leader drops from the target's row, then runs across to it, for
     a target that has no empty space directly above or below it. */
  .shot-annot--elbow {
    width: var(--dx);
    border-top: 1px solid var(--ink-2);
  }
  .shot-annot--elbow > span {
    top: 100%;
    margin-top: 7px;
    /* Centre the label on the vertical stem, not on the elbow box: the box is
       only --dx wide, so a 50% anchor pushes the words off the leader. */
    left: 0;
  }
  .shot-annot--elbow::before {
    top: -3px;
    left: auto;
    right: -3px;
  }

  /* A caption carries claims that are about the whole layout rather than one
     element, which an arrow cannot point at. */
  .shot-caption {
    margin-top: 12px;
    max-width: 62ch;
  }

  @media (max-width: 720px) {
    .shot-annots { display: none; }
  }

  .origin-points {
    margin: 4px 0 0;
    padding-left: 1.4em;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: var(--text-md);
    line-height: 1.65;
    color: var(--ink-2);
  }
  /* Rows are stacked, so the sideways step connectors would point at nothing. */
  .origin-steps--split > .origin-step + .origin-step::before,
  .origin-steps--split > .origin-step + .origin-step::after { display: none; }

  /* Linked as a flow: PRD in, three steps of thinking, prototypes out. The
     connector sits at mid-row height so it lines up with the bookend pills. */
  .origin-step { position: relative; }
  .origin-steps > .origin-step + .origin-step::before,
  .origin-steps > .origin-step + .origin-step::after {
    content: "";
    position: absolute;
    top: 50%;
    pointer-events: none;
  }
  .origin-steps > .origin-step + .origin-step::before {
    left: -25px;
    width: 14px;
    height: 1px;
    background: var(--rule-strong);
  }
  .origin-steps > .origin-step + .origin-step::after {
    left: -15px;
    width: 5px;
    height: 5px;
    border-top: 1px solid var(--rule-strong);
    border-right: 1px solid var(--rule-strong);
    transform: translateY(-50%) rotate(45deg);
  }

  /* The bookends sit outside the row now, so the three images keep the full
     measure: PRD opens the sequence, the AI prototypes close it. */
  .origin-rail {
    display: flex;
    align-items: center;
    gap: 16px;
  }
  .origin-rail--start { margin-top: 40px; }
  .origin-rail--end { margin-top: 26px; }
  .origin-rail-line {
    flex: 1 1 auto;
    height: 1px;
    background: var(--rule-strong);
  }
  .origin-tab {
    display: inline-flex;
    align-items: center;
    padding: 10px 16px;
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ink-2);
  }

  @media (max-width: 900px) {
    .origin-steps { grid-template-columns: minmax(0, 1fr); gap: 36px; }
    .origin-step--tab { justify-content: flex-start; }
    /* Stacked, so a sideways arrow would point at nothing. */
    .origin-steps > .origin-step + .origin-step::before,
    .origin-steps > .origin-step + .origin-step::after { display: none; }
  }
}

@layer components {
  /* CS01 colour pass: small blue signals and low-chroma washes only. Large
     reading surfaces, screenshots, and body copy remain neutral. */
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .principle {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
    align-items: center;
    gap: 40px;
    padding: 38px 0;
    border-top: 1px solid color-mix(in srgb, var(--accent) 36%, var(--rule));
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 36%, var(--rule));
    border-left: 0;
    background: transparent;
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .principle-tag {
    display: block;
    width: fit-content;
    padding: 0;
    border-radius: 0;
    background: transparent;
    color: var(--accent);
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .principle-body--sans {
    max-width: 48ch;
    font-size: clamp(1.25rem, 1.85vw, 1.65rem);
    line-height: 1.35;
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .user-story {
    align-items: center;
    margin-top: 8px;
    padding: 28px 32px;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: transparent;
    box-shadow: none;
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .user-story-label {
    display: block;
    width: fit-content;
    flex-basis: auto;
    padding: 0;
    border-radius: 0;
    background: transparent;
    color: var(--accent);
  }

  .case-study--cs1 .rung--explored {
    --rung-state: var(--accent);
  }
  .case-study--cs1 .rung--explored .rung-verdict {
    color: var(--accent);
  }
  .case-study--cs1 .ladder > .rung--explored.is-active {
    background: color-mix(in srgb, var(--accent-tint) 72%, var(--surface));
  }

  .case-study--cs1 .note-band {
    border-top: 2px solid color-mix(in srgb, var(--accent) 42%, var(--rule));
    background: color-mix(in srgb, var(--accent-tint) 48%, var(--surface));
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) #feature-03 {
    background: color-mix(in srgb, var(--surface) 58%, var(--bg));
    box-shadow:
      inset 0 2px 0 color-mix(in srgb, var(--accent) 26%, transparent),
      inset 0 -1px 0 var(--rule);
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) #feature-03 .feature-kicker {
    color: color-mix(in srgb, var(--accent) 46%, var(--ink-2));
  }

  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .origin-num,
  .case-study--cs1 .rung-index {
    color: color-mix(in srgb, var(--accent) 72%, var(--muted));
  }

  @media (max-width: 720px) {
    :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .principle {
      grid-template-columns: minmax(0, 1fr);
      gap: 18px;
      padding-block: 28px;
    }
    :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .user-story {
      align-items: flex-start;
      padding: 22px 18px;
      border-radius: 6px;
    }
  }
}

@layer components {
  /* ==================================================================
     Working-with-AI hero backdrop.
     CS1, CS2 and CS3 show a landscape behind their device mock because it
     is composited into the video itself, bounded by the 992px wrap. These
     two entries have bare screen recordings, so the landscape goes on the
     figure — inside the wrap, not on the full-bleed section, so it cannot
     run under the fixed outline rail and the figure note stays on the page
     ground below it. Scoped to .case-study--ai.
     ================================================================== */
  .case-study--ai .section--media > .wrap > .figure {
    padding: clamp(18px, 2.6vw, 40px);
    border-radius: 6px;
    background-image: url("../assets/shared/hero-backdrop.webp");
    background-repeat: no-repeat;
    background-position: center 58%;
    background-size: cover;
  }

  /* The recording sits above the field rather than being cut into it. */
  .case-study--ai .section--media > .wrap > .figure > video {
    display: block;
    border-radius: 6px;
    box-shadow:
      0 1px 2px rgb(28 27 25 / 0.16),
      0 16px 34px -16px rgb(28 27 25 / 0.36);
  }

  @media (max-width: 720px) {
    .case-study--ai .section--media > .wrap > .figure {
      padding: 12px;
      background-position: center 55%;
    }
  }
}

@layer components {
  /* ==========================================================================
     CS1 + CS2 heading scale — the display headings ran large enough to crowd the
     copy under them, so this dials the whole display ramp down ~25% for this
     case study only. Scoped to :is(.case-study--cs1, .case-study--cs2) so the other pages keep the
     original scale. The stat numbers (.d-62, .d-58, .d-52, .d-40, .d-28) and
     the user-story quote (.d-26) are not headings and are left alone; .h-sub
     already sits at body size, so it is untouched too.
     ========================================================================== */
  :is(.case-study--cs1, .case-study--cs2) .hero-headline { font-size: clamp(2.0625rem, 3.26vw, 3rem); }
  :is(.case-study--cs1, .case-study--cs2) .d-96 { font-size: clamp(1.40625rem, 3.15vw, 2.8125rem); }
  :is(.case-study--cs1, .case-study--cs2) .d-88 { font-size: clamp(1.40625rem, 2.925vw, 2.625rem); }
  :is(.case-study--cs1, .case-study--cs2) .d-76 { font-size: clamp(1.359375rem, 2.925vw, 2.625rem); }
  :is(.case-study--cs1, .case-study--cs2) .d-68 { font-size: clamp(1.21875rem, 2.2875vw, 2.0625rem); }
  :is(.case-study--cs1, .case-study--cs2) .d-56 { font-size: clamp(1.125rem, 2.1vw, 1.875rem); }
  :is(.case-study--cs1, .case-study--cs2) h3.d-34,
}

@layer components {
  /* ==========================================================================
     CS1 funnel — five steps with the metric under the step it belongs to:
     [note] (1) · opens (2) · clicks apply (3) · completes (4) · applied (5).
     The margin note opens the row, where the funnel's own baseline needs no
     figure. Scoped to CS1 because CS2 and CS3 map their own metrics onto the
     same grid.
     ========================================================================== */
  .case-study--cs1 .flow-inner--problem .flow-metric-cue { grid-column: 1; }
  .case-study--cs1 .flow-inner--problem .stat-item--opened { grid-column: 2; }
  .case-study--cs1 .flow-inner--problem .stat-item--applied { grid-column: 3; }
  .case-study--cs1 .flow-inner--problem .stat-item--completed { grid-column: 4; }
  .case-study--cs1 .flow-inner--problem .stat-item--success { grid-column: 5; }

  /* Leader from each drop-off metric up to its step. */
  .case-study--cs1 .flow-inner--problem .stat-item--completed::before,
  .case-study--cs1 .flow-inner--problem .stat-item--success::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 100%;
    width: 1px;
    height: 46px;
    background: var(--flow-card-negative);
  }
  .case-study--cs1 .flow-inner--problem .stat-item--completed::after,
  .case-study--cs1 .flow-inner--problem .stat-item--success::after {
    content: "";
    position: absolute;
    left: calc(50% - 4px);
    top: 1px;
    width: 7px;
    height: 7px;
    border-right: 1px solid var(--flow-card-negative);
    border-bottom: 1px solid var(--flow-card-negative);
    transform: rotate(45deg);
  }
}

@layer components {
  /* CS1's hero subhead is a full two-sentence paragraph, not the one-line
     metric the other case studies carry, so it gets the leading and the
     longer measure a paragraph needs. Scoped so the one-liners stay tight. */
  /* CS2's hero sets the scene as three pointers in the left column, with the
     project facts opposite — same two-column shape as CS1. */
  .case-study--cs2 .hero-metric { flex: 0 1 520px; }
  .case-study--cs2 .hero-context-list strong {
    font-weight: 600;
    color: var(--ink);
  }
  .case-study--cs2 .hero-context-list {
    margin: 0;
    padding-left: 1.1em;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: var(--text-md);
    line-height: 1.55;
    letter-spacing: -0.003em;
    color: var(--ink-2);
  }

  /* One paragraph per idea, so each reads on its own instead of as one block.
     Academy uses the same structure, so it takes the same rules — without them
     its paragraphs sat on a 0px gap. */
  :is(.case-study--cs1, .case-study--cs3) .hero-metric {
    display: flex;
    flex-direction: column;
    gap: 14px;
    flex: 0 1 480px;
    line-height: 1.6;
    letter-spacing: -0.003em;
  }
  :is(.case-study--cs1, .case-study--cs3) .hero-metric > p {
    margin: 0;
  }
}

@layer components {
  /* ==========================================================================
     CS1 body scale — a slight step down on desktop only (~6%), to sit under
     the reduced heading ramp. The phone ramp is left alone: :root narrows it
     under 720px on purpose, and an unscoped override here would undo that.
     ========================================================================== */
  @media (min-width: 721px) {
    .case-study--cs1 {
      --text-base: 1rem;        /* 17 → 16 */
      --text-md: 1.0625rem;     /* 18 → 17 */
      --text-lg: 1.1875rem;     /* 20 → 19 */
      --text-card: 1.1875rem;   /* 20 → 19 */
    }
  }

  /* Red rule under the phrase that names the problem, so the Core Problem
     section can be skimmed on the underlines alone. --negative-soft is the
     red this band already uses for its negative figures (the 12%), and it
     lands at ~7.3:1 on the band, so a 2px rule stays clearly visible. */
  .mark-problem {
    text-decoration-line: underline;
    text-decoration-color: var(--negative-soft);
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
    text-decoration-skip-ink: none;
  }
}

@layer components {
  /* ==========================================================================
     CS1 approach rails — the full-width divider read as a section rule rather
     than a connection, so it is replaced by an arrow: PRD points down into the
     screenshot column, and the screenshots point on to the prototypes tag. The
     rail stacks so the arrow reads vertically, and both align to the image
     column rather than the full measure.
     ========================================================================== */
  :is(.case-study--cs1, .case-study--cs2) .origin-rail {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  :is(.case-study--cs1, .case-study--cs2) .origin-rail-line { display: none; }
  :is(.case-study--cs1, .case-study--cs2) .origin-arrow {
    position: relative;
    width: 1px;
    height: 28px;
    margin-left: 34px;
    background: var(--rule-strong);
  }
  /* Chevron on the leading end, so each arrow points the way the work moves. */
  :is(.case-study--cs1, .case-study--cs2) .origin-arrow::after {
    content: "";
    position: absolute;
    left: calc(50% - 4px);
    width: 7px;
    height: 7px;
    border-right: 1px solid var(--rule-strong);
    border-bottom: 1px solid var(--rule-strong);
    transform: rotate(45deg);
  }
  :is(.case-study--cs1, .case-study--cs2) .origin-rail--start .origin-arrow::after { bottom: 0; }
  :is(.case-study--cs1, .case-study--cs2) .origin-rail--end .origin-arrow::after { bottom: 0; }
  :is(.case-study--cs1, .case-study--cs2) .origin-rail--start { margin-top: 20px; }
  :is(.case-study--cs1, .case-study--cs2) .origin-rail--end { margin-top: 4px; }
}

/* ============================================================================
   CS1 section rhythm — the desktop ramp above sets 122-144px of padding on
   every section, which reads as a lot of air now that the type ramp has come
   down. This trims it ~30% for CS1 only.

   Deliberately unlayered and last: the rules it overrides are unlayered too,
   and unlayered CSS always beats layered CSS regardless of specificity — so a
   copy of this inside @layer components would silently lose.

   Kept inside min-width 721px: under that, mobile keeps its own compact
   spacing, which was tuned separately and should not inherit this.
   ========================================================================= */
@media (min-width: 721px) {
  .case-study--cs1 .section--problem { padding-block: 84px 104px; }
  /* Same seam fix as CS1: the media caption below the hero now carries 44px of
     its own, so the band no longer needs a full 122px to separate from it. */
  .case-study--cs2 .section--problem { padding-block: 100px 122px; }
  .case-study--cs1 .section--origin,
  .case-study--cs1 .section--flow { padding-top: 112px; }
  .case-study--cs1 .section--feature { padding-top: 124px; }
  .case-study--cs1 #feature-02 { padding-bottom: 104px; }
  .case-study--cs1 #feature-03 { padding-bottom: 124px; }
  .case-study--cs1 .section--impact,
  .case-study--cs1 .section--takeaways,
  .case-study--cs1 .section--footer { padding-top: 124px; }
}

@layer components {
}

@layer components {
  /* Feature 01's intro is now a tight two-column pair, so the 96px of air
     above "What shipped" reads as a hole. Layered on purpose: the unlayered
     .shipped-block rule in the max-width:720px block still wins on phones,
     which keeps their 64px untouched. */
  #feature-01 .shipped-block,
  #feature-02 .shipped-block,
  #feature-03 .shipped-block { padding-top: 40px; }
  .case-study--cs1 #feature-01 .shipped-block,
  .case-study--cs1 #feature-02 .shipped-block,
  .case-study--cs1 #feature-03 .shipped-block { padding-top: 60px; }
  /* A stack-block that opens with a shipped heading is a new feature, so it
     gets CS1's 60px. The rest stay tight — they are continuations. */
  .case-study--cs2 .stack-block { padding-top: 12px; }
  .case-study--cs2 .stack-block:has(> .final-design-head),
  .case-study--cs2 .stack-block:has(> .shipped-copy) { padding-top: 60px; }
}

/* ============================================================================
   CS1 Feature 01 intro — a statement-length title paired with the user-story
   note, side by side so the pair reads as one unit instead of stacking with
   dead space between them.

   Deliberately unlayered: the .user-story rules it has to beat (gap: 48px,
   padding-block: 44px) live unlayered in the min-width:721px block above, and
   unlayered CSS wins over anything in @layer regardless of specificity. A copy
   of this inside @layer components would silently lose on those two props.
   ========================================================================= */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .feature-intro-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
  align-items: start;
  gap: clamp(28px, 3.5vw, 56px);
}

/* The feature's own title outranks the problem statement above it. The problem
   is the setup; the named solution is what the section is about. Stepped down
   from 34px: at that size the two lines of the title, the pill and the summary
   ran together as one dense block. */
/* NB the --sans suffix is now historical: the type ramp sets every h3 in the
   display face, so this title is Fraunces despite the name. Size and face come
   from the h3 step; only the measure and leading are local. */
:is(.case-study--cs1, .case-study--cs2) .final-design-title--sans {
  line-height: 1.32;
}

/* The problem is a plain sentence a reader takes in at a glance, so it is set
   in the body face at reading size rather than as a display statement. Fraunces
   at 28px made a two-line setup look like the section's headline. */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .feature-title--statement {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1.0625rem, 1.4vw, 1.25rem);
  line-height: 1.55;
  letter-spacing: 0;
  max-width: 46ch;
  color: var(--ink-2);
}

/* The shipped block was cramped: title, pill and summary sat on 10px gaps
   under a 20px head margin. Opened up so each part reads as its own line. */
:is(.case-study--cs1, .case-study--cs2) .final-design-head {
  margin-bottom: 26px;
}
:is(.case-study--cs1, .case-study--cs2) .shipped-copy--tight {
  gap: 20px;
}
/* Size comes from the body step; only the leading is local. */
:is(.case-study--cs1, .case-study--cs2) .final-design-summary .prose {
  line-height: 1.6;
}

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .user-story--note {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  width: 100%;
  margin: 0;
  padding: 16px 18px 18px;
  border: 0;
  border-radius: 6px;
  position: relative;
  background-color: #faf7ed;
  background-image:
    linear-gradient(to right, transparent 0 12px, rgb(176 84 82 / 0.16) 12px 13px, transparent 13px),
    repeating-linear-gradient(to bottom, transparent 0 21px, rgb(53 80 140 / 0.17) 21px 22px),
    radial-gradient(circle at 22% 31%, rgb(108 105 102 / 0.08) 0 0.55px, transparent 0.8px);
  background-size: auto, auto, 13px 15px;
  box-shadow: 0 5px 12px rgb(28 27 25 / 0.065), 0 1px 2px rgb(28 27 25 / 0.045);
  transform: rotate(1.1deg);
}
/* Strip of tape across the top edge. */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .user-story--note::after {
  content: "";
  position: absolute;
  top: -11px;
  left: 50%;
  width: 88px;
  height: 24px;
  background: rgb(225 215 190 / 0.72);
  box-shadow: 0 2px 4px rgb(28 27 25 / 0.08);
  clip-path: polygon(2% 8%, 98% 0, 96% 92%, 1% 100%);
  transform: translateX(-50%) rotate(-2deg);
  pointer-events: none;
}
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .user-story--note .user-story-label {
  flex: none;
  margin: 0;
  color: var(--muted);
}
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .user-story--note .user-story-body {
  flex: none;
  margin: 0;
  max-width: none;
  font-family: var(--font-body);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: var(--ink);
}

/* The quote is an aside on a note, not body copy, so it sits on the caption
   rung. Scoped to every case study that has one, Academy's included.

   The tag+class and :not() are for weight, not decoration: the ramp's body rule
   is (0,2,1), so a plain `.user-story-body` at (0,2,0) would lose to it. This
   is (0,3,1). */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  p.user-story-body:not(:is(.cap, .micro)) {
  font-family: var(--font-body);
  font-size: var(--cs-cap);
  line-height: 1.5;
}

@media (max-width: 720px) {
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .feature-intro-split {
    grid-template-columns: minmax(0, 1fr);
  }
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .feature-title--statement { max-width: none; }
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3) .user-story--note { transform: none; }
}

/* On CS1 and CS2 the note sits in a two-column split, so the column caps it at
   ~320px. Academy's sits alone in the wrap, where the shared `width: 100%`
   above stretched it to the full 992px and it read as a banner rather than a
   note. Declared after that rule, since both are (0,2,0). */
/* Only when the note stands alone in the wrap. Inside .feature-intro-split the
   grid column already sizes it, and a cap here would leave it short of its
   own column. */
.case-study--cs3 .wrap > .user-story--note {
  width: min(24rem, 100%);
}

@layer components {
}

@layer components {
  /* ==========================================================================
     Feedback-round additions: a hypothesis block, two stat footnotes and a
     limitations list. All set as short lines or labelled rows rather than
     paragraphs, so each one can be read at a glance.
     ========================================================================== */

  /* Hypothesis sits on the dark problem band. */
  .hypothesis {
    margin-top: 44px;
    padding-top: 40px;
    border-top: 1px solid var(--band-rule);
  }
  .hypothesis .eyebrow { color: var(--band-ink-2); }
  .hypothesis-line {
    margin: 10px 0 0;
    max-width: 68ch;
    font-size: var(--text-md);
    line-height: 1.6;
    color: var(--band-ink);
  }
  .hypothesis-metrics {
    display: flex;
    flex-wrap: wrap;
    gap: 14px 56px;
    margin: 24px 0 0;
  }
  .hypothesis-metrics > div {
    display: flex;
    align-items: baseline;
    gap: 10px;
  }
  .hypothesis-metrics dt {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--band-muted);
  }
  .hypothesis-metrics dd {
    margin: 0;
    font-size: var(--text-base);
    color: var(--band-ink);
  }

  /* A caveat that belongs to one bullet, set under it rather than after the
     list, so it cannot read as a separate point. */
  .li-note {
    display: block;
    margin-top: 5px;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--muted);
  }

  /* Blue rule under the phrase that carries the finding — the counterpart to
     .mark-problem, which marks problems in red on the dark band. */
  .mark-key {
    text-decoration-line: underline;
    text-decoration-color: var(--accent);
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
    text-decoration-skip-ink: none;
  }

  /* A note qualifying a bullet directly above it. */
  .point-foot {
    margin: 12px 0 0;
    max-width: 60ch;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--muted);
  }

  /* Footnotes that qualify a figure: whose numbers these are. */
  .flow-foot {
    margin: 22px 0 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--band-ink-2);
  }
  .stat-foot {
    margin: 14px 0 0;
    max-width: 62ch;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--ink-2);
  }

  /* Limitations: one claim per line, so what the study does not cover is as
     scannable as what it does. */
  .limitations {
    margin-top: 56px;
    padding-top: 40px;
    border-top: 1px solid var(--rule);
  }
  .limitations-list {
    margin: 12px 0 0;
    padding-left: 1.2em;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 68ch;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }
}

@layer components {
  /* Evidence -> decision, as a table: two short columns read faster than the
     same pairs written out as prose. */
  .evidence-table {
    width: 100%;
    margin-top: 48px;
    border-collapse: collapse;
    text-align: left;
    font-size: var(--text-base);
    line-height: 1.5;
  }
  .evidence-table th {
    padding: 0 24px 10px 0;
    border-bottom: 1px solid var(--rule-strong);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }
  .evidence-table td {
    padding: 14px 24px 14px 0;
    border-bottom: 1px solid var(--rule);
    color: var(--ink-2);
    vertical-align: top;
  }
  .evidence-table tbody tr:last-child td { border-bottom: 0; }
  .evidence-table td:last-child { color: var(--ink); }
  .evidence-ref {
    display: inline-block;
    margin-left: 8px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }
  @media (max-width: 720px) {
    .evidence-table, .evidence-table tbody, .evidence-table tr, .evidence-table td { display: block; width: 100%; }
    .evidence-table thead { display: none; }
    .evidence-table tr { padding-block: 14px; border-bottom: 1px solid var(--rule); }
    .evidence-table tr:last-child { border-bottom: 0; }
    .evidence-table td { padding: 0; border-bottom: 0; }
    .evidence-table td:first-child { color: var(--ink); font-weight: 600; }
    .evidence-table td:last-child { color: var(--ink-2); margin-top: 4px; }
  }

  /* A short note on the road not taken, under a Why block. */
  .why-alts {
    margin: 20px 0 0;
    max-width: 72ch;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }
}

@layer components {
  /* An edge-case behaviour stated as a labelled line, so the state and what
     happens in it read as one unit rather than buried in a paragraph. */
  .state-note {
    margin-top: 14px;
    max-width: 62ch;
  }
  .state-note-label {
    display: block;
    margin-bottom: 2px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
  }
}

@layer components {
  /* The two failure states, side by side: each one is a screenshot plus a
     two-line note, so the state and its way out read together. */
  .edge-pair {
    margin-top: 60px;
    padding-top: 40px;
    border-top: 1px solid var(--rule);
  }
  .edge-pair-label { color: var(--muted); }
  .edge-pair-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(24px, 3vw, 44px);
    margin-top: 18px;
  }
  .edge-case { margin: 0; }
  .edge-case > img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    border-radius: 6px;
  }
  .edge-case > figcaption {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 12px;
  }
  .edge-case-title {
    font-size: var(--text-base);
    font-weight: 600;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .edge-case-body {
    font-size: var(--text-sm);
    line-height: 1.55;
    color: var(--ink-2);
  }

  @media (max-width: 720px) {
    .edge-pair-grid { grid-template-columns: minmax(0, 1fr); }
  }
}

@layer components {
  /* Constraints: three tinted cards, graded light-to-deep across the row, each
     opening with a line icon. Was three bare text columns under an eyebrow,
     which read as a footnote to the section above it rather than a section of
     its own. Every value is a mix of the case study's own accent into the page
     ground, so the block inherits the theme rather than introducing a palette,
     and it inverts correctly in dark without a second set of rules. */
  .section--constraints { padding-block: 104px 0; }

  /* One heading in one face. The section name used to sit in a pill, then in a
     mono kicker beside the title; both put two typefaces on one line for a
     label the sentence can carry itself. */
  .constraints-title {
    margin: 0;
    max-width: 30ch;
    color: var(--ink);
  }

  .constraints-list {
    display: grid;
    /* Three constraints, one row: the set reads as a single glance rather than
       a 2x2 grid with a hole in it. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(14px, 1.6vw, 22px);
    margin: 44px 0 0;
  }
  .constraint {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 28px 26px 30px;
    border-radius: 6px;
    /* One fill for all three, not a grade: the three constraints are peers, and
       stepping the tint across them implied a sequence or a ranking that isn't
       there. A 5% wash of the accent is enough to lift the card off the paper
       without reading as a blue card, and it inverts on its own in dark
       because both tokens flip. */
    background: var(--surface);
    background: color-mix(in oklab, var(--accent) 5%, var(--bg));
  }

  .constraint-icon {
    width: 26px;
    height: 26px;
    /* One trace of the theme, at the same strength as the hero fact labels —
       a slate that reads as tinted grey rather than as blue. */
    color: var(--muted);
    color: color-mix(in oklab, var(--accent) 45%, var(--muted));
  }
  .constraint dt {
    font-size: clamp(1.0625rem, 1.35vw, 1.25rem);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: var(--tracking-tight);
    color: var(--ink);
  }
  .constraint dd {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--ink-2);
  }

  @media (max-width: 720px) {
    .section--constraints { padding-block: 56px 0; }
    .constraints-list {
      grid-template-columns: minmax(0, 1fr);
      gap: 12px;
      margin-top: 30px;
    }
    .constraint { padding: 22px 20px 24px; gap: 12px; }
    .constraints-title { max-width: none; }
  }
}

@layer components {
  /* Signposts that name why a section appears where it does. */
  .section-signpost {
    display: block;
    margin-bottom: 6px;
    color: var(--accent);
  }

  /* A tag marking screenshots as the pre-redesign UI. */
  .cards-tag { display: block; margin-bottom: 2px; color: var(--band-muted); }

  /* One clarifying line under the problem headline. */
  .problem-clarify {
    margin: 14px 0 0;
    max-width: 70ch;
    font-size: var(--text-md);
    line-height: 1.65;
    color: var(--band-ink);
  }

  /* Ant Design, as a single note rather than a panel. */
  .ant-note {
    margin: 22px 0 0;
    max-width: 72ch;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--ink-2);
  }

  /* What the data implied, kept visually distinct from what it showed. */
  .origin-points--inference {
    margin-top: 14px;
  }
  .origin-points--inference em { font-style: normal; font-weight: 600; color: var(--ink); }

  /* Before / after: two shots side by side, one line each. */
  .section--beforeafter { padding-block: 124px 0; }
  .ba-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(20px, 3vw, 40px);
    margin-top: 18px;
  }
  .ba-shot { margin: 0; }
  .ba-shot > img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    border-radius: 6px;
  }
  .ba-shot > figcaption {
    margin-top: 10px;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--ink-2);
  }
  @media (max-width: 720px) {
    .section--beforeafter { padding-block: 64px 0; }
    .ba-grid { grid-template-columns: minmax(0, 1fr); }
  }
}

/* ==========================================================================
   CASE-STUDY TYPE RAMP — the single authoritative scale
   --------------------------------------------------------------------------
   Before this block the five case studies had drifted badly: h1 rendered at
   47px on CS1/CS2 and 63px elsewhere, h2 came in eight sizes across two
   typefaces (including Inter 20px — a heading set in the body face), h3 in
   five, body copy at 16/17/18/19/20 and captions at 15/16/18.

   One size per level, one face per role, every case study.

   Deliberately UNLAYERED: unlayered CSS beats layered CSS regardless of
   specificity, and every rule this replaces lives in @layer components — in a
   layer the ramp would silently lose to the declarations it is meant to fix.

   SPECIFICITY, since it matters here: each rule below is (0,3,0) or (0,2,1) and
   they are ordered body -> caption -> hand, so the later, more specific rules
   win for the elements that carry two roles (p.prose.state-note is a caption,
   not prose). The :not(:is(...)) clauses are meaningful as well as load-bearing:
   a caption is never also a mono label.

   Left alone: mono labels (.eyebrow, .micro, .pill, .chip-tag), which are their
   own system, and the Caveat hand notes, which are a voice rather than a size.
   ========================================================================== */

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) {
  --cs-h1: clamp(2.0625rem, 3.26vw, 2.9375rem); /* 33 -> 47 */
  --cs-h2: clamp(1.625rem, 2.3vw, 2.0625rem);   /* 26 -> 33 */
  --cs-h3: clamp(1.25rem, 1.7vw, 1.5rem);       /* 20 -> 24 */
  --cs-h4: 1.0625rem;                           /* 17 — small bold label */
  --cs-body: 1.0625rem;                         /* 17 */
  --cs-cap: 0.9375rem;                          /* 15 */
  --cs-hand: 1.125rem;                          /* 18 */
  --cs-lh-head: 1.16;
  --cs-lh-body: 1.6;
}

@media (max-width: 720px) {
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) {
    --cs-h4: 1rem;      /* 16 */
    --cs-body: 1rem;    /* 16 */
    --cs-cap: 0.875rem; /* 14 */
    --cs-hand: 1.0625rem; /* 17 */
  }
}

/* --- Headings: display face, one size per level ------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  :is(h1, h2, h3) {
  font-family: var(--font-display);
  /* One weight for every heading. The default UA bold (700) and the various
     600s left the same level rendering at three different weights depending on
     which class happened to be on it. Fraunces at 400 is the display voice the
     .display headings already used. Deliberate exceptions stay possible with a
     more specific rule — the shipped title's name span and the explore-next
     card title both do this. */
  font-weight: 400;
  line-height: var(--cs-lh-head);
  letter-spacing: var(--tracking-display);
}

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) h1 { font-size: var(--cs-h1); }
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) h2 { font-size: var(--cs-h2); }
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) h3 { font-size: var(--cs-h3); }

/* h4 is the one heading level that stays in the body face: it labels a part of
   a section rather than heading one, and a serif at 17px reads as body copy. */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) h4 {
  font-family: var(--font-body);
  font-size: var(--cs-h4);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: var(--tracking-tight);
}

/* --- Body copy and lists — one size ------------------------------------ */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  :is(p, li):not(:is(.eyebrow, .micro, .pill, .chip-tag)) {
  font-family: var(--font-body);
  font-size: var(--cs-body);
  line-height: var(--cs-lh-body);
}

/* --- Captions — one step below body, same face ------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  :is(figcaption, .cap, .shot-caption, .figure-note, .film-caption, .li-note,
      .ab-note, .seq-note, .proto-static-note, .version-note-body, .state-note,
      .point-foot, .stat-foot, .net-label):not(:is(.eyebrow, .micro)) {
  font-family: var(--font-body);
  font-size: var(--cs-cap);
  line-height: 1.5;
}

/* --- The hand notes are a voice, not a size ---------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  :is(.user-story--note, .hand, .iteration-cue, .flow-metric-cue):not(:is(.eyebrow, .micro)) {
  font-family: var(--font-hand);
  font-size: var(--cs-hand);
  line-height: 1.45;
}

/* --------------------------------------------------------------------------
   .story-head — a section title that replaced a mono eyebrow.

   Several blocks (the current flow, the hypothesis, the survey, before/after,
   the limitations) were labelled with an eyebrow and no heading, so the page
   read as a set of tagged panels rather than a story. Each label is now a
   sentence at the h2 step; this class only supplies the spacing and measure
   the eyebrow used to occupy. Unlayered, like the ramp above it.
   -------------------------------------------------------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .story-head {
  margin: 0 0 20px;
  color: inherit;
  text-wrap: pretty;
}

/* On any inverted band the heading takes the band's ink, not the page's.
   Scoped to .section--problem alone, the survey heading on the impact band came
   out dark-on-dark at 1:1 — invisible. Every dark ground is listed here, so a
   .story-head cannot be dropped onto one and vanish. */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  :is(.section--problem, .impact-band, .note-band, .explore-band, .section--principle) .story-head {
  color: var(--band-ink);
}

@media (max-width: 720px) {
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .story-head {
    margin-bottom: 14px;
  }
}

/* --------------------------------------------------------------------------
   The shipped heading is two lines: the named solution, then what it does.
   The name carries the weight, the second line explains it a step quieter, so
   the pair reads as one heading rather than two competing ones. No colon
   between them — the line break does that work.
   -------------------------------------------------------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .final-design-title--name {
  font-weight: 600;
  color: var(--ink);
}

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .final-design-title--what {
  font-weight: 400;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   Hand-drawn cue arrow, the same one the homepage scribble uses. A text
   glyph sits on the baseline at whatever angle the font gives it, so it never
   quite aimed at the thing it was pointing to; this curves out of the last
   word instead. currentColor, so it can never drift from the handwriting.
   -------------------------------------------------------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .cue-arrow {
  display: block;
  width: 2.1rem;
  height: auto;
  margin: 0.1rem 0.6rem 0 auto;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .cue-arrow-tail {
  stroke-width: 2.2;
  opacity: 0.9;
}

@media (max-width: 720px) {
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) .cue-arrow {
    width: 1.6rem;
    margin-inline: 0.4rem auto;
  }
}

/* --------------------------------------------------------------------------
   A full-width figure that follows a content block needs a gap of its own.

   On the AI entries a standalone figure sits after a beat pair or after
   another figure's note, and with no margin it landed 0-2px under the block
   above it — a 992px screenshot butting straight into a 488px one reads as a
   broken layout rather than two figures. A figure directly under its section
   heading keeps its tighter spacing, which is why the heads are excluded.
   -------------------------------------------------------------------------- */
:is(.case-study--ai) .section--feature > .wrap
  > :not(.feature-head, .section-head) + .figure {
  margin-top: 72px;
}

@media (max-width: 720px) {
  :is(.case-study--ai) .section--feature > .wrap
    > :not(.feature-head, .section-head) + .figure {
    margin-top: 44px;
  }
}

/* --------------------------------------------------------------------------
   Extra air between sections.

   Each section already carries its own padding-top (84–144px depending on the
   section), and those values differ deliberately — a feature needs more room
   above it than a constraints list. Rather than flatten that rhythm to one
   number, this adds a constant on top of it, so the relative differences
   survive and one token controls the whole page.

   margin, not padding: only .section--problem and CS1's feature-03 carry a
   background, and growing their padding would stretch the band itself rather
   than the space before it.
   -------------------------------------------------------------------------- */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) {
  --cs-section-gap: 80px;
}

@media (max-width: 720px) {
  :is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai) {
    --cs-section-gap: 40px;
  }
}

:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  .section + .section {
  margin-top: var(--cs-section-gap);
}

/* The explore-next card sits outside <main>, so its previous sibling is <main>
   rather than a section — the adjacent-sibling rule above skipped it and that
   one boundary stayed at its old spacing. */
:is(.case-study--cs1, .case-study--cs2, .case-study--cs3, .case-study--ai)
  main + .section {
  margin-top: var(--cs-section-gap);
}
