/* ==========================================================================
   whylearn.ai
   White ground. Brand colour sampled from the logo, then darkened for any
   token that carries text. The raw brand hues fail AA on white and are
   graphic-only. Square corners, hairlines, no shadows.
   ========================================================================== */

/* ------------------------------------------------------------------ fonts - */
/* Self-hosted. This site makes no third-party requests at all, which is a
   claim a firm that builds AI on other people's data ought to make about itself. */

@font-face {
  font-family: "Schibsted Grotesk";
  src: url("/assets/fonts/schibsted-grotesk-var.woff2") format("woff2-variations");
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/fonts/plex-sans-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/fonts/plex-sans-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/fonts/plex-sans-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
/* ----------------------------------------------------------------- tokens - */

:root {
  color-scheme: light;

  /* Grounds */
  --ground:  #FFFFFF;
  --mist:    #F4F8FA;
  --tint:    #EDF6F5;
  --deep:    #0F3D4A;
  --deep-2:  #14486B;

  /* Ink. Ratios on white: 15.6 / 11.8 / 7.4 / 5.6 */
  --ink:     #0B2730;
  --ink-700: #0F3D4A;
  --ink-500: #3A5A64;
  --ink-400: #4E6C75;

  /* Brand, darkened until it may legally carry text (all >= 4.8:1 on white) */
  --blue:    #2078AA;
  --teal:    #12736F;
  --green:   #4C7F1C;
  --jade:    #258154;

  /* Brand, exactly as sampled from the logo. Graphics only, never type. */
  --g-green: #9BCD6C;
  --g-teal:  #25B9B6;
  --g-blue:  #2896D3;

  /* Rules */
  --hair: #DCE6EA;   /* decorative separation only */
  --edge: #4E6C75;   /* form borders and anything needing 3:1 */

  --link: #2078AA;

  --display: "Schibsted Grotesk", "Helvetica Neue", Arial, sans-serif;
  --sans:    "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Consolas, monospace;

  --shell: 1180px;

  /* Masthead geometry. --mast is the total occupied height including the
     hairline, and is the single source of truth for the anchor offsets below.
     Redefining a row token in a media query re-derives both automatically. */
  --mast-rule:  2px;    /* the brand gradient hairline under the bar */
  --mast-edge:  1px;    /* the masthead border-bottom */
  /* rem, not px. Everything inside the bar is rem, so a px token describes a
     box it cannot track: at a 32px root the bar measured 122px while --mast
     still said 103px, and every offset derived from it was wrong by 19px.
     4.5rem is exactly 72px at the default size, so nothing moves today. */
  --mast-row-a: 4.5rem; /* identity row, and the whole bar on wide screens */
  --mast-row-b: 0rem;   /* navigation row, none on wide screens */
  --gutter:     32px;   /* shell and masthead inline padding */
  --mast-gap:   16px;   /* breathing room above a scrolled-to anchor */

  --mast-bar:    calc(var(--mast-row-a) + var(--mast-row-b));
  --mast:        calc(var(--mast-bar) + var(--mast-edge) + var(--mast-rule));
  --mast-anchor: calc(var(--mast) + var(--mast-gap));
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: var(--mast-anchor); }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(1rem, 0.965rem + 0.16vw, 1.0625rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* ------------------------------------------------------------------- a11y - */

/* Nothing here: html's scroll-padding-top already offsets every anchor, and
   repeating it on the target adds the two together. See .has-onpage below for
   the one case that needs more. */

.skip {
  position: absolute; top: 0; left: 0; z-index: 100;
  transform: translateY(-120%);
  background: var(--ink); color: #fff; padding: .8rem 1.25rem;
  font: 600 .9375rem/1 var(--sans);
}
.skip:focus { transform: translateY(0); }

/* Focused programmatically by the skip link; it should not draw a ring. */
main:focus { outline: none; }

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

/* A system outline colour survives Windows High Contrast; ours would not.
   This has to come AFTER the rule above: same specificity, and the `outline`
   shorthand there resets outline-color, so declared first it did nothing. */
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
}

.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------- typography - */

h1, h2, h3, h4 { margin: 0; font-family: var(--display); font-weight: 500; text-wrap: balance; }

.d-xl { font-size: clamp(2.6rem, 1.55rem + 4.2vw, 4.6rem); line-height: 1.02; letter-spacing: -.03em; }
.d-l  { font-size: clamp(2.15rem, 1.6rem + 2.3vw, 3.35rem); line-height: 1.06; letter-spacing: -.024em; }
.d-m  { font-size: clamp(1.7rem, 1.35rem + 1.45vw, 2.4rem);  line-height: 1.12; letter-spacing: -.02em; }
.d-s  { font-size: clamp(1.25rem, 1.14rem + .45vw, 1.5rem);  line-height: 1.25; letter-spacing: -.012em; font-weight: 600; }

h3, .h-s {
  font-family: var(--sans); font-size: 1.125rem; font-weight: 600;
  line-height: 1.35; letter-spacing: -.006em;
}

/* Section label. Sentence case in the body sans, deliberately not a tracked-out
   monospace eyebrow, and never numbered: these sections are not a sequence. */
.label {
  margin: 0 0 .9rem;
  font-size: .875rem; font-weight: 600; letter-spacing: .01em;
  color: var(--teal);
}

.lede {
  font-size: clamp(1.1875rem, 1.06rem + .55vw, 1.4375rem);
  line-height: 1.5; letter-spacing: -.006em;
  color: var(--ink-500);
  max-width: 54ch;
}

p { margin: 0 0 1.1em; max-width: 68ch; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: .18em; }
a:hover { color: var(--ink-700); }

strong { font-weight: 600; }
em { font-style: italic; }

ul, ol { margin: 0 0 1.1em; padding-left: 1.2em; max-width: 68ch; }
li { margin-bottom: .45em; }
li::marker { color: var(--ink-400); }

/* Monospace earns its place only where the content is genuinely a string from
   a record: a company number, a retention period, a metric definition. */
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.num  { font-variant-numeric: tabular-nums lining-nums; }

/* -------------------------------------------------------------- structure - */

.shell { max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); }

.band { padding-block: clamp(64px, 8vw, 112px); }
.band-mist { background: var(--mist); }
.band-tint { background: var(--tint); }
.band + .band:not(.band-mist):not(.band-tint):not(.band-deep) { border-top: 1px solid var(--hair); }

.band-deep {
  background: linear-gradient(160deg, var(--deep) 0%, var(--deep-2) 100%);
  color: #fff;
}
.band-deep h1, .band-deep h2, .band-deep h3 { color: #fff; }
.band-deep p { color: #CFE3E4; }
.band-deep .label { color: #7ED0C6; }
.band-deep a { color: #fff; }
.band-deep :focus-visible { outline-color: #fff; }

.measure { max-width: 68ch; }
.narrow  { max-width: 54ch; }

.cols { display: grid; gap: 40px; }
.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* Hero: statement left, the four disciplines threaded on the brand gradient right. */
.herogrid { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); gap: 72px; align-items: start; }

.disciplines { margin: .6rem 0 0; padding: 0 0 0 28px; list-style: none; position: relative; max-width: none; }
.disciplines::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  background: linear-gradient(180deg, var(--g-green) 0%, var(--g-teal) 50%, var(--g-blue) 100%);
}
.disciplines li { margin: 0 0 1.5rem; }
.disciplines li:last-child { margin-bottom: 0; }
.disciplines b { display: block; font-weight: 600; font-size: 1.0625rem; color: var(--ink); }
.disciplines span { display: block; font-size: .9375rem; line-height: 1.5; color: var(--ink-500); margin-top: .15rem; }

/* Heading holds the left, content runs the right. */
.split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 64px; align-items: start; }

/* --------------------------------------------------------------- masthead - */

.masthead { position: sticky; top: 0; z-index: 40; background: var(--ground); border-bottom: 1px solid var(--hair); }

/* The logo gradient, as a single hairline. The only place the three brand hues
   appear together, and the only gradient on the site besides the mark itself. */
.masthead::after {
  content: ""; display: block; height: var(--mast-rule);
  background: linear-gradient(90deg, var(--g-green) 0%, var(--g-teal) 52%, var(--g-blue) 100%);
}

.masthead-in {
  max-width: var(--shell); margin-inline: auto;
  padding-inline: var(--gutter);
  min-height: var(--mast-bar);
  display: flex; align-items: center; gap: 36px;
  /* Wraps on content rather than on a breakpoint, so it answers text size as
     well as viewport width. At 200% text between roughly 701px and 1105px the
     row overflowed the page and took "Contact" off screen with it. */
  flex-wrap: wrap;
}

.wordmark {
  display: inline-flex; align-items: center; gap: .55rem;
  min-height: var(--mast-row-a);
  text-decoration: none; color: var(--ink);
}
.wordmark img { width: 30px; height: 30px; display: block; }
.wordmark span { font-family: var(--display); font-weight: 600; font-size: 1.2rem; letter-spacing: -.022em; }
.wordmark .tld { font-weight: 400; }
.wordmark:hover { color: var(--ink); }

.nav { margin-left: auto; display: flex; align-items: center; gap: 30px; min-width: 0; }
/* 44px tall at every width, comfortably past the 24px floor of 2.5.8. The
   2px underline is always present and only its colour changes, so the current
   page is marked by more than hue alone. */
.nav a {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 12px 0 10px;
  font-size: .9375rem; font-weight: 500; line-height: 1.4;
  color: var(--ink-500); text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--ink); }
.nav a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--g-blue); }

/* There is no menu button. Nothing here needs a click, a hover or a script
   to be legible, which is also why the site works with JavaScript off. */

/* ---------------------------------------------------------------- actions - */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font: 500 .9375rem/1 var(--sans);
  min-height: 48px; padding: .9rem 1.5rem;
  border-radius: 0; text-decoration: none;
  border: 1px solid var(--deep); background: var(--deep); color: #fff;
  transition: background .15s ease, border-color .15s ease;
}
.btn:hover { background: var(--ink); border-color: var(--ink); color: #fff; }
.btn-ghost { background: transparent; color: var(--ink-700); border-color: var(--edge); }
.btn-ghost:hover { background: var(--mist); color: var(--ink); border-color: var(--ink); }
.band-deep .btn { background: #fff; border-color: #fff; color: var(--deep); }
.band-deep .btn:hover { background: #CFE3E4; border-color: #CFE3E4; color: var(--deep); }
.band-deep .btn-ghost { background: transparent; color: #fff; border-color: #A8C4CC; }
.band-deep .btn-ghost:hover { background: rgba(255,255,255,.12); color: #fff; border-color: #fff; }

.actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 2.2rem; }

p a, dd a, li a { padding-block: 6px; margin-block: -6px; }

.arrow { font-weight: 500; text-decoration: none; border-bottom: 1px solid var(--hair); padding-bottom: 3px; }
.arrow:hover { border-bottom-color: var(--link); }
.arrow::after { content: " \2192"; }

/* ------------------------------------------------------------------ cards - */
/* Bordered rectangles. No fills, no shadows, no coloured edge bars, no radius. */

.card { border: 1px solid var(--hair); padding: 32px 28px; background: var(--ground); }
.card h2, .card h3 { margin-bottom: .6rem; }
.card p  { color: var(--ink-500); font-size: 1rem; margin-bottom: 0; }

/* The entry point. Distinguished by shape and border weight, not by colour. */
.entry {
  border: 1px solid var(--edge); background: var(--ground);
  padding: 34px 30px; margin-bottom: 40px;
  display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: 44px;
  align-items: start;
}
.entry .stage { font-size: .875rem; font-weight: 600; color: var(--teal); margin: 0 0 .4rem; }
.entry p:not(.stage) { color: var(--ink-500); }

/* A run of things where the order carries meaning. */
.rungs { border-top: 1px solid var(--hair); }
.rung {
  display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: 48px; padding: 40px 0; border-bottom: 1px solid var(--hair);
}
.rung h2, .rung h3 { margin-bottom: .5rem; }
.rung .stage { font-size: .875rem; font-weight: 600; color: var(--teal); margin: 0 0 .4rem; }
.rung p { color: var(--ink-500); }
.rung ul { color: var(--ink-500); font-size: 1rem; margin-bottom: 0; }

/* A situation quoted in the buyer's own words. */
.said {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.2rem, 1rem + .8vw, 1.5rem);
  line-height: 1.25; letter-spacing: -.018em;
  color: var(--ink); margin: 0 0 .7rem; max-width: none;
}
/* Named products at a technology layer. */
.egs { font-size: .875rem; line-height: 1.6; color: var(--ink-500); margin: .7rem 0 0; }

.deflist { margin: 0; border-top: 1px solid var(--hair); }
.deflist > div {
  display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: 48px; padding: 24px 0; border-bottom: 1px solid var(--hair);
}
.deflist dt { font-weight: 600; }
.deflist dd { margin: 0; color: var(--ink-500); }

.pull {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.1rem);
  line-height: 1.25; letter-spacing: -.02em;
  max-width: 24ch; margin: 0;
}

/* ------------------------------------------------------------------ prose - */

.prose h2 { font-size: clamp(1.5rem, 1.2rem + 1.1vw, 1.95rem); line-height: 1.15; letter-spacing: -.02em; margin: 2.8rem 0 .9rem; }
.prose h3 { margin: 2rem 0 .55rem; }
.prose > p:first-of-type { font-size: 1.15rem; line-height: 1.6; color: var(--ink-500); }

figure { margin: 2.4rem 0; max-width: none; }
figcaption { font-size: .9375rem; line-height: 1.6; color: var(--ink-500); margin-top: 1rem; max-width: 68ch; }

.scroller { overflow-x: auto; min-width: 0; max-width: 100%; }
.scroller:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* ------------------------------------------------------------------ forms - */

.field { display: block; margin-bottom: 1.3rem; max-width: 36rem; }
.field > span { display: block; font-weight: 600; font-size: .9375rem; margin-bottom: .45rem; }
.field input, .field select, .field textarea {
  width: 100%; font: 400 1rem/1.5 var(--sans); color: var(--ink);
  font-size: max(16px, 1rem);   /* below 16px, iOS zooms in on focus */
  background: var(--ground); border: 1px solid var(--edge); border-radius: 0;
  padding: .8rem .9rem; min-height: 48px;
}
.field select {
  appearance: none; padding-right: 2.75rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-500) 50%),
    linear-gradient(135deg, var(--ink-500) 50%, transparent 50%);
  background-position: calc(100% - 22px) 22px, calc(100% - 16px) 22px;
  background-size: 6px 6px; background-repeat: no-repeat;
}
.field textarea { min-height: 10rem; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--ink); }
.hint { font-size: .9375rem; color: var(--ink-500); margin-top: .4rem; max-width: 36rem; }

/* ----------------------------------------------------------------- footer - */

.footer { border-top: 1px solid var(--hair); background: var(--mist); padding-block: 64px 40px; }
.footer h2 { font-family: var(--sans); font-size: .9375rem; font-weight: 600; margin-bottom: 1rem; color: var(--ink); }
.footer ul { list-style: none; padding: 0; margin: 0; font-size: .9375rem; }
.footer li { margin-bottom: .55rem; color: var(--ink-500); }
.footer a {
  color: var(--ink-500); text-decoration: none;
  display: block; width: max-content; max-width: 100%;
  padding: 6px 0;
  /* p a, dd a, li a sets margin-block: -6px to stop the inline-link padding
     opening up prose line boxes. A footer link is an li a, and .footer a only
     overrides the padding, so that negative margin survived here and collapsed
     with the li margin below to exactly zero clear space. Reset it. */
  margin-block: 0;
}
.footer a:hover { color: var(--link); text-decoration: underline; }
.footcols { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 48px; }
.colophon {
  margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--hair);
  font-size: .875rem; line-height: 1.8; color: var(--ink-500); max-width: none;
}

/* --------------------------------------------------------------- diagram -- */

.diagram { width: 100%; height: auto; display: block; min-width: 38rem; }
.diagram .track { fill: none; stroke-width: 2.5; stroke-linecap: round; }
.diagram text { font-family: "IBM Plex Sans", sans-serif; }

/* The draw runs once, only when motion is welcome, and only after JS adds the
   class. Without JS the diagram is already complete in the markup. */
.draw .track { stroke-dasharray: var(--len); stroke-dashoffset: var(--len); animation: drawin .9s cubic-bezier(.4,0,.2,1) forwards; }
.draw .t2 { animation-delay: .14s; }
.draw .t3 { animation-delay: .28s; }
.draw .ring { stroke-dasharray: 520; stroke-dashoffset: 520; animation: drawin .8s cubic-bezier(.4,0,.2,1) .5s forwards; }
.draw .lbl { opacity: 0; animation: fadein .5s ease .95s forwards; }
@keyframes drawin { to { stroke-dashoffset: 0; } }
@keyframes fadein { to { opacity: 1; } }

/* ------------------------------------------------------------- responsive -
   Bands are set where this site's own layouts break, not at device names, and
   are ordered widest first. Real widths tuned against:
     1280 1180 1112 1024 | 834 810 768 | 430 414 393 390 375 360
   The desktop grid holds down to 1024. 768 to 1023 is the tablet band and gets
   its own layout. The phone band starts at 767, so 768 can never reach it.
   -------------------------------------------------------------------------- */

/* === 1180: shell max reached. Large tablet in landscape. Gaps only. ====== */
@media (max-width: 1180px) {
  .herogrid { gap: 56px; }
  .split    { gap: 52px; }
  .rung, .deflist > div { gap: 40px; }
  .entry    { gap: 36px; }
  .cols     { gap: 32px; }
  .footcols { gap: 40px; }
}

/* === 1023: TABLET BAND. 834 / 810 / 768 portrait. ======================== */
@media (max-width: 1023px) {
  :root { --gutter: 28px; }

  /* 8vw stays continuous with the desktop band at 1023, but the raised floor
     stops a 768 tablet inheriting the same 64px that a 375 phone gets. */
  .band   { padding-block: clamp(72px, 8vw, 88px); }
  .footer { padding-block: 56px 36px; }

  .masthead-in { gap: 24px; }
  .nav { gap: 24px; }

  /* The statement takes the whole measure, and the discipline rail becomes a
     block so the recovered width is spent on something. */
  .herogrid { grid-template-columns: 1fr; gap: 40px; }
  .disciplines {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 36px; row-gap: 1.5rem;
  }
  .disciplines li { margin: 0; }

  /* The hero column clips the lede to about 48ch on desktop. Once the hero
     stacks, 54ch would run to 96% of the line. Hold the desktop reading. */
  .lede { max-width: 48ch; }

  /* A section heading needs roughly 380px before it shatters, and the 5fr
     column cannot supply that until about 1200px. So it stacks. */
  .split { grid-template-columns: 1fr; gap: 20px; }

  /* The workhorse row takes a fixed label rail instead of a fraction. The
     left column's content need is constant; the right column's is whatever is
     left, capped at the 68ch measure. Prose runs 47ch at 768, 68ch by 1023. */
  .rung          { --rail: 13rem; gap: 32px; padding: 36px 0; }
  .deflist > div { --rail: 11rem; gap: 32px; padding: 22px 0; }
  .entry         { --rail: 11rem; gap: 28px; padding: 28px 24px; margin-bottom: 32px; }

  .rung, .deflist > div, .entry {
    grid-template-columns: minmax(0, var(--rail)) minmax(0, 1fr);
  }

  /* Except on the use-cases page, where the left column holds a quote of up
     to 74 characters and not a label. A 208px rail turns that into seven
     ragged lines, taller than the prose beside it. */
  .rung:has(.said) { grid-template-columns: 1fr; gap: 14px; }
  .said { max-width: 32ch; }

  /* Three cards two-up leaves an orphan with a hole beside a hairline box.
     Span the last one to close the row. */
  .cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cols-3 > :last-child { grid-column: 1 / -1; }
  .cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .card   { padding: 28px 26px; }

  .footcols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px 48px; }

  /* Full-bleeding the figure recovers two gutters, more than the shortfall,
     so the diagram stops scrolling by an annoying 30px on a portrait tablet.
     The type bump inside the viewBox keeps labels legible at the new width. */
  figure[data-draw]            { margin-inline: calc(var(--gutter) * -1); }
  figure[data-draw] .scroller  { padding-inline: 12px; }
  figure[data-draw] figcaption { padding-inline: var(--gutter); }
  .diagram { min-width: 40rem; }
  .diagram text                    { font-size: 15.5px; }
  .diagram text[font-weight="600"] { font-size: 18.5px; }
}

/* === 767: PHONE BAND. 768 can never reach this. ========================== */
@media (max-width: 767px) {
  :root { --gutter: 24px; }

  .band   { padding-block: clamp(56px, 9vw, 72px); }
  .footer { padding-block: 52px 34px; }

  .herogrid    { gap: 32px; }
  .disciplines { grid-template-columns: 1fr; row-gap: 1.4rem; }

  .split { gap: 16px; }
  .cols  { gap: 24px; }

  .rung, .deflist > div, .entry { grid-template-columns: 1fr; }
  .rung          { gap: 12px; padding: 30px 0; }
  .deflist > div { gap: 8px;  padding: 18px 0; }
  .entry         { gap: 16px; padding: 26px 22px; margin-bottom: 28px; }
  .rung:has(.said) { gap: 12px; }
  .said { max-width: none; }

  /* The headline was taking four lines and pushing the call to action past
     the fold on a 390px phone. */
  .d-xl { font-size: clamp(2.15rem, 1.6rem + 2.4vw, 2.6rem); line-height: 1.06; }
  .lede { font-size: 1.125rem; }
  .actions { margin-top: 1.8rem; }

  /* With the keyboard up a phone leaves roughly 330px of viewport, so a 160px
     textarea pushes the send button out of reach. */
  .field textarea { min-height: 8.5rem; }

  /* Footer links were 27px tall and 9px apart, under the target size floor. */
  /* display:block, not inline-block. An inline-block sits in a line box sized
     by line-height, so 10px of block padding overflowed the li rather than
     growing it: 44.8px anchors inside 32.8px rows, overlapping by 12px. The
     later link in document order won the hit test, so the bottom of every
     footer link opened the one beneath it, on all ten pages. */
  .footer ul { margin-top: 0; }
  .footer li { margin-bottom: 6px; }
  .footer a { padding: 10px 0; }
}

/* === 620: the diagram stops being legible and becomes a cropped picture. == */
/* At 390px the SVG is 608px inside a 342px box, with no visible scrollbar on
   iOS. That reads as a broken image on the page carrying the main argument.
   The caption already states the whole diagram in prose, so below this width
   the caption is promoted to being the content instead of a description. */
@media (max-width: 620px) {
  figure[data-draw] .scroller { display: none; }
  figure[data-draw] figcaption {
    margin-top: 0; font-size: 1rem; color: var(--ink-500);
    border-left: 3px solid var(--g-teal); padding-left: 18px;
  }
}

/* === 700: the wordmark and five links stop sharing a line. =============== */
/* Two rows: identity above, all five destinations below. There is no toggle,
   so every destination stays visible and nothing here needs a click or a
   script. The bar grows, and --mast plus both anchor offsets re-derive from
   these two numbers, so nothing else needs touching. */
@media (max-width: 700px) {
  /* 110px of permanently sticky chrome was 13% of a 844px phone viewport.
     48px still clears the 44px target the links carry. */
  :root { --mast-row-a: 3.25rem; --mast-row-b: 3rem; }

  .masthead-in { display: block; }
  .wordmark { display: flex; width: max-content; }

  .nav {
    margin-left: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    min-height: var(--mast-row-b);
    border-top: 1px solid var(--hair);
    justify-content: space-between;
    gap: 10px;
    /* Load-bearing, not a safety net. Measured: the row needs about 438px, so
       below that it genuinely overflows at the default text size in English,
       and the last link scrolls out of reach. The font-size drops at 380 and
       344 buy some of it back but not all. Whether to spend another row of
       sticky chrome on wrapping this is a live question, not a settled one. */
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-padding-inline: var(--gutter);
    /* Thin, quiet, and present. It only appears when the row actually
       overflows, which at default text size it does not. */
    scrollbar-width: thin;
    scrollbar-color: var(--edge) transparent;
  }
  .nav::-webkit-scrollbar { height: 3px; }
  .nav::-webkit-scrollbar-thumb { background: var(--edge); }
  .nav::-webkit-scrollbar-track { background: transparent; }
  /* space-between collapses to flex-start once the row overflows, and every
     engine but Firefox then drops the trailing padding. */
  .nav::after { content: ""; flex: 0 0 var(--gutter); }
  .nav a { flex: 0 0 auto; padding-inline: 4px; }
  /* An offset ring would be clipped by the scroll container. */
  .nav a:focus-visible { outline-offset: -2px; }
}

/* Keep content clear of a notch and of the home indicator. */
@supports (padding: max(0px)) {
  .masthead-in { padding-inline: max(var(--gutter), env(safe-area-inset-left)); }
  .footer { padding-bottom: calc(40px + env(safe-area-inset-bottom)); }
  /* The nav only goes full-bleed below 700, so only there does it need its own
     inset. Applied unconditionally it sat one whole gutter right of the content
     it is meant to align with, while the wordmark stayed flush left. */
  @media (max-width: 700px) {
    .nav { padding-inline: max(var(--gutter), env(safe-area-inset-left)); }
  }
}

/* === 560: card grids stop working two-up. =============================== */
@media (max-width: 560px) {
  :root { --gutter: 20px; }

  .cols-2, .cols-3, .cols-4 { grid-template-columns: 1fr; }
  .cols-3 > :last-child     { grid-column: auto; }
  .footcols { grid-template-columns: 1fr; gap: 32px; }

  .card     { padding: 26px 22px; }
  .entry    { padding: 24px 20px; }
  .footer   { padding-block: 48px 32px; }
  .colophon { margin-top: 32px; }

  /* Side by side, the two buttons wrapped into a stack 14px apart and the
     secondary one sat right under the primary at thumb height. */
  /* Full width leaves no horizontal escape, so this gap is the entire error
     budget between the call to action and a link away from it. */
  .actions { flex-direction: column; align-items: stretch; gap: 24px; }
  .actions .btn { width: 100%; }
  .actions .arrow { align-self: flex-start; }
}

/* === 380: small phone hygiene. ========================================== */
@media (max-width: 380px) {
  :root { --gutter: 16px; }
  .wordmark span { font-size: 1.1rem; }
  .btn { padding-inline: 1.15rem; }
  .card { padding: 22px 18px; }
  .nav a { font-size: .875rem; }
}

@media (max-width: 344px) {
  .nav a { font-size: .8125rem; letter-spacing: -.01em; }
}

/* When the bar is static it covers nothing, so the anchor offset that
   compensated for it has to collapse too, or every in-page link lands low. */
@media (max-height: 560px), (max-width: 700px) and (max-height: 640px) {
  .masthead { position: static; }
  /* The strip is pinned to --mast. With the masthead released that left it
     floating 103px down the viewport, with live text scrolling behind the gap
     above it. It has to come unstuck at the same time.
     Qualified with html.has-onpage because the base .onpage rule is declared
     further down this file, and at equal specificity that would win. */
  html.has-onpage .onpage { position: static; }
  :root { --mast-anchor: var(--mast-gap); }
  /* html.has-onpage, not :root: it has to outrank the .has-onpage rule below,
     which is declared later and would otherwise win on source order. */
  html.has-onpage { --onpage: 0px; }
}

/* ------------------------------------------------------- on-page index ---- */
/* A list of links to the sections of this page. It is a plain list of anchors
   in the markup and works with no script at all: sticky positioning, the
   highlight for the section you are reading, and the horizontal scroll on a
   phone are all enhancements over that. Nothing here is required to reach any
   section of any page. */
/* Deliberately a little larger than the strip actually measures. Overshooting
   leaves a slightly wider gap above a heading; undershooting hides the heading
   behind the strip, which is the failure that matters. Script narrows it to the
   measured height once it runs. */
.has-onpage { --onpage: 3.5rem; }

/* scroll-padding-top on the scroll container, not scroll-margin-top on targets.
   scroll-margin only applies to elements that have an id; links and buttons do
   not, so the browser scrolled them to 91px, which is inside a strip running to
   133px, and often declined to scroll at all because it judged them already in
   view. A focused control could come to rest fully hidden. */
html.has-onpage { scroll-padding-top: calc(var(--mast-anchor) + var(--onpage)); }

.onpage {
  position: sticky; top: var(--mast); z-index: 30;
  background: var(--ground);
  border-bottom: 1px solid var(--hair);
  margin-bottom: 8px;
}
.onpage ul {
  display: flex; gap: 8px; align-items: stretch;
  list-style: none; margin: 0; padding: 10px 0;
  /* ul carries max-width: 68ch for prose. Unset it, or the strip is capped at
     694px and scrolls sideways on a 1180px desktop that had room to spare. */
  max-width: none;
  overflow-x: auto; overscroll-behavior-inline: contain; scrollbar-width: thin;
  scrollbar-color: var(--edge) transparent;
  scroll-padding-inline: var(--gutter);
  -webkit-overflow-scrolling: touch;
}
.onpage li { flex: 0 0 auto; }
.onpage a {
  display: flex; align-items: center; min-height: 2rem;
  padding: 0 12px;
  font-family: var(--sans); font-size: .875rem; line-height: 1.2;
  color: var(--ink-500); text-decoration: none;
  border: 1px solid var(--hair); border-radius: 999px;
  white-space: nowrap;
  transition: border-color .15s, color .15s, background-color .15s;
}
.onpage a:hover { color: var(--ink); border-color: var(--edge); }
/* Offset outward, the ring is clipped by the scroll container, exactly as
   it was on .nav. Same fix. */
.onpage a:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
/* aria-current is set by script for the section in view. Without script no
   chip is marked, which is correct: nothing is claiming to be current. */
.onpage a[aria-current="true"] {
  color: var(--ground); background: var(--deep); border-color: var(--deep);
}
@media (max-width: 1023px) {
  .onpage ul { padding: 8px 0; }
  .onpage a  { min-height: 1.875rem; font-size: .8125rem; }
  /* No full-bleed here. .nav can do it because .masthead-in supplies a matching
     gutter to cancel against; .onpage's parent is <main>, which has none, so
     the negative margin pushed the document one gutter past the viewport and
     the page scrolled sideways. The inner .shell already sets the gutter. */
}

/* The honeypot. Taken out of the layout and off the screen rather than hidden,
   because a field with display:none is one a bot knows to leave alone. It is
   aria-hidden and not reachable by tab, so nobody using the site meets it. */
.offscreen {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  .draw .track, .draw .ring, .draw .lbl { animation: none !important; stroke-dashoffset: 0 !important; opacity: 1 !important; }
}

@media print {
  .masthead, .actions { display: none; }
  body { background: #fff; }
}
