@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');

/* ==========================================================================
   Healthcare Consultancy
   Source of truth: Figma "Healthcare Consultancy (Copy)" (o1bdjtU2APFErBtSL0r55r)

   The Figma file is a single 1440px artboard per page with everything absolutely
   positioned. This stylesheet rebuilds it as flow layout: the design's px values
   are preserved as the *upper* end of fluid clamp() ranges, so 1440px renders as
   drawn and everything below it scales rather than breaking.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand — verified against the Figma Style Guide artboard */
  --primary: #33285d;   /* deep purple  */
  --accent: #f16b61;    /* coral        */
  --secondary: #6bc9ce; /* teal         */
  --white: #ffffff;

  --font: "Poppins", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Fluid type. Each ramp interpolates between 380px and 1440px viewports;
     the max is the exact Figma value. */
  --fs-h1: clamp(2.25rem, 1.309rem + 3.962vw, 4.875rem);      /* 36 -> 78 */
  --fs-h2: clamp(1.75rem, 1.302rem + 1.887vw, 3rem);          /* 28 -> 48 */
  --fs-h3: clamp(1.375rem, 1.061rem + 1.321vw, 2.25rem);      /* 22 -> 36 */
  --fs-step: clamp(1.25rem, 0.981rem + 1.132vw, 2rem);        /* 20 -> 32 */
  --fs-body1: clamp(1.0625rem, 0.951rem + 0.472vw, 1.375rem); /* 17 -> 22 */
  --fs-body2: clamp(0.9375rem, 0.87rem + 0.283vw, 1.125rem);  /* 15 -> 18 */
  --fs-caption: clamp(0.9375rem, 0.826rem + 0.472vw, 1.25rem);/* 15 -> 20 */
  --fs-btn: 0.9375rem;                                        /* 15, fixed  */

  /* Line heights as ratios so they hold at every size (Figma pins them in px,
     which produces overlapping text the moment the font size drops). */
  --lh-h1: 1.09;
  --lh-h2: 1.417;
  --lh-h3: 1.417;
  --lh-body: 1.59;

  /* Rhythm */
  --gutter: clamp(1.25rem, 4.2vw, 4.25rem); /* 20 -> 68 (Figma uses 57-67) */
  /* Inset of the hero copy from the photo edge. Figma: 48-53px (home 820 of a
     767 photo, about 815). Narrower than --gutter on purpose — it is what gives
     the About focus list the width to hold one line, and the header nav aligns
     to it. */
  --hero-pad: clamp(2.5rem, 3.3vw, 3rem);
  --band-y: clamp(3.5rem, 7vw, 7rem);       /* vertical padding inside bands */
  --stack: clamp(1.5rem, 2.6vw, 2.75rem);   /* gap between stacked blocks    */

  /* Ultra-wide guard: bands stay full-bleed, their contents stop growing. */
  --content-max: 1720px;

  --radius-pill: 26px;
  --btn-h: 43px;

  /* Motion language. Four curves, one duration scale — §18 and every
     interaction in this file reference only these. The brief is clinical
     authority, so every curve either decelerates or is symmetric: nothing
     here overshoots, because a single bounced pixel reads as playful. */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);            /* alias of --ease-steady */
  --ease-steady: cubic-bezier(0.4, 0, 0.2, 1);     /* on-screen state changes */
  --ease-settle: cubic-bezier(0.22, 0.61, 0.36, 1);/* things entering         */
  --ease-draw:   cubic-bezier(0.65, 0, 0.35, 1);   /* lines drawn, lens settling */
  --ease-press:  cubic-bezier(0.3, 0, 0.7, 1);     /* pointer-down feedback   */

  --dur-press: 120ms;      /* :active, focus rings          */
  --dur-ui: 200ms;         /* every hover / state micro-move */
  --dur-reveal: 450ms;     /* one paragraph, row or button   */
  --dur-statement: 700ms;  /* display headings, declarations */
  --dur-photo: 900ms;      /* photography                    */
  --dur-signature: 1200ms; /* hero photo + the ledger only   */
}

/* --------------------------------------------------------------------------
   2. Reset / base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is absolute over the hero, but in-page anchors still need to
     clear the mobile bar. */
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  font-family: var(--font);
  font-weight: 300;
  font-size: var(--fs-body1);
  line-height: var(--lh-body);
  color: var(--white);
  background: var(--primary);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden; /* belt-and-braces against a stray full-bleed child */
}

img, svg, picture { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

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

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

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

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

.skip-link {
  position: absolute; top: 0; left: 50%; transform: translate(-50%, -120%);
  z-index: 200; padding: 0.75rem 1.5rem;
  background: var(--secondary); color: var(--primary);
  font-weight: 700; font-size: var(--fs-btn); text-transform: uppercase;
  letter-spacing: 0.05em; border-radius: 0 0 var(--radius-pill) var(--radius-pill);
  transition: transform 0.2s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* --------------------------------------------------------------------------
   3. Type utilities
   -------------------------------------------------------------------------- */
.h1 { font-size: var(--fs-h1); font-weight: 400; line-height: var(--lh-h1); }
.h2 { font-size: var(--fs-h2); font-weight: 400; line-height: var(--lh-h2); }
.h3 { font-size: var(--fs-h3); font-weight: 400; line-height: var(--lh-h3); }
.body-1 { font-size: var(--fs-body1); font-weight: 300; line-height: var(--lh-body); }
.body-2 { font-size: var(--fs-body2); font-weight: 300; line-height: 1.55; }

.caption {
  font-size: var(--fs-caption);
  font-weight: 500;
  line-height: 1.55;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--secondary);
}
.caption--coral { color: var(--accent); }
.caption--white { color: var(--white); }

.prose > * + * { margin-top: 1.15em; }

/* Long-form copy: keep measure readable at every width. The Figma columns are
   604px / 611px of 22px text, which is ~55 characters — hold that. */
.measure { max-width: 34em; }
.measure-wide { max-width: 42em; }

/* --------------------------------------------------------------------------
   3b. Cover media
   Every full-bleed photo in this design is a <picture> (for srcset) wrapping an
   <img>. <picture> is an inline box with auto height, so `height: 100%` on the
   <img> alone resolves against nothing and the photo silently renders at its
   intrinsic ratio instead of filling the band. Both elements are pinned here,
   in one place, for every container that expects a cover image.
   -------------------------------------------------------------------------- */
.hero__media > picture,
.split__media > picture,
.services-split__aside > picture,
.services-split__strip > picture,
.portrait-band > picture,
.photoband > picture,
.coming-soon > picture {
  position: absolute;
  inset: 0;
  display: block;
}
.hero__media > picture > img,
.split__media > picture > img,
.services-split__aside > picture > img,
.services-split__strip > picture > img,
.portrait-band > picture > img,
.photoband > picture > img,
.coming-soon > picture > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */
.band {
  position: relative;
  padding: var(--band-y) var(--gutter);
}
.band > * { max-width: var(--content-max); margin-inline: auto; }

.band--purple { background: var(--primary); color: var(--white); }
.band--coral  { background: var(--accent);  color: var(--white); }
.band--white  { background: var(--white);   color: var(--primary); }
.band--flush  { padding-inline: 0; }

/* Split: photo one side, content the other. Collapses to a single column with
   the photo on top — the design's side-by-side reads as "photo then copy". */
.split {
  display: grid;
  grid-template-columns: 1fr;
}
.split__media { position: relative; overflow: hidden; min-height: 46vw; }
.split__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.split__body { padding: var(--band-y) var(--gutter); }

/* Full-bleed photo band with an overlaid heading + copy + CTA. */
.photoband {
  position: relative;
  display: grid;
  align-items: end;
  min-height: min(1026px, 92vh);
  padding: var(--band-y) var(--gutter);
  overflow: hidden;
  isolation: isolate;
}
.photoband > img,
.photoband > picture { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; }
.photoband::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  /* Figma uses mix-blend-multiply scrims; a plain gradient is equivalent here
     and avoids the blend-mode repaint cost on scroll. */
  background: linear-gradient(to top, rgba(16, 13, 29, 0.85) 0%, rgba(16, 13, 29, 0.45) 45%, rgba(16, 13, 29, 0.15) 100%);
}
.photoband--top::before {
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.15) 60%, rgba(0, 0, 0, 0.35) 100%);
}
.photoband__inner {
  width: 100%; max-width: var(--content-max); margin-inline: auto;
  display: grid; gap: var(--stack);
}

/* Two-column text row used by every "big heading left, paragraph right" band. */
.duo {
  display: grid;
  gap: clamp(1.75rem, 3vw, 3.5rem);
  align-items: start;
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--btn-h);
  padding: 0.625rem 1.5625rem;
  border-radius: var(--radius-pill);
  background: var(--secondary);
  color: var(--primary);
  font-weight: 700; font-size: var(--fs-btn); line-height: 1.2;
  letter-spacing: 0.05em; text-transform: uppercase; text-align: center;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease), transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
/* A 2px lift + soft shadow on hover; :active presses back down. The shadow
   colour is the same near-black the photo scrims use, so it sits naturally
   on purple, coral and white bands alike. */
.btn:hover { background: var(--white); transform: translateY(-2px); box-shadow: 0 8px 20px rgba(16, 13, 29, 0.22); }
.btn:active { transform: translateY(1px); box-shadow: none; }

/* The Figma hover state is "fill turns white". On a white band that makes the
   button vanish, so light sections invert to the purple fill instead. */
.band--white .btn:hover,
.split__body--white .btn:hover { background: var(--primary); color: var(--white); }

/* Diagonal arrow button (Figma: two 3px white strokes, 37px box). */
.arrow-btn {
  flex-shrink: 0;
  width: clamp(28px, 2.6vw, 37px);
  height: clamp(28px, 2.6vw, 37px);
  transition: transform 0.25s var(--ease);
}
.arrow-btn svg { width: 100%; height: 100%; }
.arrow-btn path { stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; fill: none; }

/* --------------------------------------------------------------------------
   6. Header / navigation
   -------------------------------------------------------------------------- */
.site-header {
  position: absolute; top: 0; left: 0; right: 0; z-index: 100;
  padding: 1.5rem var(--gutter) 0;
  isolation: isolate;
}

/* Legibility scrim behind the nav.
   The header floats over whatever the hero happens to be, and on the pages
   where the photo sits under the links (Services, Process, Contact — anything
   using .hero__grid--flip) the top-right of the image is a bright hospital
   window. White 14px links over it are close to unreadable. The hero's own
   scrim can't solve this: it is tuned for the photo, not for the chrome on top
   of it. This one is tuned for the chrome, and is invisible over the purple
   half where there is nothing to darken. */
.site-header::before {
  content: "";
  position: absolute; z-index: -1;
  top: 0; left: 0; right: 0; height: 180px;
  background: linear-gradient(to bottom, rgba(16, 13, 29, 0.55), rgba(16, 13, 29, 0));
  pointer-events: none;
}
.site-header__inner {
  max-width: var(--content-max); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 2rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.55);
}

.logo { display: flex; align-items: center; gap: clamp(0.5rem, 1vw, 0.9rem); flex-shrink: 0; }
.logo__mark { width: clamp(26px, 2.6vw, 38px); height: auto; }
.logo__word { width: clamp(112px, 12vw, 168px); height: auto; }

.nav__list { display: flex; align-items: center; gap: clamp(1rem, 3.2vw, 4.1875rem); }
.nav__link {
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 0.5rem 0;
  font-size: clamp(0.75rem, 0.9vw, 0.875rem);
  font-weight: 400; letter-spacing: 0.05em; text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 4px solid transparent;
  transition: border-color 0.2s var(--ease), opacity 0.2s var(--ease);
}
.nav__link:hover { border-bottom-color: rgba(255, 255, 255, 0.5); }
.nav__link[aria-current="page"] { font-weight: 600; border-bottom-color: var(--white); }

.nav-toggle { display: none; }

/* --------------------------------------------------------------------------
   7. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  background: var(--primary);
}
.hero__grid { display: grid; grid-template-columns: 1fr; }
.hero__media { position: relative; overflow: hidden; min-height: 58vw; max-height: 70vh; }
.hero__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero__media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.1) 55%, rgba(0, 0, 0, 0) 100%);
}
.hero__body {
  display: grid; gap: var(--stack); align-content: center;
  padding: clamp(2.5rem, 6vw, 5.5rem) var(--gutter) clamp(3rem, 6vw, 6rem);
}
/* 12em of the h2's own size == the Figma column width (574px at 48px). */
.hero__title { max-width: 12em; }
.hero__lede { max-width: 34em; }

/* Numbered "FOCUSING ON" list (About hero). */
.focus-list { display: grid; gap: clamp(0.5rem, 1.2vw, 1.125rem); }
/* The Figma sets these as a single text line — "01", a ~10px tab, then the
   label — filling the 585px column to its edge. A fixed 2.4em number column
   was 20px wider than that tab and pushed item 01 onto a second line (client
   feedback, 3 Aug); `auto` plus the drawn gap reproduces the design. */
.focus-list li { display: grid; grid-template-columns: auto 1fr; gap: 0.45em; }
.focus-list .num { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   8. Lead / enquiry form
   -------------------------------------------------------------------------- */
/* Figma sets 44px between the "TAKE THE NEXT STEP" eyebrow and the first field. */
#enquiry > .caption { margin-bottom: clamp(1.25rem, 2.4vw, 2.75rem); }
.lead-form { display: grid; gap: clamp(1.75rem, 3vw, 2.75rem); }
.lead-form__fields { display: grid; gap: clamp(1.25rem, 2.2vw, 1.8125rem); }
.lead-form__row { display: grid; gap: clamp(1.25rem, 2.2vw, 1.8125rem); }

.field { position: relative; display: grid; }
.field__input,
.field__toggle {
  width: 100%;
  padding: 1.4rem 0 0.55rem 0.375rem;
  font-family: var(--font); font-size: 0.9375rem; font-weight: 400; line-height: 1.4;
  color: var(--white);
  background: transparent;
  border: 0; border-bottom: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 0; /* iOS rounds inputs by default */
  -webkit-appearance: none; appearance: none;
  transition: border-color 0.2s var(--ease);
}
.field__input:hover, .field__toggle:hover { border-bottom-color: rgba(255, 255, 255, 0.85); }
.field__input:focus, .field__toggle:focus { outline: none; border-bottom-color: var(--secondary); }
.field__input:focus-visible, .field__toggle:focus-visible { outline: none; }

textarea.field__input { resize: vertical; min-height: 3.5rem; }

/* The Figma form is placeholder-only, which leaves a screen reader with nothing
   once the user starts typing. These are real labels that float on focus/fill. */
.field__label {
  position: absolute; left: 0.375rem; top: 1.4rem;
  font-size: 0.9375rem; font-weight: 400; line-height: 1.4;
  color: rgba(255, 255, 255, 0.7);
  pointer-events: none;
  transform-origin: left top;
  transition: transform 0.18s var(--ease), color 0.18s var(--ease);
}
.field__input:focus + .field__label,
.field__input:not(:placeholder-shown) + .field__label,
.field.is-filled .field__label {
  transform: translateY(-1.35rem) scale(0.78);
  color: var(--secondary);
}

.field__toggle { display: flex; align-items: center; justify-content: space-between; gap: 1rem; text-align: left; }
.field__toggle .chevron { flex-shrink: 0; width: 15px; height: 8px; transition: transform 0.2s var(--ease); }
.field__toggle[aria-expanded="true"] .chevron { transform: rotate(180deg); }

.multiselect { position: relative; }
.multiselect__panel {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 0.5rem);
  display: grid; gap: 0.25rem;
  max-height: 15rem; overflow-y: auto;
  padding: 0.75rem;
  background: var(--primary);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 12px;
}
.multiselect__panel[hidden] { display: none; }
.multiselect__option {
  display: flex; align-items: center; gap: 0.75rem;
  min-height: 44px; padding: 0.35rem 0.5rem;
  font-size: 0.9375rem; font-weight: 300; cursor: pointer;
  border-radius: 6px;
}
.multiselect__option:hover { background: rgba(255, 255, 255, 0.08); }
.multiselect__option input {
  flex-shrink: 0; width: 18px; height: 18px; margin: 0;
  accent-color: var(--secondary);
}

.form-status { font-size: var(--fs-body2); min-height: 1.5em; }
/* Don't reserve a gap-plus-line-height hole above SUBMIT before there is
   anything to say. */
.form-status:empty { display: none; }
.form-status--error { color: #ffd9d5; }
.form-status--ok { color: var(--secondary); }
/* Second conversion route beside SUBMIT (client round 3, 7-8 Oct): the lead
   form stays primary; the discovery call is a real CTA, in the outline style so
   the two buttons don't compete. Wraps under SUBMIT when the column is narrow. */
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1rem; }
.form-alt { font-size: var(--fs-body2); font-weight: 300; }
.btn--outline { background: transparent; color: var(--secondary); box-shadow: inset 0 0 0 2px var(--secondary); }
.btn--outline:hover { background: var(--secondary); color: var(--primary); box-shadow: inset 0 0 0 2px var(--secondary), 0 8px 20px rgba(16, 13, 29, 0.22); }
.field__error { margin-top: 0.35rem; font-size: 0.8125rem; color: #ffd9d5; }
.field.is-invalid .field__input { border-bottom-color: #ffb3ac; }

/* --------------------------------------------------------------------------
   9. Service link rows (heading + diagonal arrow, 1px rule below)
   -------------------------------------------------------------------------- */
.link-rows { display: grid; }
.link-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem;
  padding: clamp(1rem, 2.2vw, 1.75rem) 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.55);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
/* The rows are <a> inside <li>, so the last rule has to be killed through the
   list item — `.link-rows > .link-row:last-child` never matched, which is why
   a stray rule was sitting under "Quality Management" (client feedback, 3 Aug).
   The Figma draws two rules for three rows on both the home panel and the
   Services jump-links. */
.link-rows > li:last-child > .link-row,
.link-rows > .link-row:last-child { border-bottom: 0; }

/* Hover colour comes from the Figma "Nav-Services" components: on the purple
   bands the row goes coral, on the coral panel it goes deep purple. Arrow
   included — it is drawn in currentColor. */
.link-row:hover { color: var(--accent); }
.services-split__panel .link-row:hover,
.band--coral .link-row:hover { color: var(--primary); }
.link-row:hover .arrow-btn { transform: translate(4px, -4px); }
/* The title leans 8px toward the departing arrow — transform only, so long
   titles at narrow widths can't reflow or collide (the flex gap is 24px+). */
.link-row:hover .link-row__title { transform: translateX(8px); }
.link-row__title { font-size: var(--fs-h3); font-weight: 400; line-height: var(--lh-h3); transition: transform 0.3s var(--ease); }

/* --------------------------------------------------------------------------
   10. Expandable rows (＋)
   -------------------------------------------------------------------------- */
.expand-grid {
  display: grid;
  gap: 0 clamp(2rem, 7vw, 6.8125rem);
  align-content: start;
}
.expand { border-bottom: 1px solid rgba(255, 255, 255, 0.55); }
.expand__trigger {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(1rem, 3vw, 3rem);
  width: 100%;
  padding: clamp(1rem, 2.2vw, 1.75rem) 0;
  text-align: left;
}
.expand__title { font-size: var(--fs-h3); font-weight: 400; line-height: var(--lh-h3); }
.expand__icon {
  flex-shrink: 0;
  width: clamp(34px, 3.7vw, 53px); height: clamp(34px, 3.7vw, 53px);
  transition: transform 0.25s var(--ease);
}
.expand__icon circle, .expand__icon path { stroke: currentColor; stroke-width: 3.16; fill: none; stroke-linecap: round; }
.expand__icon .bar-v { transition: opacity 0.2s var(--ease), transform 0.25s var(--ease); transform-origin: center; }
/* The ＋ is four-fold symmetric, so a quarter-turn on hover reads as a live
   "this opens" hint while landing on an identical glyph. Declared before the
   expanded rule so the open state's half-turn always wins. */
.expand__trigger:hover .expand__icon { transform: rotate(90deg); }
.expand__trigger[aria-expanded="true"] .expand__icon { transform: rotate(180deg); }
.expand__trigger[aria-expanded="true"] .bar-v { opacity: 0; }

.expand__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s var(--ease);
}
.expand__panel > div { overflow: hidden; }
.expand.is-open .expand__panel { grid-template-rows: 1fr; }
.expand__panel p { padding-bottom: clamp(1rem, 2vw, 1.75rem); font-size: var(--fs-body2); max-width: 46em; }
/* Figma "Dropdown / Values" sets its expanded copy in Body 1 (22/35), not the
   18px Body 2 the Services and Programmes accordions use. */
.expand-grid--values .expand__panel p { font-size: var(--fs-body1); }

/* Expanded state, from the "Clicked" variant of all three Dropdown components:
   the whole row — title, copy and ＋/－ glyph — switches to deep purple. On the
   coral and photo bands that is a deliberate figure/ground flip rather than an
   inherited colour, and it is what marks the open row at a glance.
   Declared outside §18 so it also applies under prefers-reduced-motion. */
.expand.is-open .expand__title,
.expand.is-open .expand__panel p,
.expand.is-open .expand__icon { color: var(--primary); }
.expand.is-open .copy-todo::before { color: currentColor; }

/* Marks copy the Figma file never supplied. Deliberately visible so the client
   can see exactly which slots they still owe. */
.copy-todo { opacity: 0.72; font-style: italic; }
.copy-todo::before { content: "\25CF  "; color: var(--secondary); font-style: normal; }
.band--white .copy-todo::before { color: var(--accent); }

/* Dark-on-light variant used on the white Programmes band. */
.expand--dark { border-bottom-color: rgba(51, 40, 93, 0.3); }

/* --------------------------------------------------------------------------
   11. Numbered feature list (01 / 02 / 03 with body copy)
   -------------------------------------------------------------------------- */
.numbered { display: grid; gap: clamp(1.25rem, 2.4vw, 1.75rem); }
.numbered__item { display: grid; gap: 0.4rem; }
.numbered__title {
  display: grid; grid-template-columns: 2.2em 1fr; gap: 0.3em;
  font-size: var(--fs-body1); font-weight: 500; line-height: 1.45;
}
.numbered__body { font-size: var(--fs-body2); max-width: 44em; }

/* Process steps: 01 | title | description */
.steps { display: grid; gap: clamp(1.75rem, 3vw, 2.5rem); }
.step {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.35rem clamp(1.5rem, 3.2vw, 3.3125rem);
  padding-bottom: clamp(1.75rem, 3vw, 2.9375rem);
  border-bottom: 1px solid rgba(255, 255, 255, 0.4);
}
.steps > .step:last-child { border-bottom: 0; padding-bottom: 0; }
.step__num { font-size: var(--fs-h2); font-weight: 400; line-height: 1.1; color: var(--accent); }
.step__title { font-size: var(--fs-step); font-weight: 400; line-height: 1.25; }
.step__body { font-size: var(--fs-body1); font-weight: 300; }
.steps-cta { margin-top: clamp(1.75rem, 3vw, 2.5rem); }

/* --------------------------------------------------------------------------
   12. Values / mission / quote
   -------------------------------------------------------------------------- */
.pillars { display: grid; gap: clamp(1.75rem, 3vw, 3rem); }
.pillar {
  padding-left: clamp(1.25rem, 2.4vw, 2.875rem);
  border-left: 1px solid rgba(255, 255, 255, 0.75);
}

.mission { text-align: center; display: grid; gap: clamp(1.25rem, 2.4vw, 1.9rem); justify-items: center; }
.mission__title {
  font-size: clamp(1.75rem, 1.145rem + 2.547vw, 3.375rem); /* 28 -> 54 */
  font-weight: 500; line-height: 1.39;
  max-width: 22ch;
}
.mission__body { max-width: 62ch; }

.quote { display: grid; gap: clamp(1.25rem, 2.4vw, 1.9rem); }
.quote__mark { width: clamp(34px, 3.5vw, 51px); height: auto; color: var(--secondary); }
.quote__text { max-width: 34em; }
.quote__cite { font-style: normal; font-weight: 500; }

/* --------------------------------------------------------------------------
   13. Footer
   -------------------------------------------------------------------------- */
.site-footer { background: var(--primary); color: var(--white); padding: clamp(3rem, 6vw, 6.875rem) var(--gutter) clamp(1.5rem, 2.5vw, 2.5rem); }
.site-footer__inner { max-width: var(--content-max); margin-inline: auto; }
.site-footer__top {
  display: grid; gap: clamp(2.25rem, 4vw, 3rem);
}
/* The rule between the two footer rows is its own element rather than a
   border on the legal row, because at desktop both rows dissolve into one
   shared grid (`display: contents`) and a border on a dissolved box has
   nothing to draw on. */
.site-footer__rule {
  height: 0; border: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.55);
  margin: clamp(2.5rem, 5vw, 5.75rem) 0 clamp(1rem, 2vw, 1.75rem);
}
/* Round 2 #23/#31: the footer nav and the legal links share one two-column
   rhythm, so "Process / Contact" lines up with "Terms & Conditions" underneath
   it. Both blocks sit in the same track of the footer grid and split it in half,
   which makes the alignment structural — it holds at every width instead of
   depending on how wide the words happen to be. */
.footer-nav { display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(1rem, 3vw, 3.5rem); }
.footer-nav__col { display: grid; gap: clamp(0.5rem, 1vw, 1.375rem); align-content: start; }
.footer-nav a, .footer-nav span {
  display: inline-flex; align-items: center; min-height: 34px;
  font-size: var(--fs-caption); font-weight: 400; text-transform: uppercase; line-height: 1.55;
}
.footer-nav a { transition: color 0.2s var(--ease); }
.footer-nav a:hover { color: var(--secondary); }

.social { display: flex; gap: 1.25rem; }
.social a {
  display: grid; place-items: center;
  width: 44px; height: 44px; margin: -8px;
  transition: color 0.2s var(--ease);
}
/* Round 2 #19: hover is a brand-teal colour change, not a fade. */
.social a:hover { color: var(--secondary); }
.social svg { width: 28px; height: 28px; }

.site-footer__legal {
  display: flex; flex-wrap: wrap; gap: 0.75rem clamp(1rem, 3vw, 3rem);
  align-items: center;
  font-size: 0.9375rem; font-weight: 400;
  color: var(--secondary);
}
/* Column-major on purpose: filling down then across puts Terms & Conditions at
   the top of column 2, directly under the nav's second column (round 2 #23), and
   reproduces the two-up legal block in her mobile reference (#31). */
.site-footer__links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(2, auto);
  grid-auto-flow: column;
  gap: 0.75rem clamp(1rem, 3vw, 3.5rem);
}
.site-footer__legal a { transition: color 0.2s var(--ease); }
.site-footer__legal a:hover { color: var(--white); }
.site-footer__legal .copyright { margin-right: auto; }

/* --------------------------------------------------------------------------
   14. Coming soon
   -------------------------------------------------------------------------- */
.coming-soon {
  position: relative; isolation: isolate;
  min-height: 100vh; min-height: 100svh;
  display: grid; align-content: space-between; justify-items: center;
  gap: 2rem;
  padding: clamp(3rem, 8vh, 7rem) var(--gutter);
  text-align: center;
}
.coming-soon > img,
.coming-soon > picture { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; }
/* Figma stacks the same photo twice with two scrims (0.6 + 0.5 over a 50%
   copy). One image and one scrim of equivalent density — half the payload. */
.coming-soon::before { content: ""; position: absolute; inset: 0; z-index: -1; background: rgba(16, 13, 29, 0.78); }
.coming-soon__title { text-transform: uppercase; align-self: center; }
.coming-soon__contact { display: grid; gap: 0.25rem; justify-items: center; }
/* The Coming Soon page has no header, so nothing pushes it clear of a bar. */
.page-coming-soon { padding-top: 0 !important; }
/* Figma draws the Coming Soon lockup horizontally with the coral mark (329x136:
   mark 80 wide, wordmark 246 wide, 3px apart) — the footer lockup at hero
   scale, not the white stacked one the header uses. */
.logo--hero { align-items: center; gap: clamp(0.25rem, 0.5vw, 0.4rem); }
.logo--hero .logo__mark { width: clamp(46px, 5.5vw, 80px); }
.logo--hero .logo__word { width: clamp(150px, 17vw, 246px); }
/* The email is the underlined link here; the button keeps the site button
   style (client feedback, 3 Aug). */
.coming-soon__contact a:not(.btn) { text-decoration: underline; text-underline-offset: 0.2em; }
.coming-soon__contact a:not(.btn):hover { color: var(--secondary); }

/* --------------------------------------------------------------------------
   15. Breakpoints
   The design is a 1440px artboard, so the layout "arrives" at 1024 and is
   pixel-faithful from 1440 up.
   -------------------------------------------------------------------------- */

/* ---- >= 600px: two-up form fields, side-by-side expandables start pairing -- */
@media (min-width: 600px) {
  .lead-form__row { grid-template-columns: 1fr 1fr; }
  .step { grid-template-columns: auto 1fr; }
  .step__num { grid-row: span 2; }
}

/* ---- >= 768px: tablet — pillars and numbered lists go multi-column --------- */
@media (min-width: 768px) {
  .pillars { grid-template-columns: repeat(3, 1fr); }
  .duo--tablet { grid-template-columns: 1fr 1fr; }
}

/* ---- >= 1024px: the designed desktop layout ------------------------------- */
@media (min-width: 1024px) {
  /* Client feedback (3 Aug): the nav links left-align to the hero copy below.
     The hero is full-bleed — a 53.3vw photo column, then --hero-pad — while the
     header's box starts one --gutter in and is `100vw - 2*gutter` wide. So the
     first column resolves to `0.533*vw + hero-pad - gutter`, rewritten against
     the header's own content box (0.533*vw == 53.3% + 1.066*gutter) so a
     classic scrollbar cannot push the two out of step. The cap comes off for
     the same reason: the header tracks the full-bleed hero, not the capped
     text measure. */
  .site-header__inner {
    max-width: none;
    display: grid;
    /* minmax(0, …) rather than a flat length: the nav takes its max-content
       width first and the alignment column yields whatever is left. At 1440 it
       resolves to the full offset and the links land on the hero copy; at 1024
       — where the conversion pages carry a sixth item, the CTA pill — it simply
       gives way and the nav packs right instead of overflowing the page. */
    grid-template-columns: minmax(0, calc(53.3% + 0.066 * var(--gutter) + var(--hero-pad))) max-content;
    column-gap: 0;
    /* The flex base sets space-between. Left as-is under grid it hands the
       leftover width to the gap between the two tracks and pushes the nav back
       to the right edge — and only once the webfont has loaded and the nav has
       shrunk enough to leave any leftover, which makes it invisible in a
       fallback-font render. Pin it. */
    justify-content: start;
    align-items: center;
  }
  .site-header__inner > .nav { justify-self: start; }

  .hero__grid { grid-template-columns: 53.3% 46.7%; } /* 767 / 1440 */
  .hero__media { min-height: 100%; max-height: none; }
  .hero__body { padding-top: clamp(7rem, 12vw, 13rem); }
  /* Figma insets the hero copy 48-53px from the photo edge, not a full gutter
     (home: 820-1394 of 1440; about: 815-1400). Restoring that is what lets
     "01 Innovative Education and Workforce Development" hold one line on the
     About hero, as drawn. */
  .hero__body { padding-inline: var(--hero-pad) clamp(2rem, 2.8vw, 2.5rem); }
  /* Home only: the photo's bottom edge lands on the SUBMIT button. In the Figma
     artboard the photo runs 0-1060 and the form 209-1061 — they finish together,
     which the trailing body padding was breaking. */
  .hero__body--flush { padding-bottom: 0; }

  .split { grid-template-columns: 42.4% 57.6%; }      /* 611 / 1440 */
  .split--half { grid-template-columns: 53.1% 46.9%; }/* 765 / 1440 */
  .split--flip { grid-template-columns: 57.6% 42.4%; }
  .split--flip .split__media { order: 2; }
  .split__media { min-height: 100%; }

  .duo { grid-template-columns: 1fr 1fr; gap: clamp(2rem, 4vw, 5.5rem); }
  .duo--wide-left { grid-template-columns: 43% 1fr; }

  .expand-grid--2 { grid-template-columns: 1fr 1fr; }
  /* Round 2 #21/#22: once side by side, the bottom-most row is the last TWO
     items — each column has to end clean, not just the final one in DOM order. */
  .expand-grid--2 > .expand:nth-last-child(-n+2) { border-bottom-color: transparent; }

  .step { grid-template-columns: auto minmax(0, 300px) minmax(0, 1fr); }
  .step__num { grid-row: 1; }

  .steps-layout { display: grid; grid-template-columns: minmax(0, 32%) minmax(0, 1fr); gap: clamp(2rem, 4vw, 4.5rem); align-items: start; }

  /* Client feedback (3 Aug): the footer nav left-aligns to "Legal" below it.
     That only holds if both rows share one set of column tracks, so the two row
     wrappers dissolve into the footer grid. Column 2 is sized by the wider of
     the nav block and the legal-links block, and both start at its left edge —
     so the alignment is structural, not a hand-tuned offset. */
  .site-footer__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) max-content max-content;
    column-gap: clamp(2rem, 5vw, 6rem);
    align-items: start;
  }
  .site-footer__top,
  .site-footer__legal { display: contents; }
  .site-footer__rule { grid-column: 1 / -1; }
  .site-footer__legal .copyright { grid-column: 1; }
  /* Column 2 only — not 2/-1. The legal links have to occupy exactly the track
     the nav occupies for the two blocks to share a column edge (#23). */
  /* Round 3 (Devyani, 12 Aug — released 8 Oct): on desktop the legal links run
     on ONE line, from the nav's column edge out to the social icons' right edge.
     The two-column grid above is the mobile layout only. */
  .site-footer__links {
    grid-column: 2 / -1;
    display: flex; flex-wrap: nowrap; justify-content: space-between;
    gap: clamp(1rem, 3vw, 3.5rem);
    white-space: nowrap;
  }
  .social { flex-direction: column; gap: clamp(0.75rem, 1.5vw, 1.5625rem); justify-self: end; }
}

/* ---- <= 1023px: mobile / tablet navigation -------------------------------- */
@media (max-width: 1023px) {
  .site-header {
    position: fixed;
    padding-top: 0;
    background: var(--primary);
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.18);
  }
  .site-header__inner {
    padding: 0.75rem 0;
    border-bottom: 0;
  }
  /* The bar is solid below 1024px, so the scrim has nothing to do. */
  .site-header::before { display: none; }
  /* Push page content clear of the now-fixed bar. */
  body { padding-top: 68px; }

  .nav-toggle {
    display: grid; place-items: center;
    width: 48px; height: 48px; margin-right: -0.5rem;
  }
  .nav-toggle__box { position: relative; width: 26px; height: 18px; }
  .nav-toggle__box span {
    position: absolute; left: 0; width: 100%; height: 2px;
    background: var(--white); border-radius: 2px;
    transition: transform 0.25s var(--ease), opacity 0.2s var(--ease), top 0.25s var(--ease);
  }
  .nav-toggle__box span:nth-child(1) { top: 0; }
  .nav-toggle__box span:nth-child(2) { top: 8px; }
  .nav-toggle__box span:nth-child(3) { top: 16px; }
  .nav-toggle[aria-expanded="true"] .nav-toggle__box span:nth-child(1) { top: 8px; transform: rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle__box span:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] .nav-toggle__box span:nth-child(3) { top: 8px; transform: rotate(-45deg); }

  .nav {
    position: fixed; inset: 68px 0 0;
    display: grid; align-content: start;
    padding: clamp(1rem, 4vw, 2.5rem) var(--gutter) 3rem;
    background: var(--primary);
    overflow-y: auto;
    opacity: 0; visibility: hidden;
    transform: translateY(-0.75rem);
    transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
  }
  .nav.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__link {
    /* Base is inline-flex, which would size the rule to the text width. */
    display: flex; width: 100%;
    min-height: 60px;
    font-size: 1.0625rem; letter-spacing: 0.08em;
    border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  }
  .nav__link[aria-current="page"] { color: var(--secondary); border-bottom-color: rgba(255, 255, 255, 0.18); }
  .nav__link:hover { border-bottom-color: rgba(255, 255, 255, 0.18); }

  body.nav-open { overflow: hidden; }

  /* The hero photo is a banner above the copy once the split collapses. */
  .hero__media { order: -1; }
}

/* ---- <= 599px: small phones (test target: 580px and down to 320px) -------- */
@media (max-width: 599px) {
  :root { --content-max: 100%; }
  .photoband { min-height: 78vh; }
  .split__media, .hero__media { min-height: 62vw; }
  .site-footer__legal { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
  .site-footer__legal .copyright { order: 1; margin-right: 0; margin-top: 0.5rem; opacity: 0.85; }
}

@media (max-width: 380px) {
  .nav__link { font-size: 1rem; }
  .logo__word { width: 104px; }
}

/* ---- >= 1920px: ultra-wide ------------------------------------------------
   Bands stay full-bleed (the design's alternating colour blocks depend on it)
   but their contents stop growing, and the split columns switch from
   percentages to a centred fixed-ratio so photos don't become letterbox
   slivers on a 5K display. */
@media (min-width: 1920px) {
  :root { --band-y: 7.5rem; --gutter: 5rem; }
  .photoband { min-height: min(1026px, 78vh); }
  .hero__media { max-height: 92vh; }
}

/* ---- Short landscape phones ---------------------------------------------- */
@media (max-height: 520px) and (orientation: landscape) {
  .hero__media { max-height: 60vh; min-height: 40vh; }
  .photoband { min-height: 120vh; }
  .coming-soon { min-height: auto; padding-block: 4rem; }
}

/* --------------------------------------------------------------------------
   16. Page compositions
   Sections whose geometry is specific enough that a generic primitive would
   obscure rather than help.
   -------------------------------------------------------------------------- */

/* Footer lockup is drawn larger than the header one in the Figma file. */
.logo--footer .logo__mark { width: clamp(38px, 4vw, 56px); }
.logo--footer .logo__word { width: clamp(160px, 18vw, 250px); }

/* Home "Services": photo column on the left with the title dropped to its
   foot, photo strip + coral panel stacked on the right. */
.services-split { display: grid; grid-template-columns: 1fr; background: var(--accent); }

.services-split__aside {
  position: relative; isolation: isolate; overflow: hidden;
  display: grid; align-items: end;
  min-height: 62vw;
  padding: var(--band-y) var(--gutter);
}
.services-split__aside img {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
  /* The Figma crop is pushed left so the applauding hands stay in frame; a
     default centre crop lands on an out-of-focus shoulder. */
  object-position: 32% 50%;
}
.services-split__aside::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, rgba(16, 13, 29, 0.75), rgba(16, 13, 29, 0.1) 60%);
}
.services-split__title { color: var(--white); }

/* A 400px-tall photo sliver above the coral panel. It carries no information
   the page doesn't already show, so small screens skip the download entirely. */
.services-split__strip { display: none; }

/* Flex rather than grid so the last block (the button) can take `margin-top:
   auto` at desktop and settle on the panel's baseline — see the bottom-align
   rule in the >=1024px composition block. */
.services-split__panel {
  display: flex; flex-direction: column; gap: clamp(1.75rem, 3.5vw, 3.25rem);
  padding: var(--band-y) var(--gutter);
  background: var(--accent); color: var(--white);
}
/* Coral/white sits at ~2.9:1. Nudging body copy from Light 300 to Regular 400
   on coral panels materially improves legibility without altering the palette
   — see HANDOVER.md, contrast note. */
.services-split__panel .body-1,
.band--coral .body-1,
.band--coral .expand__panel p { font-weight: 400; }

/* About: "Built on Experience" — intro duo above a three-up pillar row. */
.experience { display: grid; gap: clamp(2.5rem, 5vw, 5.5rem); }

/* Full-bleed portrait band that sits above a bio. The Figma crops these to
   602px of a 1440px frame; `cover` plus an aspect ratio reproduces that
   without pinning a pixel height that would letterbox on a phone. */
.portrait-band { position: relative; overflow: hidden; aspect-ratio: 16 / 9; max-height: 78vh; }
.portrait-band img { object-position: 50% 28%; }
/* Founder portrait (client round 3, 7 Oct): show the photo's full height, so
   nothing is cropped by the frame. 8 Oct: the source image itself is reframed
   (founder-mark-frame-*): ends just below the waist, with less headroom. */
.portrait-band--full { max-height: none; aspect-ratio: 1578 / 1124; }
.portrait-band--full img { object-position: 44% 50%; }
/* Home founder-quote photo, same crop request: centre Mark in the frame on
   desktop, and use Mamta's crop proportions on mobile/tablet. */
.split__media--founder img { object-position: 38% 50%; }
@media (max-width: 1023px) {
  .split__media.split__media--founder { min-height: 0; aspect-ratio: 1578 / 1124; }
  .split__media--founder img { object-position: 44% 50%; }
}

.bio { display: grid; gap: clamp(1.75rem, 3.5vw, 2.75rem); }
/* The Figma pours one continuous bio through two fixed-height columns, which
   splits a sentence across the gutter ("…standardisation of more" | "than 350
   workflows…") and leaves one column stranded half-empty. CSS multi-column
   breaks on paragraph boundaries and balances the two sides at any width. */
.bio__cols p { break-inside: avoid; }
.bio__cols p + p { margin-top: 1.15em; }
/* Founder bio (client round 3, 8 Oct): Mamta asked for the two columns to be
   used more evenly. Auto-balancing kept four paragraphs on the left, so the
   split is set explicitly in the markup: three paragraphs per side. */
.bio__cols--split { display: grid; gap: 1.15em clamp(2rem, 4vw, 3.5rem); }
@media (min-width: 900px) {
  .bio__cols { columns: 2; column-gap: clamp(2rem, 4vw, 3.5rem); }
  .bio__cols--split { columns: auto; grid-template-columns: 1fr 1fr; align-items: start; }
  .portrait-band { aspect-ratio: 1440 / 602; }
  /* 8 Oct: photo now cut above the waist (Mark: the jacket below read as a skirt). */
  .portrait-band--full { aspect-ratio: 2912 / 1190; }
  /* 8 Oct: the 78vh cap shrank Jane's band below full width on wide screens
     (1772px of 1920) and cropped her. Uncapped, at the photo's own ratio, the
     whole frame shows edge to edge at any width. */
  .portrait-band--consultant { max-height: none; aspect-ratio: 4096 / 1625; }
}

.follow { display: flex; align-items: center; gap: 0.6rem; }
.follow a { display: inline-grid; place-items: center; width: 44px; height: 44px; margin: -8px; transition: color 0.2s var(--ease); }
/* The founders' LinkedIn marks change colour on hover. The glyph is drawn in
   currentColor, so the colour is the whole change.
   Round 1 (3 Aug) set this to LinkedIn blue; round 2 #17/#18 (5 Aug) supersedes
   that and asks for the brand teal instead. */
.follow a:hover { color: var(--secondary); }
.follow svg { width: 26px; height: 26px; }

/* Closing CTA. Not in the Figma — the About and Process artboards dead-end with
   no next step. See HANDOVER.md; safe to delete if the client disagrees. */
.cta-band__action { display: grid; gap: var(--stack); justify-items: start; align-content: start; }

/* Client feedback (3 Aug): top-align the CTA copy with the heading beside it.
   They share a first line but not a line box — a 48px heading on 1.417 leading
   carries ~7px more space above its cap than 22px body on 1.59 — so the right
   column is pushed down by the difference rather than by an eyeballed pixel
   value. 0.3585/0.445 are (half-leading + Poppins' ascender-to-cap gap) per
   unit of font size for the two ramps; both track their clamp()s, so the
   alignment holds at every width. */
@media (min-width: 768px) {
  .cta-band .duo > .cta-band__action,
  .final-cta .duo > .cta-band__action {
    padding-top: calc(0.3585 * var(--fs-h2) - 0.445 * var(--fs-body1));
  }
}

/* Services hero puts the copy first and the photo second. */
@media (min-width: 1024px) {
  .hero__grid--flip { grid-template-columns: 53.9% 46.1%; } /* 776 / 1440 */
  /* Flipped heroes put the copy against the page edge, so it takes the page
     gutter — --hero-pad is the inset from a photo column, not from the page. */
  .hero__grid--flip .hero__body { padding-inline: var(--gutter) clamp(2rem, 4vw, 5rem); }
}
@media (max-width: 1023px) {
  .hero__grid--flip .hero__media { order: -1; }
}

/* The Services jump-links sit alone on a purple band with 88px between rows. */
.link-rows--spacious .link-row { padding-block: clamp(1.25rem, 3vw, 2.75rem); }

/* --------------------------------------------------------------------------
   Conversion variant (index-2.html, contact.html)
   Everything here is additive. The original index.html is untouched, so the two
   homepages can be compared like for like. All of it is built from the existing
   reveal primitives (.duo, .pillars, .numbered, .link-rows, .photoband__inner),
   so the motion layer in §18 picks it up with no new selectors to keep in sync.
   -------------------------------------------------------------------------- */

/* Nav CTA. Only on the variant and the contact page — adding a pill to the
   approved header everywhere would change the design being reviewed. */
.nav__cta {
  padding: 0.5rem 1.25rem;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--radius-pill);
  border-bottom-width: 1px;
  transition: background-color var(--dur-ui) var(--ease-steady),
              color var(--dur-ui) var(--ease-steady),
              border-color var(--dur-ui) var(--ease-steady);
}
.nav__cta:hover,
.nav__cta[aria-current="page"] {
  background: var(--secondary);
  border-color: var(--secondary);
  color: var(--primary);
  font-weight: 600;
}

/* Primary CTA at hero scale. The design's 43px pill is right inside a form;
   as the single page-level action it needs more presence. */
.btn--lg {
  min-height: 54px;
  padding: 0.875rem 2.25rem;
  font-size: 1rem;
}
/* Teal on coral is a weak pairing — on the coral band the CTA goes white. */
.btn--on-coral { background: var(--white); }
.btn--on-coral:hover { background: var(--primary); color: var(--white); }

/* Primary + "still researching" secondary, side by side. */
.cta-pair {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
}

.text-link {
  font-weight: 400;
  border-bottom: 1px solid rgba(255, 255, 255, 0.45);
  padding-bottom: 2px;
  transition: color var(--dur-ui) var(--ease-steady), border-color var(--dur-ui) var(--ease-steady);
}
.text-link:hover { color: var(--secondary); border-bottom-color: var(--secondary); }
.text-link--dark { border-bottom-color: rgba(51, 40, 93, 0.4); }
.text-link--dark:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* Credential strip under the hero CTA. Deliberately NOT reveal-animated: a
   trust signal that fades in after the reader has already decided is worthless,
   and this is the element that has to be there the moment the headline lands. */
.trust-strip {
  display: flex; flex-wrap: wrap;
  gap: 0.5rem clamp(1rem, 2.5vw, 1.75rem);
  padding-top: clamp(1rem, 2vw, 1.5rem);
  border-top: 1px solid rgba(255, 255, 255, 0.25);
  color: rgba(255, 255, 255, 0.82);
}
.trust-strip li { display: flex; align-items: center; gap: 0.55rem; }
.trust-strip li + li::before {
  content: ""; flex-shrink: 0;
  width: 4px; height: 4px; margin-right: 0.4rem;
  border-radius: 50%; background: var(--secondary);
}

/* Pillars inverted for the white proof band. */
.pillars--dark .pillar { border-left-color: rgba(51, 40, 93, 0.35); }

.final-cta .cta-band__action { gap: clamp(1.25rem, 2.5vw, 2rem); }

/* Reassurance line under the form — an alternative path for anyone who does
   not want to fill in a form at all. */
.form-reassure { margin-top: 0.5rem; color: rgba(255, 255, 255, 0.75); }
.form-reassure a { border-bottom: 1px solid rgba(255, 255, 255, 0.4); }
.form-reassure a:hover { color: var(--secondary); border-bottom-color: var(--secondary); }

/* Round 2 #16/#21/#22: no divider under the bottom-most row of any ＋ grid — the
   list should end on its last item, not on a rule.
   Kept as `transparent` rather than `none` so removing the line costs no 1px of
   layout, which would otherwise shift the two columns out of step with each
   other whenever they hold a different number of items.
   Stacked (single-column) case: only the final item. */
.expand-grid > .expand:last-child { border-bottom-color: transparent; }

/* About: "Our Values" reuses .split but the media half carries an overlaid
   title, so it needs the same scrim treatment as the services aside. */
.values-media { display: grid; align-items: end; padding: var(--band-y) var(--gutter); }
.values-media::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(16, 13, 29, 0.7), rgba(16, 13, 29, 0.15) 60%);
}
.values-media > :not(picture) { position: relative; z-index: 2; }

@media (min-width: 1024px) {
  .services-split { grid-template-columns: 42.4% 57.6%; }
  .services-split__aside { min-height: 1374px; max-height: 92vh; }
  .services-split__main { display: grid; grid-template-rows: minmax(0, 29%) minmax(0, 71%); }
  .services-split__strip { display: block; position: relative; overflow: hidden; }
  .services-split__strip img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  /* Client feedback (3 Aug): "Services" bottom-aligns with the button on the
     right. The aside is the taller column and pins its title to its own foot,
     so the button has to be pushed to the panel's foot to meet it — both then
     sit one --band-y above the same section edge. */
  .services-split__panel > :last-child { margin-top: auto; }
}

@media (min-width: 1920px) {
  .services-split__aside { max-height: 80vh; }
}

/* --------------------------------------------------------------------------
   17. Touch ergonomics
   The Figma spec pins buttons at 43px and footer links at 31px line-height,
   both of which land under the 44px minimum touch target. Rather than change
   the design everywhere, grow them only where the pointer is coarse — the
   1440px desktop render stays pixel-faithful.
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
  .btn { min-height: 48px; padding-inline: 1.75rem; }
  .footer-nav a, .footer-nav span { min-height: 44px; }
  .site-footer__legal a {
    display: inline-flex; align-items: center;
    min-height: 44px;
  }
  .site-footer__legal { gap: 0 clamp(1rem, 3vw, 3rem); }
  .link-row { padding-block: clamp(1.1rem, 2.2vw, 1.75rem); }
  .expand__trigger { padding-block: clamp(1.1rem, 2.2vw, 1.75rem); }
  .coming-soon__contact a { display: inline-flex; align-items: center; min-height: 44px; }
  /* Inline text links on the conversion pages — secondary CTAs still have to
     be tappable. inline-flex rather than block so they stay inside a sentence. */
  .text-link,
  .form-reassure a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* ==========================================================================
   18. Motion
   ==========================================================================
   Everything in this section is gated twice: on `html.js` (set by the inline
   <script> in each page's <head>, so a dead script means a fully visible page)
   and on prefers-reduced-motion: no-preference. Neither gate depends on
   main.js having run — main.js only ever ADDS `.is-in`.

   Only transform and opacity animate on scroll. Nothing animates width,
   height, top, left, or filter, so no reveal can move layout or widen the
   page — which matters because <body> is overflow-x: hidden and a horizontal
   transform would be silently clipped rather than caught.

   Reveal travel maxes out at 24px, below the tightest heading-to-copy gap on
   the site, so a half-finished stagger can never overlap neighbouring text.

   The delay custom properties:
     --d       when this element starts, relative to its reveal trigger
     --d-step  per-row base delay on the process ledger (§18.5)
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {

  /* ---- 18.1 The reveal vocabulary --------------------------------------
     Four variants. `rise` is the default; a section opts into the others by
     selector in §18.3. Anything still waiting keeps will-change; adding
     .is-in drops it, so compositor layers are not held for the life of the
     page on a 9,500px-tall Services page.

     ┌─ IMPORTANT ─────────────────────────────────────────────────────────┐
     │ This selector list is the ONLY thing that hides a reveal element,   │
     │ and it must stay in step with REVEAL_TARGETS in assets/js/main.js.  │
     │                                                                     │
     │ It lives here rather than being applied by the script because the   │
     │ script is deferred: a class added at DOMContentLoaded arrives after │
     │ the browser may already have painted, so the content would flash in │
     │ and then hide itself. Hiding from the first paint needs CSS.        │
     │                                                                     │
     │ If the two lists ever drift, main.js sweeps anything it hid but did │
     │ not observe (see the failsafe at the end of initMotion) — so the    │
     │ worst case is a missing animation, never invisible content.         │
     └─────────────────────────────────────────────────────────────────────┘ */
  .js .duo > *,
  .js .mission > *,
  .js .quote > *,
  .js .services-split__title,
  .js .services-split__panel > *,
  .js .split__body > *,
  .js .values-media > h2,
  .js .bio > *,
  .js .steps-layout > h2,
  .js .band > .caption,
  .js .band > div > .caption,
  .js .photoband__inner > *,
  .js .link-rows > li,
  .js .expand-grid > .expand,
  .js .pillars > .pillar,
  .js .numbered > .numbered__item {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity var(--dur-reveal) var(--ease-settle) var(--d, 0ms),
                transform var(--dur-reveal) var(--ease-settle) var(--d, 0ms);
    will-change: opacity, transform;
  }

  /* unveil — photography. Its hidden state is declared here, NOT bolted on by
     the script as a variant, because the two start states are not
     interchangeable: a photo parked at translateY(24px) for the few frames
     before the script runs would drop a 24px gap at the top of its band,
     every one of these being position: absolute; inset: 0 inside an
     overflow: hidden crop. It has to start scaled, from the first paint. */
  .js .photoband > picture,
  .js .portrait-band > picture,
  .js .split__media > picture,
  .js .services-split__aside > picture,
  .js .services-split__strip > picture {
    opacity: 0.85;
    transform: scale(1.06);
    transition: opacity 300ms var(--ease-settle) var(--d, 0ms),
                transform var(--dur-photo) var(--ease-draw) var(--d, 0ms);
    will-change: opacity, transform;
  }

  /* Cascade containers are transparent to the reveal system: their CHILDREN
     are the units, staggered by the script. Several of them are also matched
     by the generic "> *" selectors above (a .link-rows inside a
     .services-split__panel, an .expand-grid inside a .split__body), and if the
     container were hidden as a unit too, revealing it would not reveal the
     children — they carry their own hidden state — and the list would never
     appear. Reset them back out. Declared after the block above so it wins on
     source order at equal specificity. */
  .js .link-rows,
  .js .expand-grid,
  .js .pillars,
  .js .numbered {
    opacity: 1;
    transform: none;
    transition: none;
    will-change: auto;
  }

  /* One rule releases every variant, and drops will-change with it.
     !important throughout: the hidden selectors above are one element deep
     (".js .steps-layout > h2") and so out-specify a bare ".js .is-in". */
  .js .is-in {
    opacity: 1 !important;
    transform: none !important;
    will-change: auto !important;
  }

  /* The two text variants only ever REDUCE the start offset, so the script
     applying them a few frames late is invisible — the element was already
     hidden, and hidden is hidden. */

  /* cascade — a list being tabled. Travel is shorter because these arrive in
     sequence and a long throw reads as a wave. JS sets --d per child. */
  .js .rv--cascade { transform: translateY(16px); }

  /* statement — a declaration. Display headings only. */
  .js .rv--statement {
    transform: translateY(16px);
    transition-duration: var(--dur-statement), var(--dur-statement);
  }

  /* ---- 18.2 Hero entrance (on load) ------------------------------------
     Hidden by selector rather than by a JS-added class: the hero is above the
     fold, so a class arriving with the deferred script would flash. Delays
     are static and live here; the H1 leads because a hospital COO must be
     able to read the value proposition inside half a second.

     The H1 animates as one block — not per-line, not per-word. Splitting a
     clamp()-scaled headline means re-splitting at every viewport, and
     cascading words makes the reader wait for the sentence. */
  .js .hero__media > picture,
  .js .hero__title,
  .js .hero__lede,
  .js .hero__body .caption,
  .js .hero__body .field,
  .js .hero__body .form-status,
  .js .hero__body .btn,
  .js .hero__body .form-alt,
  .js .hero__body .focus-list > li {
    opacity: 0;
    transition: opacity var(--dur-reveal) var(--ease-settle) var(--d, 0ms),
                transform var(--dur-reveal) var(--ease-settle) var(--d, 0ms);
  }
  .js .hero__title,
  .js .hero__lede,
  .js .hero__body .caption,
  .js .hero__body .field,
  .js .hero__body .form-status,
  .js .hero__body .btn,
  .js .hero__body .form-alt,
  .js .hero__body .focus-list > li { transform: translateY(12px); }

  .js .hero__title {
    --d: 100ms;
    transform: translateY(16px);
    transition-duration: var(--dur-statement), var(--dur-statement);
  }
  .js .hero__lede,
  .js .hero__body .caption { --d: 350ms; }

  /* Fields stagger 60ms apart from 450ms. The two-up name/phone row counts as
     one slot, because that is what it looks like — both halves arrive
     together. Indices are 1-based over .lead-form__fields' own children, where
     child 1 is the name/phone row. */
  .js .lead-form__row > .field { --d: 450ms; }
  .js .lead-form__fields > .field:nth-child(2) { --d: 510ms; }
  .js .lead-form__fields > .field:nth-child(3) { --d: 570ms; }
  .js .lead-form__fields > .field:nth-child(4) { --d: 630ms; }
  .js .lead-form__fields > .field:nth-child(5) { --d: 690ms; }
  .js .lead-form__fields > .field:nth-child(6) { --d: 750ms; }
  .js .hero__body .focus-list > li { --d: calc(450ms + var(--i, 0) * 60ms); }
  .js .hero__body .btn { --d: 950ms; }
  .js .hero__body .form-alt { --d: 1010ms; }

  /* The photo does not gate the copy: it starts at 0ms and takes its time. */
  .js .hero__media > picture {
    opacity: 0.85;
    transform: scale(1.05);
    transition: opacity 300ms var(--ease-settle),
                transform var(--dur-signature) var(--ease-draw);
  }

  .js .hero.is-in .hero__media > picture,
  .js .hero.is-in .hero__title,
  .js .hero.is-in .hero__lede,
  .js .hero.is-in .caption,
  .js .hero.is-in .field,
  .js .hero.is-in .form-status,
  .js .hero.is-in .btn,
  .js .hero.is-in .form-alt,
  .js .hero.is-in .focus-list > li { opacity: 1; transform: none; }

  /* Hover on hero-form buttons (Yellow feedback #5, 28 Oct). The reveal rule
     above pins transform:none and swaps in a 950ms-delayed transition, which
     cancelled the site-wide lift + shadow on every form button in a hero.
     Once revealed, only opacity keeps the stagger delay; the hover properties
     go back to the standard 0.2s curve and the lift is restored. */
  .js .hero.is-in .btn {
    transition: opacity var(--dur-reveal) var(--ease-settle) var(--d, 0ms),
                transform 0.2s var(--ease), background-color 0.2s var(--ease),
                color 0.2s var(--ease), box-shadow 0.2s var(--ease);
  }
  .js .hero.is-in .btn:hover { transform: translateY(-2px); }
  .js .hero.is-in .btn:active { transform: translateY(1px); }

  /* The header never animates. A regulator's site does not introduce its own
     navigation — the nav is chrome, and chrome that performs reads as a
     brochure. Listed explicitly so nobody adds it back by accident. */

  /* ---- 18.3 Section mapping --------------------------------------------
     Which band gets which variant, and the offsets that let a heading land
     before the copy it introduces. */
  .js .duo > p,
  .js .mission__body,
  .js .quote__text { --d: 150ms; }
  .js .quote__cite { --d: 300ms; }
  .js .services-split__panel > :nth-child(2) { --d: 150ms; }
  .js .services-split__panel > :nth-child(3) { --d: 300ms; }
  .js .photoband__inner > .cta-band__action,
  .js .cta-band .cta-band__action { --d: 150ms; }

  /* ---- 18.4 Photography -------------------------------------------------
     One treatment everywhere, because consistency is itself the clinical
     signal. The overscale is safe precisely because of the existing markup:
     every one of these <picture>s is position: absolute; inset: 0 inside an
     overflow: hidden container with the <img> at object-fit: cover, so 6%
     is cropped by the band, exposes no edge and moves no layout.

     Scrims (the ::before / ::after gradients) deliberately do NOT animate —
     they are the legibility guarantee for the text sitting on top and must
     be at full strength before any of it reveals. */

  /* A transform makes the <picture> a stacking context, which traps the
     z-index: -2 that this aside sets on the <img> and would repaint the photo
     over its own scrim and title. Pin the picture itself instead. Applies
     unconditionally under .js, because the picture carries a transform in both
     its hidden and its settled state. */
  .js .services-split__aside > picture { z-index: -2; }

  /* ---- 18.5 Signature moment: the ruled ledger --------------------------
     The Process steps, revealed as an auditor rules off a checklist: coral
     numeral first, copy behind it, then the rule draws left-to-right and
     certifies the entry. Six rows at 120ms apart — the whole ledger is ruled
     in under 1.4s.

     This is the one place in the site that spends real craft, and the only
     place allowed to draw its rules. The real border-bottom goes transparent
     rather than to zero so the row keeps its 1px and nothing shifts. */
  .js .steps > .step { position: relative; border-bottom-color: transparent; }
  .js .steps > .step::after {
    content: "";
    position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
    background: rgba(255, 255, 255, 0.4);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--dur-statement) var(--ease-draw)
                calc(var(--d-step, 0ms) + 150ms);
  }
  .js .steps > .step:last-child::after { content: none; }
  .js .steps.is-in > .step::after { transform: scaleX(1); }

  .js .steps > .step > * {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-reveal) var(--ease-settle) var(--d, 0ms),
                transform var(--dur-reveal) var(--ease-settle) var(--d, 0ms);
  }
  /* Coral leads, every time. */
  .js .steps > .step > .step__num { --d: var(--d-step, 0ms); }
  .js .steps > .step > .step__title,
  .js .steps > .step > .step__body { --d: calc(var(--d-step, 0ms) + 80ms); }
  .js .steps.is-in > .step > * { opacity: 1; transform: none; }

  /* ---- 18.6 Micro-interactions -----------------------------------------
     Retimed onto the scale. The row's hover colour is the Figma one (§9) —
     coral on purple, purple on coral — and the arrow travels with it, so
     nothing here re-tints it. */
  .js .link-row {
    transition: color var(--dur-ui) var(--ease-steady),
                border-color var(--dur-ui) var(--ease-steady);
  }
  .js .link-row:hover { border-bottom-color: currentColor; }
  .js .link-row .arrow-btn { transition: transform var(--dur-ui) var(--ease-steady); }

  /* Accordions open a little slower than they close — opening is a reveal,
     closing is a dismissal. The open row's colour switch lives in §10, because
     it is design state rather than motion and must survive reduced motion. */
  .js .expand__panel { transition-duration: 350ms; }
  .js .expand:not(.is-open) .expand__panel { transition-duration: 250ms; }
  .js .expand__icon { transition: transform 300ms var(--ease-draw), color var(--dur-ui) var(--ease-steady); }
  .js .expand__title { transition: color var(--dur-ui) var(--ease-steady); }

  .js .expand__panel p {
    opacity: 0;
    transition: opacity var(--dur-ui) var(--ease-steady);
  }
  .js .expand.is-open .expand__panel p { opacity: 1; transition-delay: 100ms; }

  .js .field__label { transition-duration: 160ms; }
  .js .btn:active { transition-duration: var(--dur-press); transition-timing-function: var(--ease-press); }

  /* Mobile menu: the panel arrives, then its links table themselves 40ms
     apart. Closing has no stagger — the list goes at once and the panel fades
     over it, because nobody should wait to leave. */
  .js .nav { transition-duration: 300ms, 300ms, 300ms; }
  .js .nav:not(.is-open) { transition-duration: var(--dur-ui), var(--dur-ui), var(--dur-ui); }
  @media (max-width: 1023px) {
    .js .nav__list > li {
      opacity: 0;
      transform: translateY(8px);
      transition: opacity 250ms var(--ease-settle), transform 250ms var(--ease-settle);
    }
    .js .nav.is-open .nav__list > li {
      opacity: 1;
      transform: none;
      transition-delay: calc(80ms + var(--i, 0) * 40ms);
    }
  }
}

/* ---- Motion / print ------------------------------------------------------
   The no-preference gate above means this block never has hidden states to
   undo. It stays as the backstop for the transitions declared outside §18 and
   to kill smooth scrolling. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media print {
  .site-header, .nav-toggle, .btn { display: none !important; }
  body { color: #000; background: #fff; }
}

/* ==========================================================================
   17. Client feedback round 2 — Pastel, 5 Aug 2026
   https://usepastel.com/link/oed5x573/
   Kept together at the end of the sheet so the round is auditable as a unit and
   can be lifted out wholesale if the client changes their mind. Comment numbers
   match the Pastel thread; `node tools/verify-round2.mjs` asserts each one.
   ========================================================================== */

/* #15 — breathing room between the "Focusing on:" caption and the list under it. */
#focusing-on { margin-bottom: clamp(1rem, 2vw, 1.75rem); }

/* #26/#27 — "Services Required" is a resting label, exactly like the placeholder
   text in the sibling fields, so it has to read in the same grey. It switches to
   solid white once the user actually picks something, which is the same journey
   a typed input makes from placeholder to value.
   `.is-filled` is already set on the wrapper by initMultiselect() in main.js,
   so this reuses that hook rather than adding a second source of truth. */
.field__toggle-label { color: #757575; }
.multiselect.is-filled .field__toggle-label,
.field__toggle[aria-expanded="true"] .field__toggle-label { color: var(--white); }

@media (max-width: 1023px) {
  /* #28 — the open mobile menu draws a rule under every link as a separator.
     The last one (Contact) has nothing after it to separate, so it just reads as
     a stray line under the menu. */
  .nav__list li:last-child .nav__link { border-bottom-color: transparent; }

  /* #31 — mobile/tablet footer rebuilt to her reference layout
     (feedback/round2-pastel/mobile-footer-reference.jpg): five nav links in two
     columns, with the social icons pulled up into the empty second-column cell
     beside ABOUT rather than dropped on a row of their own.
     The nav wrappers dissolve so every <li> becomes a cell of the footer grid —
     that is what lets a sibling (.social) share the same rows. Placement is
     explicit rather than auto-flowed because auto-placement fills column 2 from
     wherever the cursor happens to be, which puts the links on the wrong rows. */
  .site-footer__top {
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(1rem, 3vw, 3.5rem);
    row-gap: clamp(0.5rem, 1vw, 1.375rem);
  }
  .logo--footer { grid-column: 1 / -1; margin-bottom: clamp(1.75rem, 3vw, 2.5rem); }
  .footer-nav, .footer-nav__col { display: contents; }
  .footer-nav__col:nth-child(1) li:nth-child(1) { grid-area: 2 / 1; }
  .footer-nav__col:nth-child(1) li:nth-child(2) { grid-area: 3 / 1; }
  .footer-nav__col:nth-child(1) li:nth-child(3) { grid-area: 4 / 1; }
  .footer-nav__col:nth-child(2) li:nth-child(1) { grid-area: 2 / 2; }
  .footer-nav__col:nth-child(2) li:nth-child(2) { grid-area: 3 / 2; }
  .social { grid-area: 4 / 2; align-self: center; }

  /* The legal row stacks — links first, copyright beneath — for the whole
     mobile/tablet range, not just small phones. Left as a side-by-side flex row
     between 600 and 1023px it pushed the links off to the right of the
     copyright, which broke the column alignment on tablets specifically.
     `stretch` matters too: the previous `flex-start` let the links block
     shrink-wrap, so its two tracks came out narrower than the nav's. */
  .site-footer__legal { flex-direction: column; align-items: stretch; gap: 0.5rem; }
  .site-footer__legal .copyright { order: 1; margin-right: 0; margin-top: 0.5rem; opacity: 0.85; }
  .site-footer__links { align-self: stretch; }

  /* #30 — drop the legal row to ~10pt on small screens. */
  .site-footer__legal,
  .site-footer__legal a,
  .site-footer__legal .copyright { font-size: 0.8125rem; }

  /* 8 Oct (Sid): the legal links run on ONE line at every width, not the 2x2
     grid. On phones the type scales with the viewport so all four still fit
     (~9.5px at 360px, 13px from ~470px up). */
  .site-footer__links {
    display: flex; flex-wrap: nowrap; justify-content: space-between;
    gap: 0.5rem; white-space: nowrap;
  }
  .site-footer__links a { font-size: min(0.8125rem, calc((100vw - 4rem) * 0.032)); }
}

/* #30 cont. — the coarse-pointer block grows legal links to a 44px tap target;
   that is a height floor, not a licence to put the type back up to 15px. */
@media (pointer: coarse) and (max-width: 1023px) {
  .site-footer__legal a { font-size: 0.8125rem; }
  .site-footer__links a { font-size: min(0.8125rem, calc((100vw - 4rem) * 0.032)); }
}
