/* ==========================================================================
   Docintosh
   --------------------------------------------------------------------------
   Layer order is declared once, here, before any import. Everything after
   obeys it regardless of file order or selector specificity, which is what
   makes a hand-written stylesheet stay predictable as it grows.
   ========================================================================== */

@layer tokens, base, layout, components, utilities;

/* ---- tokens.css ---- */
@layer tokens {
/* ==========================================================================
   Docintosh design tokens
   --------------------------------------------------------------------------
   Colour strategy is Committed: navy carries 30 to 50 percent of surface area
   across the site, arriving in large planes rather than small UI accents.

   One hard rule, enforced by the content lint: --accent-bright never appears
   on a light surface. It clears AA on navy at 8.1:1 and fails it on white at
   2.6:1. Keeping brand cyan exclusively on navy is what separates this from
   the generic healthcare-teal the category defaults to.
   ========================================================================== */

@layer tokens {
  :root {
    /* --- colour ------------------------------------------------------- */

    /* Navy. Flood planes, dark sections, primary buttons, headings. */
    --ink: #071c38;          /* brand navy, unchanged. 15.9:1 on white */
    --ink-deep: #04121f;     /* the one near-black break section per page */
    --ink-mid: #16304f;      /* elevated cards inside navy sections */
    --ink-soft: #23305c;     /* brand secondary navy, hover on dark */

    /* Neutrals. Cool, so cards read as floating rather than merged. */
    --slate-700: #46536b;    /* secondary text, grey half of two-tone heads  7.4:1 */
    /* Meta text. Checked against --base (#F2F4F7), not white: most meta sits
       on the grey page, where the old #6B7689 fell to 4.2:1 and failed AA.
       This clears 4.5:1 on grey and 5.9:1 on white. */
    --slate-500: #626c7e;
    --slate-300: #a8b0be;    /* decorative and disabled only, never body text */
    --line: #e3e7ec;         /* hairlines, card borders */
    --line-strong: #d3d7de;  /* inputs, stronger dividers */
    /* The page is white, like the reference. --base is no longer the page: it
       is the sunken fill used for banded sections, hover states and inset
       panels, which is the only job it was actually doing.

       Flipping this improves contrast rather than costing it: --slate-500 goes
       from 4.5:1 to 5.9:1 and --accent from 5.5:1 to 6.1:1, because both were
       tuned against the grey and now sit on white. */
    --page: #ffffff;         /* page background */
    --base: #f2f4f7;         /* sunken bands, hover fills, inset panels */
    --paper: #ffffff;        /* card surface */

    /* Accents. */
    --accent-bright: #15c7d8; /* brand cyan. ON NAVY ONLY */
    /* Teal for links and accent text on light surfaces. Derived from brand
       teal #0AAEBD, which sits at 2.6:1 on white and fails AA outright: the
       current site uses it for links. Checked against --base, not white,
       since most links sit on the grey page. 5.5:1 there, 6.1:1 on white. */
    --accent: #0a6d77;
    --accent-ink: #06474f;    /* pressed state for the above */
    --amber: #e7a22e;         /* on dark planes only */
    --amber-deep: #8a5a06;    /* AA-safe amber for text on light   5.4:1 on --base */
    --success: #1a9e5f;
    --error: #c8362b;

    /* --- tints and washes ------------------------------------------------
       Pale grounds for status and category, and the light greys that carry
       text on navy. These existed as fifty-seven loose hex values before they
       existed as a palette, which is why a success chip on /students/ and one
       on /brands/ were two different greens. Same lesson as the radius scale:
       the system was real, it just had no names. */
    --tint-success: #e9f7f2;
    --ink-success: #0a6b53;
    --tint-error: #fdeceb;
    --ink-error: #9c2b1f;
    --tint-mint: #d9f3e3;
    --tint-teal: #e6f6f8;
    --tint-teal-strong: #c2e6ea;
    --tint-teal-soft: #cdeef2;
    --tint-amber: #fdf3e3;
    --tint-amber-strong: #f0dcb8;
    --tint-amber-soft: #fdf6ea;
    --ink-amber: #6d4805;
    --ink-amber-deep: #4a3204;
    --tint-page-cool: #e8eef4;
    --tint-page-soft: #e9edf3;
    /* The mid stop of the /doctors/ media plate and the ground of the students
       board card. Both used to be one-off hex, and one of them was hiding
       behind `var(--surface-2, #eef0f3)` â€” a fallback for a token that has
       never existed anywhere in this codebase, so every render used the
       fallback. A fallback nobody defines is a hard-coded colour in a token's
       clothing. */
    --tint-page-mist: #eef1f6;

    /* Text and hairlines on navy. Not --slate-*: those are tuned for dark ink
       on a light ground and go muddy when the relationship inverts. */
    --text-on-dark: #b9c6d8;
    --text-on-dark-strong: #d7e0ec;
    --text-on-dark-soft: #d7e2ef;

    /* Scrims. Black, not navy: a navy scrim over a photograph tints the skin
       tones in it, which on documentary clinical photography reads as a
       colour cast rather than as shade. */
    --black: #000;
    --scrim: rgb(0 0 0 / 0.55);
    --scrim-soft: rgb(0 0 0 / 0.45);

    /* --- activity-type chips ---------------------------------------------
       Six activity types, each a pale ground paired with its --sys-*-deep ink,
       plus the same six at full saturation for the wordless skeleton mockups.
       They read as one set because they are one set; written out as twelve
       loose hex values they had already started to drift apart. */
    --chip-survey: #ffe9e7;
    --chip-review: #d6f2f0;
    --chip-profile: #e5e5fa;
    --chip-coach: #d9f3e3;
    --chip-case: #dbe9ff;
    --chip-quiz: #ffedd2;
    --chip-survey-solid: #ffb4ab;
    --chip-review-solid: #8fd9d2;
    --chip-profile-solid: #b9b9f2;
    --chip-coach-solid: #96dcb2;
    --chip-case-solid: #a8c9ff;
    --chip-quiz-solid: #ffd694;

    /* Crosstab heat. Intensity carries magnitude so the table reads as a
       heatmap at a glance rather than as a wall of digits. */
    --heat-1: rgb(11 124 135 / 0.08);
    --heat-2: rgb(11 124 135 / 0.16);
    --heat-3: rgb(11 124 135 / 0.28);

    /* The one warm practical on the students plane. */
    --glow-amber: rgb(255 205 120 / 0.2);

    /* --- system hues ----------------------------------------------------
       Apple's system palette. One accent was never going to carry a grid of
       sixty specialties: with nothing to cycle through, the roundels read as
       one colour in five tints, which is exactly how they looked.

       These are FILLS FOR DARK SURFACES ONLY. Several fail AA on white
       outright, so each carries an on-light partner below and the content lint
       blocks the fill values outside a dark section.

       systemPurple and systemBrown are deliberately absent: no purple, and
       brown reads as neither medical nor Apple in a saturated grid.

       Used by the home page specialty drift and nothing else. Adopting them
       elsewhere is a separate change with its own contrast pass. */
    --sys-red: #ff3b30;
    --sys-orange: #ff9500;
    --sys-yellow: #ffcc00;
    --sys-green: #34c759;
    --sys-mint: #00c7be;
    --sys-cyan: #32ade6;
    --sys-blue: #007aff;
    --sys-indigo: #5856d6;
    --sys-pink: #ff2d55;

    /* On-light partners. Each clears 4.5:1 on white and on --base. */
    --sys-red-deep: #b4241b;
    --sys-orange-deep: #8a5100;
    --sys-yellow-deep: #6f5600;
    --sys-green-deep: #0f7a3d;
    --sys-mint-deep: #06716c;
    --sys-cyan-deep: #0f6592;
    --sys-blue-deep: #0057b8;
    --sys-indigo-deep: #3b39a8;
    --sys-pink-deep: #b3123a;

    /* Rich black. The drift sits on this rather than on --ink-deep: navy
       behind saturated system colours tints every one of them toward the
       cool end, which is half of why the grid read flat. */
    --rich-black: #080808;

    /* Full-bleed hero planes. Each audience gets its own field, so the pages
       do not all open on the same navy rectangle. */
    --plane-navy: linear-gradient(155deg, #0a2444 0%, #071c38 45%, #04121f 100%);
    --plane-amber: linear-gradient(155deg, #2a1c05 0%, #3d2a08 42%, #1b1204 100%);
    /* The teal plane's base stop, named because .plane--photo-teal paints it
       as a flat background-color behind its gradient. */
    --plane-teal-base: #04181d;
    --plane: var(--plane-navy);
    --plane-pop: var(--accent-bright);

    /* Semantic aliases. Components reference these, never raw values. */
    --text: var(--ink);
    --text-muted: var(--slate-700);
    --text-meta: var(--slate-500);
    --surface: var(--paper);
    --surface-sunken: var(--base);
    --border: var(--line);
    --link: var(--accent);

    /* --- type ---------------------------------------------------------- */

    --font-display: "Figtree", "Segoe UI", system-ui, sans-serif;
    --font-body: "Inter", "Segoe UI", system-ui, sans-serif;

    /* Fluid, ratio >= 1.4 between steps. Flat scales read as uncommitted. */
    --size-hero: clamp(2.75rem, 7.5vw, 5rem);
    --size-display: clamp(2.25rem, 5vw, 3.5rem);
    --size-h2: clamp(1.75rem, 3.5vw, 2.5rem);
    --size-h3: clamp(1.25rem, 2vw, 1.5rem);
    --size-h4: 1.625rem;      /* 26px. Card and sub-card titles. */
    /* The shared figure display. Two files had this exact clamp written out
       longhand, which is how a "one-off" quietly becomes a step. */
    --size-figure: clamp(2rem, 4vw, 2.75rem);
    --size-lead: clamp(1.125rem, 1.7vw, 1.375rem);
    --size-body: 1.0625rem;   /* 17px. Larger than default, deliberately */
    --size-small: 0.875rem;
    --size-eyebrow: 0.75rem;  /* 12px. THE FLOOR for sentence-case UI text. */

    /* 11px. Uppercase tracked labels ONLY: caps have no descenders and the
       tracking opens them up, so they hold at a size sentence case does not.
       Never use this for a sentence, a number to be compared, or a name.

       The scale used to stop at --size-eyebrow, so thirty-eight components
       invented their own value below it and the site ended up with real text
       rendering at 9, 10 and 11px in places nobody had looked. Naming the two
       legitimate steps is what stops that happening again; the floor is
       enforced by tests/e2e/legibility.spec.js, which reads computed sizes in
       a real browser rather than trusting the stylesheet. */
    --size-micro: 0.6875rem;

    /* --- the control scale ----------------------------------------------
       The ramp above is editorial: it runs from a hero down to a caption and
       it governs things that are read. Chrome is not read, it is operated,
       and it was quietly using its own three sizes in forty places because
       nothing here named them.

       --size-ui-lg is 16px and inputs must not go below it. Mobile Safari
       zooms the viewport when a focused input is smaller than 16px, which
       throws the layout sideways mid-typing on the one form that matters
       most. That is a functional floor, not a taste. */
    --size-ui-lg: 1rem;         /* 16px. Inputs and prominent controls. */
    --size-ui: 0.9375rem;       /* 15px. Nav links, buttons, card bodies. */
    --size-ui-sm: 0.8125rem;    /* 13px. Notes, chips, secondary controls. */

    /* Mockup interiors ONLY. The phone, laptop and report facsimiles are
       pictures of an interface rendered at a fraction of life size; their
       captions are texture, not copy, and they sit inside aria-hidden frames.
       Using any of these on real text is the bug these tokens exist to make
       obvious in review. */
    --size-mock: 0.625rem;      /* 10px */
    --size-mock-sm: 0.5625rem;  /*  9px */
    --size-mock-xs: 0.5rem;     /*  8px */

    --leading-hero: 1.02;
    --leading-display: 1.08;
    --leading-h2: 1.15;
    --leading-h3: 1.3;
    --leading-lead: 1.55;
    --leading-body: 1.7;

    --tracking-hero: -0.04em;
    --tracking-display: -0.035em;
    --tracking-h2: -0.02em;
    --tracking-eyebrow: 0.1em;

    --measure: 66ch;

    /* --- space --------------------------------------------------------- */

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --space-12: 3rem;
    --space-16: 4rem;
    --space-24: 6rem;
    --space-32: 8rem;
    --space-40: 10rem;
    --space-50: 12.5rem;

    --container: 1180px;
    --gutter: 1.5rem;              /* 24px at 375 */
    /* Was 96/128/160. The reference runs tighter than that and the homepage
       was reading as a series of separated posters rather than one page. */
    --section-y: var(--space-16);  /* 64px, lifts to 112px on wide screens */
    --section-y-tight: var(--space-12);

    /* --- shape --------------------------------------------------------- */

    /* Nine steps, and every one of them has a job. The system documented four
       while the CSS used seventeen, which is how a card ended up at 18px next
       to one at 20px for no reason anybody could state. The values below are
       the ones that were doing real work; the rest collapsed onto them. */
    --radius-hair: 2px;      /* the active nav mark, the OTP caret, tick rules */
    --radius-focus: 4px;     /* the focus ring only */
    --radius-chip: 8px;      /* chips, table cells, small marks, confetti */
    --radius-input: 12px;    /* inputs, options, rows, small controls */
    --radius-soft: 1rem;     /* 16px. The softer control shape the sign-in
                                panel brought with it, kept on purpose. */
    --radius-card: 20px;
    --radius-media: 28px;
    --radius-device: 38px;   /* the phone frame's outer shell */
    --radius-pill: 999px;

    /* Elevation is real here. It is how a white card separates from the
       cool grey base, and every reference does it this way. */
    --shadow-rest: 0 1px 2px rgb(7 28 56 / 0.04), 0 8px 24px rgb(7 28 56 / 0.06);
    --shadow-raised: 0 1px 2px rgb(7 28 56 / 0.05), 0 12px 32px rgb(7 28 56 / 0.1),
      0 2px 6px rgb(7 28 56 / 0.04);
    --shadow-nav: 0 1px 2px rgb(7 28 56 / 0.04), 0 8px 28px rgb(7 28 56 / 0.08);

    /* --- motion -------------------------------------------------------- */

    --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
    --dur-micro: 160ms;   /* hover, focus, colour */
    --dur-state: 240ms;   /* toggles, accordions, checks */
    --dur-enter: 440ms;   /* scroll reveal */
    --stagger: 70ms;

    --focus-ring: 2px solid var(--accent);
    --focus-offset: 2px;
  }

  @media (min-width: 768px) {
    :root {
      --gutter: 2.5rem;             /* 40px */
      --section-y: var(--space-24); /* 96px */
      --section-y-tight: var(--space-16);
    }
  }

  @media (min-width: 1280px) {
    :root {
      --gutter: 4rem;               /* 64px */
      --section-y: 7rem;            /* 112px */
      --section-y-tight: var(--space-16);
    }
  }

  /* Inside a navy plane every semantic alias flips. Components do not need to
     know whether they are on light or dark; they read the alias.

     .statcard--filled and .strio__card--dark paint a navy ground without ever
     joining this list, so --text-muted inside them stayed #46536b: dark grey
     on navy. Neither ever shipped looking broken, because each one hand-patched
     its own text back to a white at some opacity â€” 0.78 here, 0.86 there, #fff
     somewhere else. That is the tell. A surface that paints itself dark and
     then repairs its own text one declaration at a time is a surface that
     never declared what it was, and the patches read as washed-out grey
     because an opacity over an unknown ground is not a colour you can choose.

     Any element that paints a dark ground belongs on this list. */
  .dark,
  .statcard--filled,
  .strio__card--dark {
    --text: var(--paper);
    --text-muted: #b9c6d8;
    --text-meta: #8fa1ba;
    --surface: var(--ink-mid);
    --surface-sunken: var(--ink-deep);
    --border: rgb(255 255 255 / 0.12);
    --link: var(--accent-bright);
    --focus-ring: 2px solid var(--accent-bright);
  }
}

}
/* ---- base.css ---- */
@layer base {
/* ==========================================================================
   Reset, document defaults, typography
   ========================================================================== */

@layer base {
  /* --- fonts ---------------------------------------------------------- */
  /* Self-hosted. No request leaves the visitor's browser to Google, which
     under DPDPA avoids shipping visitor IP addresses to a third party with no
     consent basis. Run `node scripts/fetch-fonts.js` to populate. */

  /* Figtree carries headings, Inter carries copy. See scripts/fetch-fonts.js
     for why each was chosen; the short version is that Figtree is the more
     characterful voice at display sizes and Inter is the better reader at 12
     to 15px, and neither is good at the other's job.

     Figtree starts at 500 rather than 400 on purpose. A variable font clamps a
     request outside its declared range to the nearest end, so any heading rule
     that forgets to set a weight still lands at 500 instead of rendering thin. */
  @font-face {
    font-family: "Figtree";
    src: url("../assets/fonts/figtree-latin.woff2") format("woff2");
    font-weight: 500 800;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: "Inter";
    src: url("../assets/fonts/inter-latin.woff2") format("woff2");
    font-weight: 400 600;
    font-style: normal;
    font-display: swap;
  }

  /* --- reset ---------------------------------------------------------- */

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

  * {
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    /* Anchored headings must not hide under the sticky nav. */
    scroll-padding-top: 6rem;
  }

  body {
    min-height: 100svh;
    background: var(--page);
    color: var(--text);
    font-family: var(--font-body);
    font-size: var(--size-body);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    /* Belt and braces against a rogue wide child. The e2e suite asserts
       body.scrollWidth <= innerWidth at every breakpoint. */
    overflow-x: clip;
  }

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

  /* Put the hidden attribute back.

     The rule above is an author rule inside a layer, and any author rule beats
     the UA stylesheet's [hidden] { display: none } no matter how weak its
     selector is. So <svg hidden> stopped being hidden: the inlined icon sprite
     rendered at the replaced-element default of 300x150 and put a white band
     across the top of every page on the site. */
  [hidden] {
    display: none !important;
  }

  img,
  video {
    height: auto;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
    color: inherit;
  }

  button {
    background: none;
    border: 0;
    cursor: pointer;
  }

  ul[class],
  ol[class] {
    list-style: none;
    padding: 0;
  }

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

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

  h1 {
    font-size: var(--size-display);
    line-height: var(--leading-display);
    letter-spacing: var(--tracking-display);
  }

  h2 {
    font-size: var(--size-h2);
    line-height: var(--leading-h2);
    letter-spacing: var(--tracking-h2);
    font-weight: 600;
  }

  h3 {
    font-size: var(--size-h3);
    line-height: var(--leading-h3);
    font-weight: 600;
  }

  p,
  li {
    text-wrap: pretty;
  }

  a {
    color: var(--link);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
    transition: color var(--dur-micro) var(--ease-out-quint);
  }

  a:hover {
    color: var(--accent-ink);
  }

  .dark a:hover {
    color: var(--paper);
  }

  strong,
  b {
    font-weight: 600;
  }

  /* Figures are read and compared, so they need consistent column width. */
  .num,
  time,
  td[data-numeric] {
    font-variant-numeric: tabular-nums;
  }

  /* --- focus ---------------------------------------------------------- */
  /* Always visible, never removed. :focus-visible keeps it off mouse clicks
     while guaranteeing it for keyboard traversal. */

  :focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
    border-radius: var(--radius-focus);
  }

  /* --- utilities ------------------------------------------------------ */

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

  .skip-link {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: 100;
    padding: var(--space-3) var(--space-4);
    background: var(--ink);
    color: var(--paper);
    border-radius: var(--radius-input);
    font-family: var(--font-display);
    font-weight: 600;
    text-decoration: none;
    transform: translateY(-150%);
    transition: transform var(--dur-micro) var(--ease-out-quint);
  }

  .skip-link:focus-visible {
    transform: translateY(0);
    color: var(--paper);
  }

  /* Motion preferences are handled unlayered at the end of main.css, not
     here. A rule in @layer base cannot override one in @layer components, so
     an accessibility guarantee written at this level would be silently inert. */
}

}
/* ---- layout.css ---- */
@layer layout {
/* ==========================================================================
   Layout: containers, section rhythm, the shapes sections come in
   --------------------------------------------------------------------------
   Section rhythm varies deliberately: tight groupings inside a section,
   generous separation between. Identical padding everywhere is monotony.
   ========================================================================== */

@layer layout {
  .container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .container--narrow {
    max-width: 780px;
  }

  .container--prose {
    max-width: var(--measure);
  }

  /* --- sections -------------------------------------------------------- */

  .section {
    padding-block: var(--section-y);
  }

  .section--tight {
    padding-block: var(--section-y-tight);
  }

  .section--flush-top {
    padding-block-start: 0;
  }

  .section--sunken {
    background: var(--base);
  }

  .section--paper {
    background: var(--paper);
  }

  /* Rich black, for a break carrying saturated colour rather than brand navy.
     Navy behind Apple's system palette tints every hue toward the cool end. */
  .section--rich {
    background: var(--rich-black) !important;
  }

  /* The one near-black break per page. Where brand cyan is allowed to live. */
  .section--dark {
    background: var(--ink-deep);
    color: var(--text);
    position: relative;
    isolation: isolate;
  }

  /* Grain kills gradient banding and reads as print texture rather than
     screen flatness. Decorative, so it is hidden from assistive tech. */
  .section--dark::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0.025;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  .section--navy {
    background: var(--ink);
    color: var(--text);
  }

  /* --- section headers -------------------------------------------------- */

  .section-head {
    max-width: 46rem;
    margin-block-end: var(--space-8);
  }

  .section-head--center {
    margin-inline: auto;
    text-align: center;
  }

  .section-head > * + * {
    margin-block-start: var(--space-4);
  }

  .lead {
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-muted);
    max-width: 52ch;
  }

  .section-head--center .lead {
    margin-inline: auto;
  }

  /* --- grids ------------------------------------------------------------ */
  /* auto-fit + minmax means these respond without a single media query. */

  .grid {
    display: grid;
    gap: var(--space-6);
  }

  .grid--cards {
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  }

  .grid--wide {
    grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  }

  /* Exactly three, not auto-fit. auto-fit gives four columns on a wide
     viewport whenever the content happens to be narrow, which is right for a
     card wall and wrong for a fixed set of three quotes. */
  .grid--three {
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  }

  @media (min-width: 940px) {
    .grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }

  .grid--halves {
    grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
    gap: var(--space-12);
    align-items: center;
  }

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

  .prose {
    max-width: var(--measure);
  }

  .prose > * + * {
    margin-block-start: var(--space-6);
  }

  .prose h2 {
    margin-block-start: var(--space-16);
  }

  .prose h3 {
    margin-block-start: var(--space-12);
  }

  .prose ul,
  .prose ol {
    padding-inline-start: var(--space-6);
    display: grid;
    gap: var(--space-3);
  }

  .prose li::marker {
    color: var(--text-meta);
  }

  /* --- stack ------------------------------------------------------------ */

  .stack > * + * {
    margin-block-start: var(--space-4);
  }

  .stack-6 > * + * {
    margin-block-start: var(--space-6);
  }

  .stack-8 > * + * {
    margin-block-start: var(--space-8);
  }

  .cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: center;
  }

  /* Primary CTA pairs need more room between them than a default cluster. */
  .cluster--cta {
    gap: var(--space-4);
    margin-block-start: var(--space-8);
  }
}

}
/* ---- components.css ---- */
@layer components {
/* ==========================================================================
   Components
   --------------------------------------------------------------------------
   No two adjacent sections should share a shape. The section types here are
   deliberately varied: photo cards with overlay pills, a horizontal stat rail,
   a numbered process row, a two-column split, an accordion, a big-number
   moment, and the dark break. Identical icon-heading-text card grids repeated
   down a page are the single most template-looking pattern available.
   ========================================================================== */

@layer components {
  /* ======================================================================
     Eyebrow. Budget of four per page, enforced by the content lint.
     A tracked uppercase label above every heading is template scaffolding;
     used sparingly it is a brand system.
     ====================================================================== */

  .eyebrow {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .eyebrow::before {
    content: "";
    inline-size: 5px;
    block-size: 5px;
    border-radius: 50%;
    background: var(--accent);
    flex: none;
  }

  .dark .eyebrow::before {
    background: var(--accent-bright);
  }

  .section-head--center .eyebrow {
    justify-content: center;
  }

  /* ======================================================================
     Icons.

     One sprite is inlined into every page by the layout, and glyphs are
     referenced with <use>. Colour comes from currentColor, so an icon takes
     the colour of whatever it sits in without a per-icon rule.
     ====================================================================== */

  .icon {
    inline-size: 1.25em;
    block-size: 1.25em;
    flex: none;
    /* <use> content inherits this, which is what lets one sprite serve both
       Lucide's stroke work and healthicons' filled outlines. */
    color: currentColor;
  }

  /* ======================================================================
     Two-tone headline. Bold dark first line, lighter grey second.
     ====================================================================== */

  .title-two-tone > span {
    display: block;
  }

  .title-two-tone > span:last-child {
    font-weight: 500;
    color: var(--text-muted);
  }

  /* ======================================================================
     Buttons. Pill, 52px, navy fill. Fill shifts on hover, never scale:
     a scaling CTA reads as a sales page.
     ====================================================================== */

  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-block-size: 52px;
    padding-inline: var(--space-8);
    border-radius: var(--radius-pill);
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
    text-decoration: none;
    text-align: center;
    /* Long labels must wrap rather than push the page sideways. A pill that
       runs to two lines is untidy; a page that scrolls horizontally is broken. */
    white-space: normal;
    max-inline-size: 100%;
    transition:
      background-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint),
      border-color var(--dur-micro) var(--ease-out-quint);
  }

  .btn--primary {
    background: var(--ink);
    color: var(--paper);
  }

  .btn--primary:hover {
    background: var(--ink-soft);
    color: var(--paper);
  }

  .btn--primary:active {
    background: var(--ink-deep);
  }

  .dark .btn--primary {
    background: var(--paper);
    color: var(--ink);
  }

  .dark .btn--primary:hover {
    background: var(--paper);
    color: var(--accent);
  }

  .btn--secondary {
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--line-strong);
  }

  .btn--secondary:hover {
    color: var(--ink);
    border-color: var(--ink);
    background: var(--paper);
  }

  .dark .btn--secondary {
    background: transparent;
    color: var(--paper);
    border-color: rgb(255 255 255 / 0.28);
  }

  .dark .btn--secondary:hover {
    border-color: var(--accent-bright);
    color: var(--accent-bright);
  }

  .btn--full {
    inline-size: 100%;
  }

  /* Below this the generous pill padding is most of the available width. */
  @media (max-width: 400px) {
    .btn {
      padding-inline: var(--space-6);
      font-size: var(--size-ui);
    }
  }

  /* Text link with a travelling arrow. Standalone rather than inline in a
     paragraph, so it carries a full touch target. */
  .link-arrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    font-family: var(--font-display);
    font-weight: 600;
    text-decoration: none;
  }

  /* Inside running text it becomes an inline link again: forcing 44px there
     would break the line rhythm it sits in. */
  p > .link-arrow,
  li > .link-arrow {
    min-block-size: 0;
  }

  .link-arrow svg {
    transition: transform var(--dur-micro) var(--ease-out-quint);
  }

  .link-arrow:hover svg {
    transform: translateX(3px);
  }

  /* ======================================================================
     Cards
     ====================================================================== */

  .card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: var(--space-8);
    box-shadow: var(--shadow-rest);
    transition:
      transform var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint),
      border-color var(--dur-micro) var(--ease-out-quint);
  }

  .dark .card {
    box-shadow: none;
  }

  a.card,
  .card--interactive {
    text-decoration: none;
    color: inherit;
    display: block;
  }

  a.card:hover,
  .card--interactive:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-raised);
    border-color: var(--line-strong);
  }

  .card__title {
    font-family: var(--font-display);
    font-size: var(--size-h3);
    font-weight: 600;
    line-height: var(--leading-h3);
  }

  .card__body {
    color: var(--text-muted);
    margin-block-start: var(--space-3);
  }

  /* ======================================================================
     Photo card with overlay pill.
     Image scales inside a fixed-radius mask while the card itself stays put:
     motion without layout shift.
     ====================================================================== */

  /* Two across rather than four. Vitaal runs these as a 2x2, and at four
     across the pill label and the info chip have no room to breathe. */
  .photogrid {
    display: grid;
    gap: var(--space-6);
  }

  @media (min-width: 720px) {
    .photogrid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  .photo-card {
    position: relative;
    display: block;
    border-radius: var(--radius-card);
    overflow: hidden;
    background: var(--ink);
    isolation: isolate;
    /* These are links on the home page and articles elsewhere, so the link
       treatment has to be neutralised: the whole card is the target. */
    text-decoration: none;
    color: inherit;
    transition: transform var(--dur-micro) var(--ease-out-quint);
  }

  a.photo-card:hover {
    transform: translateY(-3px);
  }

  a.photo-card:hover .photo-card__pill {
    background: var(--ink);
    color: var(--paper);
  }

  .photo-card__pill,
  .photo-card__chip {
    transition:
      background-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint);
  }

  /* display: contents so the picture element leaves no box of its own behind.
     The image then participates in the card's layout exactly as the placeholder
     div used to, and none of the positioning below has to change. */
  .photo-card picture {
    display: contents;
  }

  .photo-card__media {
    inline-size: 100%;
    /* Was 4:3. Shorter cards let the whole grid sit in one screen on a
       laptop, which is what the reference does and what the page was
       losing to height. */
    aspect-ratio: 16 / 11;
    object-fit: cover;
    /* Subjects sit in the upper half of all four photographs, because the
       lower 60% is scrimmed and the bottom third is under the chip. Biasing
       the crop upward keeps faces clear of both. */
    object-position: center 35%;
    transition: transform 600ms var(--ease-out-quint);
  }

  .photo-card:hover .photo-card__media {
    transform: scale(1.03);
  }

  /* The scrim is gone. It existed to seat a chip that sat flush against the
     photograph's bottom edge; the chip is now inset on all three sides with
     its own opaque background, so there is nothing left for a gradient to do
     except darken the picture. */

  /* Centred and near the top, at the reference's scale. It was a small
     top-left chip, which read as a tag on the image rather than as the card's
     name. */
  .photo-card__pill {
    position: absolute;
    inset-block-start: 7%;
    inset-inline: 0;
    z-index: 1;
    inline-size: fit-content;
    margin-inline: auto;
    padding: var(--space-3) var(--space-6);
    border-radius: var(--radius-pill);
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  /* Floats inside the image with a margin on all three sides, rather than
     spanning the card's full width against its edge. */
  .photo-card__chip {
    position: absolute;
    inset-inline: var(--space-4);
    inset-block-end: var(--space-4);
    z-index: 1;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-input);
    background: rgb(255 255 255 / 0.96);
    color: var(--ink);
    backdrop-filter: blur(8px);
  }

  .photo-card__chip .icon {
    grid-row: span 2;
    box-sizing: content-box;
    inline-size: 20px;
    block-size: 20px;
    padding: 10px;
    border-radius: 50%;
    background: rgb(10 109 119 / 0.12);
    color: var(--accent);
  }

  .photo-card__chip strong {
    align-self: end;
    font-family: var(--font-display);
    font-size: var(--size-ui);
    line-height: 1.25;
  }

  .photo-card__chip span {
    align-self: start;
    font-size: var(--size-ui-sm);
    line-height: 1.35;
    color: var(--slate-700);
  }

  /* ======================================================================
     Placeholder media. Every image slot renders at its real aspect ratio
     before the asset exists, so layout and spacing are final either way.
     ====================================================================== */

  .placeholder {
    display: grid;
    place-content: center;
    gap: var(--space-1);
    text-align: center;
    background:
      repeating-linear-gradient(
        135deg,
        rgb(7 28 56 / 0.04) 0 10px,
        transparent 10px 20px
      ),
      var(--base);
    border: 1px dashed var(--line-strong);
    color: var(--slate-500);
    font-family: var(--font-display);
    font-size: var(--size-small);
    padding: var(--space-6);
  }

  .placeholder strong {
    font-size: var(--size-eyebrow);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
  }

  /* Behind hero copy the label must sit out of the way, and read light
     because the scrim above it is dark. */
  .placeholder--behind {
    place-content: end;
    text-align: end;
    background:
      repeating-linear-gradient(
        135deg,
        rgb(255 255 255 / 0.05) 0 12px,
        transparent 12px 24px
      ),
      var(--ink-mid);
    border-color: rgb(255 255 255 / 0.12);
    color: rgb(255 255 255 / 0.5);
  }

  /* ======================================================================
     Big-number moment. One per page maximum.
     Never animated: animating a figure dramatises it, and in a system
     selling auditability a static number reads more credible.
     ====================================================================== */

  .bignum {
    text-align: center;
  }

  .bignum__value {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-hero);
    font-weight: 800;
    line-height: var(--leading-hero);
    letter-spacing: var(--tracking-hero);
    font-variant-numeric: tabular-nums;
  }

  .bignum__label {
    display: block;
    margin-block-start: var(--space-4);
    color: var(--text-muted);
    font-size: var(--size-lead);
  }

  /* ======================================================================
     Stat rail. Horizontal on wide screens, snap-scroll on narrow.
     ====================================================================== */

  .rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 85%;
    gap: var(--space-4);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-block-end: var(--space-4);
    /* Let cards bleed to the viewport edge while staying inside the grid. */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scrollbar-width: thin;
  }

  .rail > * {
    scroll-snap-align: start;
  }

  @media (min-width: 768px) {
    .rail {
      grid-auto-columns: minmax(0, 1fr);
      overflow-x: visible;
      margin-inline: 0;
      padding-inline: 0;
      gap: var(--space-6);
    }
  }

  /* ======================================================================
     Numbered process row
     ====================================================================== */

  .steps {
    counter-reset: step;
    display: grid;
    gap: var(--space-8);
  }

  @media (min-width: 900px) {
    .steps--row {
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-8);
    }
  }

  .step {
    position: relative;
    padding-inline-start: calc(var(--space-12) + var(--space-2));
    counter-increment: step;
  }

  .step::before {
    content: counter(step);
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: -2px;
    display: grid;
    place-content: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border-radius: 50%;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--font-display);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
  }

  .step__title {
    font-family: var(--font-display);
    font-size: var(--size-h3);
    font-weight: 600;
    line-height: var(--leading-h3);
  }

  .step__body {
    margin-block-start: var(--space-3);
    color: var(--text-muted);
  }

  /* The one-line note on what each step protects. */
  .step__note {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border);
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  /* ======================================================================
     Trust list, used inside the dark break
     ====================================================================== */

  .trust {
    display: grid;
    gap: var(--space-8);
  }

  @media (min-width: 860px) {
    .trust {
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-12);
    }
  }

  .trust__item > h3 {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: 1.125rem;
  }

  .trust__item > h3 svg {
    color: var(--accent-bright);
    flex: none;
  }

  .trust__item > p {
    margin-block-start: var(--space-3);
    color: var(--text-muted);
  }

  /* ======================================================================
     Quotes
     ====================================================================== */

  .quote {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: var(--space-8);
    box-shadow: var(--shadow-rest);
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    block-size: 100%;
  }

  .quote__text {
    font-size: 1.125rem;
    line-height: 1.6;
  }

  .quote__who {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-block-start: auto;
  }

  .quote__avatar {
    inline-size: 48px;
    block-size: 48px;
    border-radius: 50%;
    object-fit: cover;
    flex: none;
  }

  .quote__name {
    font-family: var(--font-display);
    font-weight: 600;
  }

  .quote__role {
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  /* ======================================================================
     Accordion. grid-template-rows avoids magic heights and never jumps on a
     long answer.
     ====================================================================== */

  .accordion {
    border-block-start: 1px solid var(--border);
  }

  .accordion__item {
    border-block-end: 1px solid var(--border);
  }

  .accordion__trigger {
    inline-size: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    padding-block: var(--space-6);
    text-align: start;
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--text);
    min-block-size: 44px;
  }

  .accordion__icon {
    flex: none;
    transition: transform var(--dur-state) var(--ease-out-quint);
  }

  .accordion__trigger[aria-expanded="true"] .accordion__icon {
    transform: rotate(45deg);
  }

  /* Open is the served state, and the script is what closes it. The default
     used to be collapsed, which meant that with JavaScript off every answer
     sat at zero height behind a button that could not open it. Inverting it
     costs nothing when the script runs and is the difference between readable
     and unreachable when it does not. */
  /* Opens slower than it closes. Opening is the reader asking for something
     and is worth watching arrive; closing is them dismissing it, and a
     dismissal that takes as long as a request feels like the interface
     arguing. 280ms out, 180ms back. */
  .accordion__panel {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 280ms var(--ease-out-quint);
  }

  .accordion__panel[data-open="false"] {
    grid-template-rows: 0fr;
    transition-duration: 180ms;
  }

  /* The plus becomes a cross rather than swapping for one. */
  .accordion__trigger[aria-expanded="true"] .accordion__icon {
    transform: rotate(135deg);
  }

  /* The number in the gutter takes the accent as its item opens: the cheapest
     possible "you are here" in a list of fourteen. */
  .abq__num {
    transition: color var(--dur-state) var(--ease-out-quint);
  }

  .accordion__trigger[aria-expanded="true"] .abq__num {
    color: var(--accent);
  }

  .accordion__inner {
    overflow: hidden;
  }

  .accordion__inner > div {
    padding-block-end: var(--space-6);
    color: var(--text-muted);
    max-width: var(--measure);
  }

  /* ======================================================================
     Anchor offsets

     The audience filter and the jump bar both lived here and both went with
     the /about/ rebuild: the filter because the page no longer has one, the
     jump bar because a numbered FAQ and four sections do not need a table of
     contents. What survives is the reason they were near each other.

     An anchored heading that lands under a sticky bar is a heading the reader
     never sees. This buys back the bar's height on every jump target.
     ====================================================================== */

  [id="story"],
  [id="faq"],
  [id="contact"],
  .accordion__item[id] {
    scroll-margin-block-start: var(--space-24, 6rem);
  }

  /* ======================================================================
     Specialties

     Two renderings of one list. The band prints all 61 and is the canonical
     copy; the marquee is for slots where 61 names will not fit, and never
     carries anything the band does not already state in full.
     ====================================================================== */

  .specband {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .specband > span,
  .specmarquee__set > span {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    border: 1px solid var(--line-strong);
    background: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 500;
    color: var(--slate-700);
  }

  /* The register's longest entry, "Specialist - Pain Management and Palliative
     Care", is wider than a 375px viewport's gutters. In the band it wraps to
     two lines and the pill grows; in the marquee it must not, because a
     wrapped chip changes the track's height mid-loop. */
  .specband > span {
    max-inline-size: 100%;
  }

  .specmarquee__set > span {
    white-space: nowrap;
  }

  .dark .specband > span,
  .dark .specmarquee__set > span {
    background: rgb(255 255 255 / 0.06);
    border-color: rgb(255 255 255 / 0.18);
    color: rgb(255 255 255 / 0.86);
  }

  /* ======================================================================
     The specialty finder â€” backlog C1, C2, C3
     ---------------------------------------------------------------------- */

  .specfind {
    position: relative;
    display: grid;
    gap: var(--space-5, 1.25rem);
  }

  .specfind__head {
    display: grid;
    gap: var(--space-2);
    max-inline-size: 28rem;
  }

  .specfind__label {
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--text-muted);
  }

  .specfind__search {
    min-block-size: 52px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-soft);
    background: var(--surface);
    font-family: var(--font-body);
    font-size: var(--size-ui-lg);
    color: var(--ink);
  }

  .specfind__search::placeholder { color: var(--text-meta); }

  .specfind__count,
  .specfind__none {
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  .specfind__none { max-inline-size: 46ch; }

  /* The list is the same wall of names as the chip band, made operable. The
     chip look carries over so the section does not change character when it
     gains a search field. */
  .specband--links {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .specband--links > li[hidden] { display: none; }

  .specband__link {
    display: inline-flex;
    align-items: center;
    min-block-size: 44px;
    max-inline-size: 100%;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    border: 1px solid var(--line-strong);
    background: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 500;
    color: var(--slate-700);
    text-decoration: none;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .specband__link:hover {
    border-color: var(--ink);
    background: var(--ink);
    color: var(--paper);
  }

  /* --- the card ---------------------------------------------------------- */

  .speccard {
    display: grid;
    gap: var(--space-4);
    justify-items: start;
    max-inline-size: 34rem;
    padding: var(--space-6);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
    box-shadow: var(--shadow-rest);
  }

  .speccard[hidden] { display: none; }

  .speccard__title {
    font-size: var(--size-h3);
    line-height: 1.15;
    letter-spacing: -0.02em;
  }

  .speccard__title:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 4px;
  }

  .speccard__body {
    font-size: var(--size-body);
    line-height: 1.6;
    color: var(--text-muted);
  }

  /* The figure, from the client's tracker, rounded down so the "+" is true.
     A specialty with too few registered doctors to state honestly hides this
     line rather than showing a small number beside 250,000+. */
  .speccard__count {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--size-h4);
    font-weight: 600;
    line-height: 1.2;
    color: var(--ink);
  }

  .speccard__count[hidden] { display: none; }

  .speccard__count b {
    font-size: var(--size-figure);
    font-weight: 800;
    letter-spacing: -0.03em;
  }

  .speccard__source {
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  .speccard__source[hidden] { display: none; }

  /* --- the tooltip --------------------------------------------------------
     Hover and focus, positioned over the name it belongs to. aria-hidden,
     because the card repeats the same figure as real text and a screen
     reader does not need a second copy of it. */

  .spectip {
    position: absolute;
    z-index: 20;
    transform: translate(-50%, -100%);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-chip);
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: var(--shadow-raised);
  }

  .spectip[hidden] { display: none; }

  .spectip::after {
    content: "";
    position: absolute;
    inset-block-start: 100%;
    inset-inline-start: 50%;
    inline-size: 8px;
    block-size: 8px;
    background: var(--ink);
    transform: translate(-50%, -50%) rotate(45deg);
  }

  .speccard__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
  }

  .speccard__back {
    min-block-size: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--link);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  .specmarquee {
    overflow: hidden;
    /* Inside a grid or flex parent the default min-width:auto refuses to
       shrink below the track's max-content width, so the row pushed the
       doctors page 5px past a 375px viewport even though overflow was
       hidden. Clipping does not shrink a box; this does. */
    min-inline-size: 0;
    max-inline-size: 100%;
    /* The row runs to the viewport edge rather than stopping at the container,
       which is what makes it read as a continuous register instead of a
       truncated list. */
    mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
  }

  .specmarquee__track {
    display: flex;
    inline-size: max-content;
    animation: specmarquee 90s linear infinite;
  }

  .specmarquee__set {
    display: flex;
    gap: var(--space-2);
    padding-inline-end: var(--space-2);
  }

  @keyframes specmarquee {
    from { transform: translateX(0); }
    /* -50% is one full set, so the second set lands exactly where the first
       began. Any other value visibly jumps. */
    to { transform: translateX(-50%); }
  }

  .specmarquee:hover .specmarquee__track,
  .specmarquee:focus-within .specmarquee__track {
    animation-play-state: paused;
  }

  /* ======================================================================
     Expandable register

     Sixty-one chips is a wall. It is also the proof, so it cannot simply be
     cut. <details> gives the disclosure for free: keyboard operable, announced
     as a disclosure, and open by default for anyone without CSS or scripting.
     ====================================================================== */

  .specdisclose > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-inline: var(--space-5, 1.25rem);
    border-radius: var(--radius-pill);
    border: 1px solid var(--line-strong);
    background: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
    list-style: none;
  }

  .specdisclose > summary::-webkit-details-marker { display: none; }

  .specdisclose > summary:hover { border-color: var(--ink); }

  .specdisclose > summary .icon {
    inline-size: 16px;
    block-size: 16px;
    color: var(--accent);
    transition: transform var(--dur-state) var(--ease-out-quint);
  }

  .specdisclose[open] > summary .icon { transform: rotate(180deg); }

  /* The label changes with the state, so the control says what it will do
     rather than what it currently is. */
  .specdisclose > summary .specdisclose__more { display: inline; }
  .specdisclose > summary .specdisclose__less { display: none; }
  .specdisclose[open] > summary .specdisclose__more { display: none; }
  .specdisclose[open] > summary .specdisclose__less { display: inline; }

  .specdisclose .specband { margin-block-start: var(--space-8); }

  /* ======================================================================
     Registered office

     Was a dark band on its own page. The merged page already spends its one
     dark break on governance, so this carries the same weight in light.
     ====================================================================== */

  .officecard {
    display: grid;
    gap: var(--space-6);
    padding: var(--space-8);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    /* Was --shadow-card, which is not a token. An undefined custom property
       makes the declaration invalid at computed-value time, so the card had
       no shadow at all and nothing said so. */
    box-shadow: var(--shadow-rest);
  }

  @media (min-width: 768px) {
    .officecard {
      grid-template-columns: 1fr auto;
      align-items: start;
      gap: var(--space-12);
      padding: var(--space-12);
    }
  }

  /* ======================================================================
     Forms
     ====================================================================== */

  .field {
    display: grid;
    gap: var(--space-2);
  }

  .field > label {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--size-small);
  }

  .field > .field__hint {
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  .field input,
  .field select,
  .field textarea {
    inline-size: 100%;
    min-block-size: 52px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-input);
    background: var(--paper);
    color: var(--ink);
    transition: border-color var(--dur-micro) var(--ease-out-quint);
  }

  .field textarea {
    min-block-size: 8rem;
    resize: vertical;
  }

  .field input:hover,
  .field select:hover,
  .field textarea:hover {
    border-color: var(--slate-500);
  }

  .field [aria-invalid="true"] {
    border-color: var(--error);
  }

  /* Errors sit beside the field they belong to, never only at the top. */
  .field__error {
    display: none;
    font-size: var(--size-small);
    color: var(--error);
    font-weight: 500;
  }

  .field__error[data-shown="true"] {
    display: block;
  }

  .checkbox {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-3);
    align-items: start;
    font-size: var(--size-small);
    color: var(--text-muted);
  }

  .checkbox input {
    inline-size: 20px;
    block-size: 20px;
    min-block-size: 0;
    margin-block-start: 2px;
    accent-color: var(--ink);
  }

  /* Honeypot. Off-screen rather than display:none so it stays out of the
     accessibility tree without being obviously hidden to a naive bot. */
  .honeypot {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* ======================================================================
     Tables. Scroll inside their own container so the page body never does.
     ====================================================================== */

  .table-scroll {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
  }

  .table-scroll table {
    inline-size: 100%;
    border-collapse: collapse;
    min-inline-size: 34rem;
  }

  .table-scroll th,
  .table-scroll td {
    padding: var(--space-4) var(--space-6);
    text-align: start;
    border-block-end: 1px solid var(--border);
    vertical-align: top;
  }

  .table-scroll th {
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
  }

  .table-scroll tr:last-child td {
    border-block-end: 0;
  }

  /* ======================================================================
     Notice block, used for compliance and legal callouts
     ====================================================================== */

  .notice {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: var(--space-6);
  }

  .notice > h3 {
    font-size: var(--size-ui-lg);
  }

  .notice > p {
    margin-block-start: var(--space-2);
    color: var(--text-muted);
    font-size: var(--size-small);
  }
}

}
/* ---- site.css ---- */
@layer components {
/* ==========================================================================
   Site chrome: nav, hero, audience rail, claims, footer, motion
   ========================================================================== */

@layer components {
  /* ======================================================================
     Floating pill nav
     ====================================================================== */

  .nav-wrap {
    position: sticky;
    inset-block-start: var(--space-3);
    z-index: 50;
    padding-inline: var(--gutter);
    margin-block-start: var(--space-3);
    /* The sticky wrapper must not intercept clicks on the page behind it. */
    pointer-events: none;
  }

  .nav {
    pointer-events: auto;
    max-width: var(--container);
    margin-inline: auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
    background: rgb(255 255 255 / 0.86);
    backdrop-filter: blur(14px);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-nav);
    view-transition-name: site-nav;
  }

  .nav__brand {
    display: flex;
    align-items: center;
    /* A 31px-tall home link is a real miss on a phone, and the brand mark is
       the target people reach for first. */
    min-block-size: 44px;
    text-decoration: none;
    flex: none;
  }

  .nav__logo {
    inline-size: auto;
    block-size: 26px;
  }

  /* The mark is two colours: a navy blob with a white "d" in it. A filter
     cannot preserve that â€” brightness(0) invert(1) drives both to white and
     the letter disappears into the blob, which is what made the wordmark
     unreadable over the hero. So the dark ground gets its own artwork, with
     the two colours swapped, and neither version is filtered.

     display rather than opacity: the hidden one should not be a transparent
     box sitting on top of the visible one. */
  .nav__logo--light {
    display: none;
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__logo--dark {
    display: none;
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__logo--light {
    display: block;
  }

  @media (max-width: 479px) {
    .nav__logo { block-size: 22px; }
  }

  .nav__brand:hover {
    color: var(--ink);
  }

  .nav__links {
    display: none;
    align-items: center;
    gap: var(--space-6);
    margin-inline-end: auto;
  }

  .nav__link {
    position: relative;
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 500;
    color: var(--slate-700);
    text-decoration: none;
    /* Padded on all sides now, because the active state is a filled shape
       rather than a rule under the text. The inline padding is what the pill
       sits in, so it exists on every link whether or not it is the current
       one: without it the row would reflow as you moved between pages. */
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    transition:
      color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  /* Hover is the same shape at a fraction of the strength, so the active state
     reads as the resting end of a gesture the reader has already felt. */
  .nav__link:hover {
    color: var(--ink);
    background: var(--base);
  }

  /*
   * The current section is a tinted pill, not an underline.
   *
   * A 2px rule under a word is the oldest convention on the web and it carries
   * two problems here: it competes with the hover underline directly above it
   * in this file, and it is invisible the moment the bar sits on the navy
   * plane. A filled shape reads at a glance, survives both grounds, and uses
   * the chip vocabulary the rest of the site already speaks.
   */
  .nav__link[aria-current="page"] {
    color: var(--accent);
    font-weight: 600;
    background: var(--tint-teal-soft);
    /* The pill travels between links across a navigation rather than fading
       out here and in there. It is the one shared element on the site, and it
       is the reason this attribute has to be in the served HTML. */
    view-transition-name: nav-active;
  }

  .nav__link[aria-current="page"]:hover {
    background: var(--tint-teal-strong);
  }

  /* On the navy plane the same pill inverts: a pale tint would glow, and the
     teal text would fail against it. */
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link[aria-current="page"] {
    background: rgb(255 255 255 / 0.14);
    color: var(--paper);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link:hover {
    background: rgb(255 255 255 / 0.08);
    color: var(--paper);
  }


  .nav__login {
    display: none;
    align-items: center;
    min-block-size: 44px;
    padding-inline: var(--space-3);
    border-radius: var(--radius-pill);
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 500;
    color: var(--slate-700);
    text-decoration: none;
    white-space: nowrap;
  }

  .nav__login:hover {
    color: var(--ink);
  }

  /* 560px is where the wordmark, both actions and the menu button stop
     colliding. Below it Log in lives in the menu instead of being dropped. */
  @media (min-width: 560px) {
    .nav__login {
      display: inline-flex;
    }
  }

  .nav__cta {
    min-block-size: 44px;
    padding-inline: var(--space-4);
    font-size: var(--size-ui);
    /* Without this the flex item refuses to shrink below its text width and
       pushes the whole bar past the viewport. */
    min-inline-size: 0;
  }

  /* Below this the wordmark costs more than it earns: dropping it is what
     lets the mark, the CTA and the menu button all fit on a 320px screen. */
  @media (max-width: 479px) {
    .nav__wordmark {
      display: none;
    }
  }

  @media (min-width: 480px) {
    .nav {
      gap: var(--space-6);
      padding-inline-start: var(--space-6);
    }

    .nav__cta {
      padding-inline: var(--space-6);
    }
  }

  .nav__toggle {
    display: grid;
    place-content: center;
    inline-size: 44px;
    block-size: 44px;
    border-radius: var(--radius-pill);
    color: var(--ink);
    flex: none;
  }

  @media (min-width: 900px) {
    .nav__links {
      display: flex;
    }

    .nav__toggle {
      display: none;
    }

    .nav__actions {
      margin-inline-start: 0;
    }
  }

  /* Mobile menu. A disclosure panel, not a modal. */
  .nav-menu {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-state) var(--ease-out-quint);
    pointer-events: auto;
    max-width: var(--container);
    margin-inline: auto;
  }

  .nav-menu[data-open="true"] {
    grid-template-rows: 1fr;
  }

  .nav-menu__inner {
    overflow: hidden;
  }

  .nav-menu__list {
    margin-block-start: var(--space-2);
    padding: var(--space-4);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-nav);
    display: grid;
    gap: var(--space-1);
  }

  .nav-menu__list a {
    display: flex;
    align-items: center;
    min-block-size: 48px;
    padding-inline: var(--space-4);
    border-radius: var(--radius-input);
    font-family: var(--font-display);
    font-weight: 500;
    color: var(--ink);
    text-decoration: none;
  }

  .nav-menu__list a:hover {
    background: var(--base);
  }

  .nav-menu__login {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 48px;
    margin-block-start: var(--space-2);
    padding: var(--space-4);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-nav);
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
  }

  .nav-menu__login:hover {
    background: var(--base);
  }

  /* Once the bar itself shows Log in, repeating it in the menu is noise. */
  @media (min-width: 560px) {
    .nav-menu__login {
      display: none;
    }
  }

  @media (min-width: 900px) {
    .nav-menu {
      display: none;
    }
  }

  /* ======================================================================
     Hero
     ====================================================================== */

  .hero {
    position: relative;
    padding-block: var(--space-16) var(--space-12);
  }

  .hero__inner {
    position: relative;
    border-radius: var(--radius-media);
    overflow: hidden;
    background: var(--ink);
    isolation: isolate;
    min-block-size: 30rem;
    display: grid;
    align-items: end;
    padding: var(--space-8);
  }

  @media (min-width: 768px) {
    .hero__inner {
      min-block-size: 34rem;
      padding: var(--space-16);
    }
  }

  .hero__media {
    position: absolute;
    inset: 0;
    z-index: -2;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  /* Scrim. Text over photography always needs one, and the gradient runs
     from the text side so the subject stays readable. */
  .hero__inner::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
      linear-gradient(to top, rgb(4 18 31 / 0.9) 0%, rgb(4 18 31 / 0.45) 45%, rgb(4 18 31 / 0.15) 100%),
      linear-gradient(to right, rgb(4 18 31 / 0.72) 0%, rgb(4 18 31 / 0.1) 62%);
  }

  .hero__content {
    max-width: 40rem;
    color: var(--paper);
    /* Grid items default to min-inline-size: auto, so a long heading sets a
       floor the item will not shrink below and the hero pushes past a 320px
       viewport. */
    min-inline-size: 0;
    overflow-wrap: break-word;
  }

  .hero__title {
    font-size: var(--size-hero);
    line-height: var(--leading-hero);
    letter-spacing: var(--tracking-hero);
    font-weight: 800;
    color: var(--paper);
  }

  .hero__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-lead);
    line-height: 1.6;
    color: rgb(255 255 255 / 0.9);
    max-width: 34ch;
  }

  .hero .eyebrow {
    color: rgb(255 255 255 / 0.78);
    margin-block-end: var(--space-6);
  }

  .hero .eyebrow::before {
    background: var(--accent-bright);
  }

  /* Compact hero for interior pages: no photograph, navy plane only. */
  .hero--plain .hero__inner {
    min-block-size: 0;
    background: var(--ink);
    align-items: center;
  }

  .hero--plain .hero__inner::after {
    background: radial-gradient(
      120% 100% at 15% 0%,
      rgb(22 48 79 / 0.9) 0%,
      transparent 60%
    );
  }

  /* ======================================================================
     Audience rail. Overlaps the hero's bottom edge, half on the photograph
     and half on the grey. This is what puts all three audiences in the
     first viewport, which the 60-second target requires.
     ====================================================================== */

  .audience {
    margin-block-start: calc(var(--space-16) * -1);
    position: relative;
    z-index: 2;
  }

  /* The rail already carries its own bottom padding for the card shadows, so
     the section after it starts from a shorter runway. */
  .audience + .section {
    padding-block-start: var(--space-16);
  }

  /* When a strip sits between the hero and the rail there is nothing left to
     overlap, so the negative pull has to come off. */
  .strip + .audience {
    margin-block-start: var(--space-16);
  }

  .audience__rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 85%;
    gap: var(--space-4);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    margin-inline: calc(var(--gutter) * -1);
    padding: var(--space-2) var(--gutter) var(--space-6);
    scrollbar-width: thin;
  }

  @media (min-width: 900px) {
    .audience__rail {
      grid-auto-columns: minmax(0, 1fr);
      overflow-x: visible;
      margin-inline: 0;
      padding-inline: 0;
      gap: var(--space-6);
    }
  }

  .audience__card {
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-raised);
    text-decoration: none;
    color: var(--ink);
    transition:
      transform var(--dur-micro) var(--ease-out-quint),
      opacity var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint);
  }

  /* Dimming the siblings is a pointer affordance. On touch there is no
     hover, so all three stay at full strength as equal tap targets. */
  @media (hover: hover) {
    .audience__rail:has(.audience__card:hover) .audience__card:not(:hover),
    .audience__rail:has(.audience__card:focus-visible) .audience__card:not(:focus-visible) {
      opacity: 0.68;
    }

    .audience__card:hover {
      transform: translateY(-4px);
    }
  }

  .audience__media {
    inline-size: 100%;
    aspect-ratio: 9 / 6;
    object-fit: cover;
  }

  .audience__body {
    padding: var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    flex: 1;
  }

  .audience__title {
    font-family: var(--font-display);
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: -0.02em;
  }

  .audience__text {
    color: var(--text-muted);
    font-size: var(--size-ui);
    flex: 1;
  }

  .audience__cta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--accent);
    margin-block-start: var(--space-2);
  }

  .audience__card:hover .audience__cta svg {
    transform: translateX(3px);
  }

  .audience__cta svg {
    transition: transform var(--dur-micro) var(--ease-out-quint);
  }

  /* The one quiet per-audience signal. */
  .audience__card[data-audience="doctors"] .audience__accent { background: var(--accent); }
  .audience__card[data-audience="students"] .audience__accent { background: var(--amber); }
  .audience__card[data-audience="brands"] .audience__accent { background: var(--slate-700); }

  .audience__accent {
    block-size: 3px;
    inline-size: 100%;
  }

  /* ======================================================================
     Claims, with sticky scroll-telling.
     ----------------------------------------------------------------------
     The claim headline pins while its detail scrolls past. position: sticky
     only, so there is no scroll-jacking and a reader can always scroll
     straight through. Below 900px it collapses to a plain stack: pinning a
     heading on a short screen steals the space the detail needs.
     ====================================================================== */

  .claim {
    display: grid;
    gap: var(--space-6);
    padding-block: var(--space-12);
    border-block-start: 1px solid var(--border);
  }

  .claim:first-of-type {
    border-block-start: 0;
    padding-block-start: 0;
  }

  .claim__index {
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    color: var(--text-meta);
    font-variant-numeric: tabular-nums;
    margin-block-end: var(--space-3);
  }

  .claim__title {
    font-size: var(--size-h3);
    line-height: var(--leading-h3);
  }

  .claim__body {
    max-width: var(--measure);
  }

  @media (min-width: 900px) {
    .claim {
      grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
      gap: var(--space-16);
      padding-block: var(--space-16);
    }

    .claim__head {
      position: sticky;
      inset-block-start: 7rem;
      align-self: start;
    }

    .claim__title {
      font-size: var(--size-h2);
      letter-spacing: var(--tracking-h2);
    }
  }

  /* ======================================================================
     Video, click to play. Never autoplay, and for a third-party embed the
     facade means no cookie is set before the visitor asks for one.
     ====================================================================== */

  .video {
    position: relative;
    border-radius: var(--radius-card);
    overflow: hidden;
    background: var(--ink);
    aspect-ratio: 16 / 9;
  }

  .video__poster {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  .video__play {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    inline-size: 100%;
    color: var(--paper);
  }

  .video__play::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgb(4 18 31 / 0.3);
    transition: background-color var(--dur-micro) var(--ease-out-quint);
  }

  .video__play:hover::before {
    background: rgb(4 18 31 / 0.45);
  }

  .video__play span {
    position: relative;
    display: grid;
    place-content: center;
    inline-size: 64px;
    block-size: 64px;
    border-radius: 50%;
    background: rgb(255 255 255 / 0.94);
    color: var(--ink);
  }

  /* ======================================================================
     Footer
     ====================================================================== */

  .footer {
    background: var(--ink-deep);
    color: rgb(255 255 255 / 0.72);
    padding-block: var(--space-16) var(--space-12);
    /* No top margin. The preceding section already owns the rhythm; adding
       margin here stacks two gaps and leaves a dead grey band. */
  }

  .footer a {
    color: rgb(255 255 255 / 0.72);
    text-decoration: none;
  }

  .footer a:hover {
    color: var(--paper);
  }

  /* "Cookie preferences" is an action (it reopens the consent banner), not a
     navigable page, so it is a <button> rather than an <a href="#">. Reset to
     match the surrounding links exactly, so the difference is invisible. */
  .footer__list button {
    all: unset;
    cursor: pointer;
    color: rgb(255 255 255 / 0.72);
  }

  .footer__list button:hover {
    color: var(--paper);
  }

  .footer__list button:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }

  .footer__grid {
    display: grid;
    gap: var(--space-12);
  }

  @media (min-width: 700px) {
    .footer__grid {
      grid-template-columns: 1.4fr repeat(3, 1fr);
      gap: var(--space-8);
    }
  }

  .footer__brand {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 1.25rem;
    color: var(--paper);
  }

  .footer__blurb {
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
    max-width: 30ch;
  }

  .footer__heading {
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: rgb(255 255 255 / 0.5);
    margin-block-end: var(--space-4);
  }

  .footer__list {
    display: grid;
    gap: var(--space-3);
    font-size: var(--size-small);
  }

  .footer__base {
    margin-block-start: var(--space-16);
    padding-block-start: var(--space-8);
    border-block-start: 1px solid rgb(255 255 255 / 0.12);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4) var(--space-8);
    justify-content: space-between;
    font-size: var(--size-small);
  }

  /* The line at the very bottom, slimmed on 2026-08-24. It used to carry the
     postal address as well, on a row as tall and as loud as the columns
     above it. Two items now, a size down and a tone quieter, closer to the
     rule they sit under. */
  .footer__base--slim {
    margin-block-start: var(--space-8);
    padding-block-start: var(--space-4);
    gap: var(--space-2) var(--space-6);
    font-size: var(--size-ui-sm);
    color: rgb(255 255 255 / 0.5);
  }

  /* ======================================================================
     Scroll reveal
     ----------------------------------------------------------------------
     Two tiers. Native scroll-driven animation runs off the main thread with
     no JS; IntersectionObserver covers everything else. Content is present
     and readable with JS off, because [data-reveal] only becomes hidden once
     the script confirms it can un-hide it.
     ====================================================================== */

  /* One mechanism, not two.
   *
   * An earlier version used native CSS scroll-driven animation
   * (`animation-timeline: view()`) with IntersectionObserver as a fallback.
   * It was elegant and it was wrong. Elements sitting near the top of a page
   * never reach their animation range, so they stayed at `opacity: 0` even
   * after a full scroll, and with JavaScript disabled the CSS still hid them
   * because the rule did not depend on script running. Three separate e2e
   * failures traced back to it.
   *
   * IntersectionObserver alone is gated on `html.js`, which only exists once
   * reveal.js has run. No script, no hiding. Reduced motion, no hiding. That
   * is the property worth more than the elegance. */
  html.js [data-reveal] {
    opacity: 0;
    transform: translateY(16px);
    transition:
      opacity var(--dur-enter) var(--ease-out-quint),
      transform var(--dur-enter) var(--ease-out-quint);
  }

  html.js [data-reveal][data-revealed="true"] {
    opacity: 1;
    transform: none;
  }

  /* ----------------------------------------------------------------------
     Three reveals, not one.

     The rule above greeted a card grid, a photographic band, a testimonial, a
     diagram and a CTA with the same 16px rise. The eye learns that gesture in
     two scrolls and then stops reading it as motion at all â€” it becomes the
     sound of the page loading, which is exactly how a hand-built site starts
     looking templated.

     So the reveal is now chosen by what the thing IS. All three are CSS, and
     all three are still gated on `html.js`: they work whether or not GSAP
     loaded, and they do not exist at all under reduced motion.

     `data-reveal` with no value keeps the original rise, so anything not
     classified still behaves.
     ---------------------------------------------------------------------- */

  /* TEXT â€” uncovered rather than moved. A top-down curtain with a short rise
     under it. At heading sizes a fade reads as a slow image loading; a wipe
     reads as typesetting. */
  html.js [data-reveal="text"] {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 100% 0);
    transition: clip-path 720ms var(--ease-out-quint);
  }

  html.js [data-reveal="text"] > * {
    opacity: 0;
    transform: translateY(10px);
    transition:
      opacity var(--dur-enter) var(--ease-out-quint) 80ms,
      transform var(--dur-enter) var(--ease-out-quint) 80ms;
  }

  html.js [data-reveal="text"][data-revealed="true"] {
    clip-path: inset(0 0 -10% 0);
  }

  html.js [data-reveal="text"][data-revealed="true"] > * {
    opacity: 1;
    transform: none;
  }

  /* CARDS â€” a rise, but half the distance, staggered along the reading axis.
     Capped at five: a nine-card grid staggered to the end takes 700ms to
     finish, by which point the reader has stopped waiting for it. */
  html.js [data-reveal="cards"] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  html.js [data-reveal="cards"] > * {
    opacity: 0;
    transform: translateY(10px);
    transition:
      opacity var(--dur-enter) var(--ease-out-quint),
      transform var(--dur-enter) var(--ease-out-quint);
  }

  html.js [data-reveal="cards"][data-revealed="true"] > * {
    opacity: 1;
    transform: none;
  }

  html.js [data-reveal="cards"][data-revealed="true"] > :nth-child(2) { transition-delay: 70ms; }
  html.js [data-reveal="cards"][data-revealed="true"] > :nth-child(3) { transition-delay: 140ms; }
  html.js [data-reveal="cards"][data-revealed="true"] > :nth-child(4) { transition-delay: 210ms; }
  html.js [data-reveal="cards"][data-revealed="true"] > :nth-child(n + 5) { transition-delay: 280ms; }

  /* SPLIT â€” the two audience cards part from the centre, one leaving left and
     one leaving right. This is the only place on the site where a fork is the
     actual content, so it is the only place the motion says "two paths". */
  html.js [data-reveal="split"] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  html.js [data-reveal="split"] > * {
    opacity: 0;
    transition:
      opacity var(--dur-enter) var(--ease-out-quint),
      transform 620ms var(--ease-out-quint);
  }

  html.js [data-reveal="split"] > :first-child { transform: translateX(-12px); }
  html.js [data-reveal="split"] > :last-child { transform: translateX(12px); }

  html.js [data-reveal="split"][data-revealed="true"] > * {
    opacity: 1;
    transform: none;
  }

  /* MEDIA â€” arrives, never slides. A photograph that translates reads as a
     carousel; one that settles out of a 1.03 scale reads as a plate being
     placed. This is the same gesture as the home hero's `hero-settle`, which
     is deliberate: the two should feel like one idea. */
  html.js [data-reveal="media"] {
    opacity: 0;
    transform: scale(1.03);
    transition:
      opacity 560ms var(--ease-out-quint),
      transform 560ms var(--ease-out-quint);
  }

  html.js [data-reveal="media"][data-revealed="true"] {
    opacity: 1;
    transform: none;
  }

  /* ======================================================================
     Page-load choreography, Home only. Runs once.
     ====================================================================== */

  @keyframes hero-settle {
    from {
      opacity: 0;
      transform: scale(1.04);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }

  @keyframes rise {
    from {
      opacity: 0;
      transform: translateY(24px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }

  html.js [data-choreo="hero-media"] {
    animation: hero-settle 900ms var(--ease-out-quint) both;
  }

  html.js [data-choreo="rise"] {
    animation: rise 640ms var(--ease-out-quint) both;
    animation-delay: calc(var(--choreo-index, 0) * var(--stagger) + 120ms);
  }

  /* ======================================================================
     View transitions. One rule for the whole multi-page site.
     ====================================================================== */

  @view-transition {
    navigation: auto;
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 200ms;
  }
}

/* ==========================================================================
   Hardening
   --------------------------------------------------------------------------
   Conditions the layout has to survive rather than features it offers.
   ========================================================================== */

@layer utilities {

  /* --- long unbroken strings -------------------------------------------
     An email address, a council registration number, a pasted URL or a
     German compound will not break at a space because it contains none. In a
     grid or flex child that sets a floor the item cannot shrink below, which
     is how a single word pushes a whole page sideways at 320px.

     `min-inline-size: 0` was already applied to the hero for exactly this
     reason. The condition is not specific to the hero. */
  .prose,
  .prose p,
  .prose li,
  .card__body,
  .quote__text,
  .abtrack__body,
  .accordion__inner,
  address {
    overflow-wrap: break-word;
  }

  .grid > *,
  .asym > *,
  .strio > *,
  .sperks > * {
    min-inline-size: 0;
  }

  /* --- Windows High Contrast / forced-colors ----------------------------
     Forced-colors replaces every colour the page sets, so anything drawn only
     as a background disappears: the specialty roundels, the poll's share
     bars, the score rings, the numbered step discs. A border is preserved,
     which is why each of these gets one only in this mode â€” it costs nothing
     in normal rendering and it is the difference between a diagram and an
     empty box for a reader who needs the mode.

     Icons are `currentColor` throughout, so they survive without help. */
  @media (forced-colors: active) {
    .speccircles__item,
    .qcard__option,
    .board__row,
    .sperk,
    .statcard,
    .voicering__tile,
    .voicering__core,
    .strio__card,
    .asym__card {
      border: 1px solid CanvasText;
    }

    /* The ring, the poll fill and the progress bar carry meaning as shapes.
       forced-colors keeps a border but flattens the fill, so the outline is
       what is left to read them by. */
    .voicering__line circle,
    .ring__bar,
    .ring__track {
      stroke: CanvasText;
    }

    .qcard__option::after,
    .progress__fill {
      outline: 1px solid CanvasText;
    }

    /* A focus ring drawn as a colour is invisible here. */
    :focus-visible {
      outline: 3px solid Highlight;
      outline-offset: 2px;
    }
  }
}

}
/* ---- blocks.css ---- */
@layer components {
/* ==========================================================================
   Blocks
   --------------------------------------------------------------------------
   The richer section types: full-bleed colour planes, two-column heroes with
   media, product-UI fragments, data compositions, stat tiles and bento.

   These exist so pages stop sharing one shape. A site where every page is
   hero-box, section head, card grid, dark break, CTA reads as a template no
   matter how good the copy is.
   ========================================================================== */

@layer components {
  /* ======================================================================
     Per-audience theming.
     One accent per audience, rebound on the body. Components read the
     semantic alias, so this is the only place audiences differ.
     ====================================================================== */

  /* Students theming lives in students.css: that page runs a whole register
     of its own rather than a swapped accent. */

  body[data-audience="brands"] {
    --plane: var(--ink);
    --plane-pop: var(--accent-bright);
  }

  /* ======================================================================
     Plane. A full-bleed colour band that runs edge to edge and carries the
     nav inside it, rather than an inset box sitting on grey.
     ====================================================================== */

  .plane {
    background: var(--plane, var(--ink));
    color: var(--paper);
    position: relative;
    isolation: isolate;
    overflow: clip;
  }

  /* Grain, so a large flat field does not band. */
  .plane::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0.03;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  .plane__glow {
    position: absolute;
    inset-block-start: -30%;
    inset-inline-end: -10%;
    inline-size: 60%;
    aspect-ratio: 1;
    z-index: -1;
    border-radius: 50%;
    background: radial-gradient(circle, rgb(21 199 216 / 0.16), transparent 62%);
    pointer-events: none;
  }

  /* ======================================================================
     Photographic plane, home hero only.

     Scoped to its own class rather than to an audience or to [data-plane]:
     every other plane on the site stays a flat gradient, and a page that
     later gains a plane cannot inherit a photograph by accident.

     Two layers. The photograph underneath, and a scrim over it that is
     heavy on the left and clears to nothing on the right. The left of this
     particular image is already a near-flat dark field, so the scrim costs
     no visible detail there; it exists so the headline's contrast does not
     depend on where the crop happens to land at an unusual viewport width.
     ====================================================================== */

  :root {
    --hero-plane: image-set(
      url(../assets/img/hero-plane.webp) type("image/webp"),
      url(../assets/img/hero-plane.jpg) type("image/jpeg")
    );
    --students-plane: image-set(
      url(../assets/img/students-plane.webp) type("image/webp"),
      url(../assets/img/students-plane.jpg) type("image/jpeg")
    );
  }

  .plane--photo {
    background-color: var(--ink-deep);
    background-image:
      linear-gradient(
        100deg,
        rgb(4 18 31 / 0.88) 0%,
        rgb(4 18 31 / 0.66) 34%,
        rgb(4 18 31 / 0.18) 62%,
        rgb(4 18 31 / 0) 100%
      ),
      var(--hero-plane);
    background-size: cover;
    background-position: center, right center;
    background-repeat: no-repeat;
  }

  /* The same construction in the students page's own teal. The scrim base is
     #04181d rather than #04121f: matching the field it sits on keeps the left
     of the plane reading as one colour rather than as navy laid over teal. */
  /* Held further right than the navy one. This image's drift begins around
     45% across where the navy plane's begins at 55%, so the same gradient let
     texture reach the tail of the headline: measured worst case fell from
     17:1 at 375px to 8.5:1 at 1920px. Still passing, but the navy plane holds
     18:1 at every width and these two should behave alike. */
  .plane--photo-teal {
    background-color: var(--plane-teal-base);
    background-image:
      linear-gradient(
        100deg,
        rgb(4 24 29 / 0.9) 0%,
        rgb(4 24 29 / 0.8) 40%,
        rgb(4 24 29 / 0.3) 68%,
        rgb(4 24 29 / 0) 100%
      ),
      var(--students-plane);
    background-size: cover;
    background-position: center, right center;
    background-repeat: no-repeat;
  }

  /* The photographs carry their own light source in the upper right. The CSS
     glow on top of that reads as a second, competing one. */
  .plane--photo .plane__glow,
  .plane--photo-teal .plane__glow {
    display: none;
  }

  @media (max-width: 899px) {
    /* The plane turns portrait below this width, and cover on a landscape
       source then shows a narrow vertical slice. Left alone that slice is
       the right edge of the image, which is the bright drift, sitting
       directly behind a full-width headline. Pull the crop back into the
       dark field and flatten the scrim so contrast holds whatever lands. */
    .plane--photo {
      /* Far enough right to catch the leading, darker edge of the drift, and
         no further: past about 60% the crop lands in the bright core. The
         scrim then runs vertically instead of horizontally, heavy across the
         headline and clearing toward the foot of the plane, so the texture
         shows where the media card sits rather than behind the type. */
      background-position: center, 55% center;
      background-image:
        linear-gradient(
          180deg,
          rgb(4 18 31 / 0.93) 0%,
          rgb(4 18 31 / 0.9) 48%,
          rgb(4 18 31 / 0.72) 78%,
          rgb(4 18 31 / 0.6) 100%
        ),
        var(--hero-plane);
    }

    .plane--photo-teal {
      background-position: center, 55% center;
      background-image:
        linear-gradient(
          180deg,
          rgb(4 24 29 / 0.93) 0%,
          rgb(4 24 29 / 0.9) 48%,
          rgb(4 24 29 / 0.72) 78%,
          rgb(4 24 29 / 0.6) 100%
        ),
        var(--students-plane);
    }
  }

  body[data-audience="students"] .plane__glow {
    background: radial-gradient(circle, var(--glow-amber), transparent 62%);
  }

  /* The plane runs up behind the nav rather than starting below it. The nav
     stays where it is in the document; the plane reaches back up under it, so
     nothing has to be repositioned and the sticky behaviour is untouched. */
  :root {
    --nav-total: 76px;
  }

  body[data-plane="on"] main > .plane:first-child {
    margin-block-start: calc(var(--nav-total) * -1);
    padding-block-start: var(--nav-total);
  }

  /* The light-on-dark treatment applies while the bar sits over the coloured
     field, and is withdrawn once the plane scrolls behind it, which is what
     stops white text stranding on pale grey.

     Read the selector carefully: it is granted by data-plane, which the build
     writes into the served HTML, and only *revoked* by data-over-plane="false",
     which nav.js sets on scroll. The earlier form required nav.js to grant it,
     so with scripting off every plane page shipped a navy button on a navy
     plane, and with scripting on there was a navy-on-navy frame before the
     script caught up. Correct by default, adjusted by script: never the
     reverse. */
  body[data-plane="on"]:not([data-over-plane="false"]) .nav {
    background: rgb(255 255 255 / 0.08);
    border-color: rgb(255 255 255 / 0.16);
    box-shadow: none;
    backdrop-filter: blur(10px);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__brand,
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__login,
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link {
    color: rgb(255 255 255 / 0.88);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link:hover,
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__login:hover,
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__brand:hover,
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link[aria-current="page"] {
    color: var(--paper);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__link[aria-current="page"]::after {
    background: var(--plane-pop);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__toggle {
    color: var(--paper);
  }

  /* A white pill carries the primary action on a dark field, the way
     Biofarma's does. Bright cyan on navy clears the contrast threshold on
     paper and still vibrates badly in practice: the numbers pass and the eye
     disagrees. Cyan stays for hairlines, icons and the active-nav rule, where
     it is a mark rather than a surface holding text. */
  body[data-plane="on"]:not([data-over-plane="false"]) .nav__cta,
  .plane .btn--primary {
    background: var(--paper);
    color: var(--ink);
  }

  body[data-plane="on"]:not([data-over-plane="false"]) .nav__cta:hover,
  .plane .btn--primary:hover {
    background: var(--plane-pop);
    color: var(--ink);
  }

  .plane .btn--secondary {
    background: transparent;
    color: var(--paper);
    border-color: rgb(255 255 255 / 0.3);
  }

  .plane .btn--secondary:hover {
    border-color: var(--paper);
    color: var(--paper);
  }

  .plane :focus-visible,
  body[data-plane="on"] .nav :focus-visible {
    outline-color: var(--plane-pop);
  }

  /* Once the nav scrolls off the plane it needs its light treatment back. */
  body[data-plane="on"] .nav-wrap {
    z-index: 50;
  }

  /* ======================================================================
     Split hero: copy on the plane, media card beside it.
     ====================================================================== */

  .phero {
    padding-block: var(--space-16) var(--space-24);
  }

  /* ----------------------------------------------------------------------
     One viewport, on a desktop.

     The home hero has to end where the screen ends: a hero that spills a
     hundred pixels past the fold reads as a section that did not fit rather
     than as an invitation to scroll.

     svh, not vh: on mobile browsers vh is the tallest the viewport ever gets,
     with the toolbars retracted, so a vh-sized hero is taller than the screen
     for as long as they are showing. Applied from 900px up, where the two
     columns appear; below that the hero stacks and grows, which is correct,
     because forcing a phone hero into one screen means shrinking either the
     headline or the photograph until neither works.
     ---------------------------------------------------------------------- */

  @media (min-width: 900px) {
    .phero--viewport {
      /* min, not fixed: content that outgrows the screen still gets its room
         rather than being clipped. */
      min-block-size: calc(100svh - var(--nav-total));
      display: grid;
      align-content: center;
      padding-block: var(--space-8) var(--space-12);
    }

    /* The media was the thing making it tall: a 4:5 frame in a 560px column
       is 700px before any padding.

       Width now comes from the column and height from the viewport, rather
       than one being derived from the other through an aspect ratio. Two
       things follow. The frame gets wider, so the poll card stops covering
       most of the photograph and sits in the corner of it as intended. And
       the height answers to the screen, so the hero scales with it.

       The floor is the card: a frame shorter than the thing floating in it
       would clip it, since the frame hides its own overflow. */
    .phero--viewport .phero__media {
      aspect-ratio: auto;
      inline-size: 100%;
      block-size: max(23rem, min(56svh, 30rem));
    }
  }

  /* Short desktop screens, which is most laptops once the browser chrome is
     out of the way. A 1366x768 window has about 620px of page below the nav,
     and the hero at its full display size needs 720. The headline gives back
     most of it: three lines at 72px against three at 56px is 48px, and the
     rest comes off the vertical rhythm. Nothing is dropped. */
  @media (min-width: 900px) and (max-height: 820px) {
    .phero--viewport .phero__title {
      font-size: clamp(2.5rem, 4vw, 3.5rem);
    }

    .phero--viewport .phero__lead {
      margin-block-start: var(--space-4);
    }

    .phero--viewport .heroform {
      margin-block-start: var(--space-6);
    }

    .phero--viewport .phero__media {
      block-size: max(23rem, 46svh);
    }
  }

  /* Shorter still: a laptop with the browser at half height, or a window
     someone has dragged small. Below about 700px there is no arrangement of
     a display headline, a lead, a sign-in and two figures that fits, so the
     headline drops to its floor and the rhythm closes up. The alternative is
     a hero that runs off the bottom of the screen, which is what this was
     doing. */
  @media (min-width: 900px) and (max-height: 700px) {
    .phero--viewport {
      padding-block: var(--space-6) var(--space-8);
    }

    .phero--viewport .phero__title {
      font-size: clamp(2rem, 3vw, 2.5rem);
    }

    .phero--viewport .phero__lead {
      font-size: 1.0625rem;
      margin-block-start: var(--space-3);
    }

    .phero--viewport .heroform {
      margin-block-start: var(--space-4);
    }

    .phero--viewport .phero__grid {
      gap: var(--space-8);
    }
  }

  /* Tall screens do not need the compression, and squeezing a 1200px viewport
     into the same proportions leaves the hero floating in the middle of a
     field of navy. */
  @media (min-width: 900px) and (min-height: 1000px) {
    .phero--viewport {
      min-block-size: 0;
      padding-block: var(--space-16) var(--space-24);
    }
  }

  .phero__grid {
    display: grid;
    gap: var(--space-12);
    align-items: center;
  }

  @media (min-width: 960px) {
    .phero__grid {
      /* Copy gets the larger share: the display size needs the measure, and a
         cramped column turns a three-word line into a five-line stack. */
      grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
      gap: var(--space-12);
    }
  }

  .phero__copy {
    min-inline-size: 0;
  }

  /* Bigger and tighter than the body scale allows. A hero headline is the one
     place on the site where type is the image, and the references all commit
     hard here rather than settling at a comfortable display size. */
  .phero__title {
    font-size: clamp(2.75rem, 6.2vw, 4.5rem);
    line-height: 0.98;
    letter-spacing: -0.045em;
    font-weight: 800;
    color: var(--paper);
    text-wrap: balance;
  }

  /* ======================================================================
     Marquee strip. Vitaal runs a greyscale logo wall directly under the
     hero. We have no confirmed client logos, so the same slot carries
     something true instead: the specialties actually represented.
     ====================================================================== */

  .strip {
    border-block-end: 1px solid var(--line);
    background: var(--paper);
  }

  /* Wraps rather than scrolls. A horizontally scrollable region containing no
     focusable children is unreachable by keyboard, and the fix for that is
     either a tab stop nobody wants or simply not making it a scroller. */
  .strip__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4) var(--space-8);
    padding-block: var(--space-6);
  }

  .strip__label {
    flex: none;
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  /* When the strip carries the marquee, the label sits above it rather than
     beside it: the row runs edge to edge, so anything sharing its line would
     be overrun. */
  /* The label came out: the reference has none here, and "60+ specialties"
     was already stated twice in the hero badges directly above. */
  .strip--marquee {
    padding-block: var(--space-6);
  }

  .strip__item {
    flex: none;
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--slate-500);
    white-space: nowrap;
  }

  /* ======================================================================
     Giant footer wordmark, Vitaal's closing device.
     ====================================================================== */


  .phero__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-lead);
    line-height: 1.6;
    color: rgb(255 255 255 / 0.86);
    max-width: 42ch;
  }

  .phero .eyebrow {
    color: rgb(255 255 255 / 0.72);
    margin-block-end: var(--space-5, 1.25rem);
  }

  .phero .eyebrow::before {
    background: var(--plane-pop, var(--accent-bright));
  }

  /* Media side.

     The frame no longer clips. The poll card is height-locked at ~323px by its
     own content â€” three 44px option rows plus the question â€” so no amount of
     narrowing made it shorter, and confined inside the frame it covered two
     thirds of the photograph's height whatever width it took. Letting it hang
     past the bottom edge is the only move that actually reduces how much of
     the picture it sits on.

     The rounding and the hairline move to the picture, which is what should
     have been carrying them anyway: they belong to the photograph, not to the
     positioning context around it. */
  .phero__media {
    position: relative;
    aspect-ratio: 4 / 3;
  }

  .phero__media > picture {
    display: block;
    block-size: 100%;
    border-radius: var(--radius-media);
    overflow: hidden;
    background: rgb(255 255 255 / 0.06);
    border: 1px solid rgb(255 255 255 / 0.12);
  }

  /* Portrait once the hero is two columns, which is what gives the question
     card somewhere to sit. The reference's hero photograph is portrait for the
     same reason. */
  @media (min-width: 900px) {
    .phero__media {
      aspect-ratio: 4 / 5;
    }
  }

  /* Descendant rather than child: a picture element wraps the image once a real
     photograph is in, and a child selector silently stops matching. */
  .phero__media picture {
    display: block;
    inline-size: 100%;
    block-size: 100%;
  }

  .phero__media img,
  .phero__media > .placeholder {
    inline-size: 100%;
    block-size: 100%;
    /* Encoded to 4:3 already, so this crops nothing. It is here so an
       replacement at another ratio degrades to a crop rather than a squash. */
    object-fit: cover;
    object-position: center 38%;
  }

  /* ======================================================================
     Fragment. A miniature of real product UI, laid over or beside media.
     This is the single biggest thing separating a designed page from a
     styled document: it shows the thing rather than describing it.
     ====================================================================== */

  .fragment {
    background: var(--paper);
    color: var(--ink);
    border-radius: var(--radius-soft);
    box-shadow: 0 18px 44px rgb(4 18 31 / 0.28);
    padding: var(--space-5, 1.25rem);
    inline-size: min(21rem, 82%);
  }

  .fragment--float {
    position: absolute;
    inset-inline-start: var(--space-4);
    inset-block-end: var(--space-4);
    z-index: 2;
  }

  @media (min-width: 640px) {
    .fragment--float {
      inset-inline-start: var(--space-6);
      inset-block-end: var(--space-6);
    }
  }

  .fragment__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-block-end: var(--space-4);
  }

  .fragment__title {
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 700;
  }

  .fragment__tag {
    font-size: var(--size-micro);
    font-weight: 600;
    font-family: var(--font-display);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    background: var(--tint, var(--tint-teal));
    color: var(--accent-ink);
  }

  .fragment__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block: var(--space-2);
    font-size: var(--size-small);
  }

  .fragment__row + .fragment__row {
    border-block-start: 1px solid var(--line);
  }

  .fragment__row span:last-child {
    font-variant-numeric: tabular-nums;
    color: var(--slate-700);
  }

  /* Avatar stack, the "+8" device. */
  .stack-avatars {
    display: flex;
    align-items: center;
  }

  .stack-avatars > * {
    inline-size: 28px;
    block-size: 28px;
    border-radius: 50%;
    border: 2px solid var(--paper);
    background: var(--line-strong);
    margin-inline-start: -8px;
    display: grid;
    place-content: center;
    font-size: var(--size-micro);
    font-weight: 700;
    font-family: var(--font-display);
    color: var(--ink);
  }

  .stack-avatars > *:first-child {
    margin-inline-start: 0;
  }

  /* ======================================================================
     Data composition. CSS-drawn charts, so the page can show real shapes
     without waiting on image assets.
     ====================================================================== */

  .bars {
    display: grid;
    gap: var(--space-3);
  }

  .bars__row {
    display: grid;
    grid-template-columns: minmax(6rem, 8rem) 1fr auto;
    gap: var(--space-4);
    align-items: center;
    font-size: var(--size-small);
  }

  .bars__track {
    block-size: 8px;
    border-radius: var(--radius-pill);
    background: var(--line);
    overflow: hidden;
  }

  .bars__fill {
    display: block;
    block-size: 100%;
    border-radius: var(--radius-pill);
    background: var(--accent);
    inline-size: var(--pct, 50%);
  }

  .dark .bars__track {
    background: rgb(255 255 255 / 0.12);
  }

  .dark .bars__fill {
    background: var(--accent-bright);
  }

  .bars__value {
    font-variant-numeric: tabular-nums;
    color: var(--text-meta);
    min-inline-size: 3ch;
    text-align: end;
  }

  /* Segment table, Compeers' cross-tab device. */
  .segments {
    inline-size: 100%;
    border-collapse: collapse;
    font-size: var(--size-small);
  }

  .segments th,
  .segments td {
    padding: var(--space-2) var(--space-3);
    text-align: end;
  }

  .segments th:first-child,
  .segments td:first-child {
    text-align: start;
    color: var(--text-meta);
  }

  .segments thead th {
    font-family: var(--font-display);
    font-size: var(--size-micro);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-meta);
    font-weight: 600;
  }

  .segments tbody td {
    font-variant-numeric: tabular-nums;
    border-radius: var(--radius-chip);
  }

  /* Intensity of the cell communicates the value, so the table reads as a
     heatmap at a glance rather than a wall of digits. */
  .segments [data-heat="1"] { background: var(--heat-1); }
  .segments [data-heat="2"] { background: var(--heat-2); }
  .segments [data-heat="3"] { background: var(--heat-3); font-weight: 600; }

  /* Quote chips, the qualitative device. */
  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .chip {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-input);
    background: var(--base);
    border: 1px solid var(--line);
    font-size: var(--size-small);
    color: var(--slate-700);
  }

  .chip--mark {
    background: var(--tint-amber);
    border-color: var(--tint-amber-strong);
    color: var(--ink-amber);
  }

  .chip--teal {
    background: var(--tint-teal);
    border-color: var(--tint-teal-strong);
    color: var(--accent-ink);
  }

  /* ======================================================================
     Quiz card. The students hero shows the product rather than a stock
     photo of someone holding a book.
     ====================================================================== */

  .quiz {
    background: var(--paper);
    color: var(--ink);
    border-radius: var(--radius-media);
    padding: var(--space-6);
    box-shadow: 0 24px 60px rgb(4 18 31 / 0.35);
  }

  @media (min-width: 640px) {
    .quiz { padding: var(--space-8); }
  }

  .quiz__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block-end: var(--space-4);
    border-block-end: 1px solid var(--line);
  }

  .quiz__q {
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
    color: var(--slate-700);
  }

  .quiz__timer {
    font-family: var(--font-display);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    background: var(--tint-amber);
    color: var(--ink-amber);
  }

  .quiz__stem {
    margin-block: var(--space-6);
    font-size: 1.0625rem;
    line-height: 1.55;
  }

  .quiz__opts {
    display: grid;
    gap: var(--space-2);
    list-style: none;
    padding: 0;
  }

  .quiz__opt {
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-input);
    font-size: var(--size-small);
  }

  .quiz__opt--picked {
    border-color: var(--amber);
    background: var(--tint-amber-soft);
    font-weight: 600;
  }

  .quiz__note {
    margin-block-start: var(--space-4);
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  /* ======================================================================
     Readout. The brands hero shows the deliverable, not a meeting photo.
     ====================================================================== */

  .readout {
    background: var(--paper);
    color: var(--ink);
    border-radius: var(--radius-media);
    padding: var(--space-6);
    box-shadow: 0 24px 60px rgb(4 18 31 / 0.35);
  }

  @media (min-width: 640px) {
    .readout { padding: var(--space-8); }
  }

  .readout__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block-end: var(--space-5, 1.25rem);
    margin-block-end: var(--space-6);
    border-block-end: 1px solid var(--line);
  }

  .readout__foot {
    margin-block-start: var(--space-6);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--line);
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  /* ======================================================================
     Leaderboard fragment
     ====================================================================== */

  .board {
    display: grid;
    gap: 2px;
  }

  .board__row {
    display: grid;
    grid-template-columns: 2rem 1fr auto;
    gap: var(--space-4);
    align-items: center;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-input);
    font-size: var(--size-small);
    background: var(--surface);
  }

  /* Deep teal with white, not bright cyan with navy. The White-on-Colour Rule
     is explicit that dark letterforms do not go on a saturated brand colour â€”
     the ratio passes and the eye still reads it as work â€” and bright cyan
     cannot carry white (2.06:1), so the fill has to move, not the text. */
  .board__row[data-rank="1"] {
    background: var(--accent);
    color: var(--paper);
    font-weight: 700;
  }

  .dark .board__row {
    background: rgb(255 255 255 / 0.06);
  }

  .dark .board__row[data-rank="1"] {
    background: var(--accent);
    color: var(--paper);
  }

  .board__pos {
    font-family: var(--font-display);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
  }

  .board__score {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
  }

  /* ======================================================================
     Big-number tile, Biofarma's 20%up / 300star device.
     ====================================================================== */

  .tile {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-6);
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-rest);
    block-size: 100%;
  }

  .tile--accent {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);
  }

  /* Signal amber carries white at 2.1:1, so a text-bearing amber fill has to
     be the deep partner. Navy on amber is the same violation as navy on cyan. */
  .tile--pop {
    background: var(--amber-deep);
    border-color: var(--amber-deep);
    color: var(--paper);
  }

  .tile__value {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-size: clamp(2.25rem, 4.5vw, 3.25rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
  }

  .tile__label {
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--accent);
  }

  .tile--accent .tile__label { color: var(--accent-bright); }
  .tile--pop .tile__label { color: var(--ink-amber); }

  .tile__body {
    font-size: var(--size-small);
    color: var(--text-muted);
  }

  .tile--accent .tile__body { color: rgb(255 255 255 / 0.78); }
  .tile--pop .tile__body { color: var(--ink-amber-deep); }

  /* ======================================================================
     Bento. Asymmetric on purpose: a uniform card grid is the most
     template-looking layout available.
     ====================================================================== */

  .bento {
    display: grid;
    gap: var(--space-6);
  }

  @media (min-width: 900px) {
    .bento {
      grid-template-columns: repeat(3, minmax(0, 1fr));
      grid-auto-rows: minmax(0, auto);
    }

    .bento__wide { grid-column: span 2; }
    .bento__tall { grid-row: span 2; }
  }

  /* ======================================================================
     Numbered steps with a connector line running through the markers.
     ====================================================================== */

  .flow {
    display: grid;
    gap: var(--space-8);
    counter-reset: flow;
  }

  @media (min-width: 860px) {
    .flow {
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-8);
    }
  }

  .flow__step {
    counter-increment: flow;
    position: relative;
    padding-block-start: var(--space-16);
  }

  .flow__step::before {
    content: counter(flow);
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    display: grid;
    place-content: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border-radius: 50%;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    z-index: 1;
  }

  /* The line joining them. */
  .flow__step::after {
    content: "";
    position: absolute;
    inset-block-start: calc(1.25rem - 1px);
    inset-inline-start: 2.5rem;
    inline-size: calc(100% - 2.5rem);
    block-size: 2px;
    background: var(--line-strong);
  }

  .flow__step:last-child::after {
    display: none;
  }

  @media (max-width: 859px) {
    .flow__step::after { display: none; }
  }

  /* A white disc on navy, not a cyan one. Dark text belongs on light neutrals;
     it does not belong on the brand cyan. */
  .dark .flow__step::before {
    background: var(--paper);
    color: var(--ink);
  }

  .dark .flow__step::after {
    background: rgb(255 255 255 / 0.18);
  }

  /* ======================================================================
     Hairline rule grid, Compeers' structural device.
     ====================================================================== */

  .ruled {
    border-block-start: 1px dashed var(--line-strong);
  }

  .ruled > * {
    border-block-end: 1px dashed var(--line-strong);
    padding-block: var(--space-8);
  }

  @media (min-width: 860px) {
    .ruled {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
    }

    .ruled > * {
      padding: var(--space-8);
      border-inline-end: 1px dashed var(--line-strong);
    }

    .ruled > *:nth-child(3n) {
      border-inline-end: 0;
    }
  }

  /* ======================================================================
     Feature grid. Vitaal's "Virtual Healthcare Without Limits" block: a
     heading held left, a paragraph held right, then a hairline-ruled grid of
     small icon-and-text items underneath. Quiet, dense, and a different
     shape from every card grid on the page.
     ====================================================================== */

  /* Hairlines, restored.
     They were removed last round on the theory that six ruled cells read as a
     table. Checking the reference at full resolution, it rules every cell on
     every side, and the rules are what hold a 3x2 of loose paragraphs together
     as a grid. The titles that came in at the same time go back out: the
     reference has none, and a bold label above a two-line paragraph doubles
     the height of a cell to restate its first three words. */
  .featgrid {
    display: grid;
    gap: 0;
    border-block-start: 1px solid var(--line);
  }

  @media (min-width: 720px) {
    .featgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }

  .featgrid__item {
    padding-block: var(--space-6);
    padding-inline-end: var(--space-8);
    border-block-end: 1px solid var(--line);
  }

  @media (min-width: 720px) {
    .featgrid__item {
      padding-inline: var(--space-8);
      border-inline-end: 1px solid var(--line);
    }

    .featgrid__item:nth-child(3n) { border-inline-end: 0; padding-inline-end: 0; }
    .featgrid__item:nth-child(3n + 1) { padding-inline-start: 0; }
  }

  .featgrid__icon {
    inline-size: 22px;
    block-size: 22px;
    color: var(--accent);
    margin-block-end: var(--space-4);
  }

  .featgrid__text {
    color: var(--text-muted);
    font-size: var(--size-ui);
    line-height: 1.6;
  }

  /* ======================================================================
     Hero question card

     The reference floats a schedule with a real patient on it: the product
     doing its job, not a label describing it. The equivalent here is the thing
     a member actually meets â€” one question, unanswered, with its options.

     Deliberately unanswered rather than showing a result. A results card would
     imply peers can see each other's responses, which is true for case studies
     and false for content review, and the home page is the wrong place to make
     that distinction.
     ====================================================================== */

  /* The display wordmark: a watermark across the foot of every page. It is
     aria-hidden and carries nothing that is not already in the footer as real
     text, so it is decoration in the WCAG sense and is exempt from the
     contrast requirement. */
  .footer__wordmark {
    margin-block-start: var(--space-16);
    font-family: var(--font-display);
    font-size: clamp(3.5rem, 15vw, 11rem);
    font-weight: 800;
    line-height: 0.85;
    letter-spacing: -0.05em;
    color: rgb(255 255 255 / 0.05);
    text-align: center;
    user-select: none;
  }

  .qcard {
    /* Sized to overlap the lower-left corner of the photograph, not to sit on
       top of it. At 17rem and 66% it covered nearly two thirds of the frame in
       both directions and the image underneath stopped doing any work; 15rem
       and 54% still took half the width and both clinicians with it. */
    inline-size: min(16rem, 55%);
    padding: var(--space-4);
    border-radius: var(--radius-card);
    background: var(--paper);
    box-shadow: var(--shadow-raised);
  }



  /* A fieldset is the correct grouping for a set of radios and brings a
     browser border and padding with it that no design wants. */
  /*
     B3. One face at a time.

     This was a 3D flip. It is not any more: backface-visibility is the one
     part of 3D transforms that engines still disagree about, and the failure
     mode is the worst possible â€” both faces painted at once, the front
     mirrored behind the back, which is exactly what it did. A card that reads
     as broken on one browser is not worth the trick on the others.

     What replaces it does the same job with none of the risk. The script
     shows one face and hides the other, so only one is ever in the DOM's
     paint. The movement is a short rise and fade, which is the site's own
     entrance, and it is the only thing prefers-reduced-motion has to switch
     off.
  */
  .qcard__flip {
    display: grid;
  }

  .qcard__face {
    grid-area: 1 / 1;
    min-inline-size: 0;
  }

  .qcard__face[hidden] { display: none; }

  @keyframes qcard-turn {
    from { opacity: 0; transform: translateY(10px) scale(0.97); }
    to { opacity: 1; transform: none; }
  }

  html.js .qcard__face[data-enter] {
    animation: qcard-turn 380ms var(--ease-out-quint) both;
  }

  .qcard__face--back {
    display: grid;
    align-content: center;
    justify-items: start;
    gap: var(--space-3);
  }

  /* The card is a light surface sitting inside the navy hero, where .dark has
     rebound --text to a light colour. Both of these say their colour, or the
     heading is white on white and the button is a white pill on a white
     card. */
  .qcard__thanks {
    font-family: var(--font-display);
    font-size: var(--size-h4);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--ink);
  }

  .qcard__thanks:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 4px;
  }

  .qcard__thanks-note {
    font-size: var(--size-small);
    line-height: 1.5;
    color: var(--slate-700);
  }

  .qcard__face--back .btn--primary {
    min-block-size: 44px;
    padding-inline: var(--space-5, 1.25rem);
    font-size: var(--size-ui);
    background: var(--ink);
    color: var(--paper);
  }

  .qcard__face--back .btn--primary:hover {
    background: var(--ink-soft);
    color: var(--paper);
  }

  .qcard__again {
    min-block-size: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  /* The waiting moment: the card is still the question, and the options stop
     accepting a second answer. */
  .qcard[data-state="waiting"] .qcard__options {
    pointer-events: none;
  }

  .qcard__set {
    border: 0;
    padding: 0;
    margin: 0;
    margin-block-start: var(--space-3);
    min-inline-size: 0;
  }

  .qcard__question {
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
    /* Stops "clinic?" landing alone on its own line. */
    text-wrap: balance;
  }

  .qcard__options {
    display: grid;
    gap: 6px;
    margin-block-start: var(--space-3);
  }

  /* A label wrapping a real radio, not a drawn one. The card used to be
     decoration behind aria-hidden, so a painted circle was the honest choice;
     now that it answers, the control has to be a control.

     44px minimum because a radio's hit area is its label's, which is what
     every browser does and what the touch-target assertion measures. */
  .qcard__option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding: 7px var(--space-3);
    border-radius: var(--radius-input);
    border: 1px solid var(--line);
    font-size: var(--size-ui-sm);
    color: var(--slate-700);
    cursor: pointer;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .qcard__option:hover {
    border-color: var(--line-strong);
    background: var(--base);
  }

  /* The native control, sized up and given the brand colour rather than
     replaced. A custom radio is a lot of CSS to reimplement something the
     platform already does accessibly. */
  .qcard__option input {
    flex: none;
    inline-size: 16px;
    block-size: 16px;
    margin: 0;
    accent-color: var(--ink);
  }

  /* :has, with the checked input as the fallback signal. Where :has is not
     supported the radio still fills in and the answer is still visible; only
     the surrounding emphasis is lost. */
  .qcard__option:has(input:checked) {
    border-color: var(--ink);
    background: var(--base);
    color: var(--ink);
    font-weight: 600;
  }

  /* ---- the result -------------------------------------------------------
     Answering turns the options into a result. The fill is a pseudo-element
     sized from --pct on each row, so the share is data in the markup and the
     drawing is one rule; the number beside it is the same value written out.

     The radios stay. Hiding them after a vote would read as "locked", the way
     a real poll locks, but it would lock by making the control unreachable:
     gone from the tab order, gone from the accessibility tree, and no way to
     change an answer given by mistake. The dot doubles as the mark against
     your own row, which is what it was already doing.
     ---------------------------------------------------------------------- */

  .qcard__option {
    position: relative;
    overflow: hidden;
  }

  .qcard__pct {
    margin-inline-start: auto;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--text-meta);
    opacity: 0;
    transition: opacity var(--dur-state) var(--ease-out-quint);
  }

  .qcard__opt-name,
  .qcard__pct {
    position: relative;
    z-index: 1;
  }

  /* The fill. Sits under the label, grows from the leading edge, and is drawn
     at low opacity so the text on top of it never drops below AA. */
  .qcard__option::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    inline-size: 0;
    background: rgb(10 109 119 / 0.14);
    transition: inline-size 520ms var(--ease-out-quint);
  }

  .qcard:has(input:checked) .qcard__option::after {
    inline-size: var(--pct, 0);
  }

  .qcard:has(input:checked) .qcard__pct {
    opacity: 1;
  }

  /* The reader's own answer keeps a deeper fill and the navy edge, so the
     result still says which one was theirs. */
  .qcard:has(input:checked) .qcard__option:has(input:checked)::after {
    background: rgb(10 109 119 / 0.28);
  }

  /* Unpicked rows lose the tint they had as choices, so the fill is the only
     thing carrying weight once the result is showing. */
  .qcard:has(input:checked) .qcard__option:not(:has(input:checked)) {
    background: transparent;
  }

  /* The ring stays on the control that has focus. Moving it to the row with
     :has and clearing it on the input reads better and is wrong: assistive
     tooling and the focus-ring test both look at the focused element, and an
     element reporting outline: none is indistinguishable from one that never
     had a ring. The row gets a tint instead, which is additive. */
  .qcard__option:has(input:focus-visible) {
    border-color: var(--line-strong);
    background: var(--base);
  }


  /* Inside the frame, as the reference's schedule card is. The media clips its
     own overflow, so anything hanging off the corner is cut rather than
     floated.

     Sat higher and wider before, and it smothered the photograph: at 54% of
     the frame it covered both clinicians from the chest down, so the picture
     stopped being a picture of anyone. Dropped toward the corner and narrowed,
     it reads as a card resting on the image rather than as a panel replacing
     it. The overlap is the point â€” a card floating clear of the frame is just
     a second box â€” but it should cost the lower corner, not the subjects. */
  .qcard.fragment--float {
    inset-inline-start: var(--space-4);
    /* Negative: the card hangs below the photograph and overlaps only its
       lower corner. Block direction only â€” an inline overhang would push past
       the container on a narrow screen and the no-sideways-scroll assertion
       would catch it, correctly. */
    inset-block-end: calc(var(--space-12) * -1);
  }

  /* On a phone the card stops floating altogether and takes its own row under
     the photograph.

     It is height-locked at roughly 323px by its content, and a phone's frame
     is smaller than that: at 320px the card was taller than the image it was
     supposed to be resting on, so it started above the frame's top edge and
     the photograph became a strip behind a card. Overlap only works where
     there is something left to overlap. In flow, the picture is whole and the
     poll is full width and easier to answer with a thumb, which is the reading
     order anyway â€” see the thing, then answer it.

     The frame stays 4:3 here: squaring it existed to buy height back from the
     floating card, and nothing floats at this width any more. */
  @media (max-width: 599px) {
    /* The ratio moves from the frame to the picture. While .phero__media held
       aspect-ratio itself, adding an in-flow card made the box grow past the
       ratio and the picture's block-size: 100% resolved against the new taller
       box, so the photograph and the card each took a share and the card still
       landed on top of the image. With the frame on auto height and the ratio
       carried by the picture, the frame is just a stack: photo, then card. */
    .phero__media {
      aspect-ratio: auto;
    }

    .phero__media > picture {
      aspect-ratio: 4 / 3;
      block-size: auto;
    }

    .qcard.fragment--float {
      position: static;
      inline-size: 100%;
      margin-block-start: var(--space-4);
    }
  }

  /* ======================================================================
     Hero sign-in

     One field and one button, in place of two buttons. A returning member
     types what they already know; anyone else lands on the sign-up page from
     the same control. It is a real form with a real action, so it works with
     scripting off, and it posts nowhere until submitted.
     ====================================================================== */

  /* One pill, with the button nested inside it. The reference does this and it
     is not decoration: a field and a detached button read as two decisions,
     where a single control reads as one. The wrapper carries the border and
     the radius; the input inside it has neither. */
  .heroform {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 26rem;
    margin-block-start: var(--space-8);
    padding: 5px;
    border-radius: var(--radius-pill);
    border: 1px solid rgb(255 255 255 / 0.28);
    background: rgb(255 255 255 / 0.1);
  }

  .heroform__field {
    flex: 1 1 9rem;
    min-inline-size: 0;
    min-block-size: 46px;
    padding-inline: var(--space-5, 1.25rem) var(--space-3);
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--paper);
    font-family: var(--font-body);
    font-size: var(--size-ui-lg);
  }

  .heroform__field::placeholder {
    color: rgb(255 255 255 / 0.6);
  }

  /* The problem was never that the input carried the ring; it was that a
     square-cornered outline inside a pill reads as a broken control. Give the
     field the pill's own radius and pull the outline inside its edge, and the
     ring traces the shape it belongs to. It stays on the focused element,
     which is what a keyboard user and the focus-ring test both require. */
  .heroform__field:focus-visible {
    outline: 2px solid var(--accent-bright);
    outline-offset: -1px;
  }

  .heroform:focus-within {
    border-color: rgb(255 255 255 / 0.5);
    background: rgb(255 255 255 / 0.14);
    box-shadow: 0 0 0 3px rgb(21 199 216 / 0.35);
  }

  .heroform .btn {
    flex: none;
    min-block-size: 46px;
    padding-inline: var(--space-6);
  }

  /* A3. Arrow only. A round button the same height as the field, so the pill
     ends in a circle rather than in a word that repeats what the field
     already says. The label is still there for a screen reader. */
  /* Specific enough to beat `.heroform .btn` above, which sets the generous
     pill padding. At 46px wide with 24px of padding on each side there was no
     room left for the arrow, and it computed to zero width. */
  .heroform .heroform__go {
    inline-size: 46px;
    padding-inline: 0;
    border-radius: var(--radius-pill);
  }

  .heroform__go .icon {
    inline-size: 20px;
    block-size: 20px;
  }

  /* A4. Two confirmed figures and the price, under the field, on one line at
     every width down to 320 because the row wraps rather than shrinking. */
  .herofacts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-5, 1.25rem);
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
    color: var(--text-on-dark);
  }

  .herofacts b {
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 700;
    color: var(--paper);
    margin-inline-end: 4px;
  }

  /* A hairline between the facts rather than a bullet character, which is one
     more glyph to translate and reads as punctuation in a screen reader. */
  .herofacts span + span {
    padding-inline-start: var(--space-5, 1.25rem);
    border-inline-start: 1px solid rgb(255 255 255 / 0.22);
  }

  @media (max-width: 420px) {
    .herofacts span + span { padding-inline-start: 0; border-inline-start: 0; }
  }

  /* A sibling of the form, not a child. Nesting it inside meant fighting the
     pill's own border and padding with negative margin, and the note is not
     part of the control. */
  .heroform__note {
    max-width: 26rem;
    margin-block-start: var(--space-3);
    font-size: var(--size-small);
    color: rgb(255 255 255 / 0.72);
  }

  .heroform__note a {
    color: var(--paper);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  /* The teal closing band is a lighter surface than the navy hero, so the
     note's 70% white drops to 3.83:1 there. It takes full white instead:
     measured, not guessed. */
  .ctaband .heroform__field {
    border-color: rgb(255 255 255 / 0.4);
  }

  .ctaband .heroform__note {
    color: var(--paper);
  }

  /* ======================================================================
     Specialty circles

     Vitaal breaks to near-black for a scattered grid of flag roundels. Ours
     carries specialties, which is the equivalent fact: the thing the network
     spans. The circles are texture and are hidden from assistive technology;
     the register itself is printed in full on /doctors/, which is where the
     link below the grid goes.
     ====================================================================== */

  /* A constellation, not a sticker sheet.
   *
   * The previous version scattered by hand: forty inline `--y` values inside a
   * flex row. It read as four rows with a wobble, because it was four rows
   * with a wobble. Every roundel was the same diameter and the same weight, so
   * nine hues across forty identical discs came out as one texture repeated.
   *
   * Three things fix it, and none of them need a script or absolute
   * positioning (which collapses at 375px, which is why it was rejected the
   * first time):
   *
   * 1. SIZE. Four diameters on a 7-step cycle. Rows hold 11, 10, 11 and 9
   *    items, and 7 shares no factor with any of them, so a size never lands
   *    in the same column twice.
   * 2. DEPTH. Every third roundel drops back: smaller, dimmer, no glow. The
   *    field gains a near and a far, which is what stops it reading flat.
   * 3. WEIGHT. Half the roundels are outlines rather than fills. Nine hues in
   *    two treatments read as eighteen, and the density halves without losing
   *    a single specialty.
   *
   * Vertical offset runs on a 13-step cycle for the same coprime reason, at
   * roughly three times the old amplitude. Rows still exist in the markup
   * because forty absolutely positioned circles are unmaintainable; they are
   * no longer legible as rows.
   *
   * The field sits on rich black rather than the navy break. Navy behind
   * saturated hues tints every one of them toward the cool end. */

  /* A packed scatter in a fixed box, not rows.

     Every disc carries its own --x / --y / --dia as percentages of this box,
     and the box carries an aspect-ratio, so one solved packing holds at every
     width without re-solving. Positions come from scripts in the build notes;
     they are deliberately irregular and provably non-overlapping.

     overflow is visible, not clipped: the hover label of a disc near an edge
     has to be able to leave the box. The padding below reserves the room. */
  .speccircles {
    position: relative;
    aspect-ratio: 1000 / 360;
    inline-size: min(100%, 1180px);
    margin-inline: auto;
    margin-block: var(--space-10, 2.5rem) var(--space-12);
    overflow: visible;
  }

  /* The inline variant sits beside a column of text on /doctors/, so it is
     squarer and narrower than the full-width one on the home page. */
  .speccircles--inline {
    aspect-ratio: 520 / 300;
    inline-size: 100%;
  }


  /* `translate` centres the disc on its coordinates and nothing else touches
     it. The fall in js/motion.js runs on `transform`, which is a separate
     property that composes with this one rather than replacing it, so the
     animation cannot move a disc off its mark and the centring needs no
     custom properties to survive it. */
  .speccircles__item {
    position: absolute;
    left: var(--x);
    top: var(--y);
    inline-size: var(--dia);
    aspect-ratio: 1;
    translate: -50% -50%;
    border-radius: 999px;
    display: grid;
    place-items: center;
    transition: scale var(--dur-state) var(--ease-out-quint);
  }

  /* Nothing is legible mid-fall. Labels come back the moment the field lands,
     which is when motion.js removes the attribute. */
  .speccircles[data-raining] .speccircles__item::after {
    opacity: 0 !important;
  }

  /* aspect-ratio, not a percentage height. The disc now takes its height from
     its own aspect-ratio rather than an explicit block-size, and a percentage
     height resolved against that is indefinite: the glyphs computed to zero
     and every disc rendered as a bare circle. */
  .speccircles__item .icon {
    inline-size: 48%;
    block-size: auto;
    aspect-ratio: 1;
  }



  /* --- colour: one hue per specialty ------------------------------------

     Forty specialties, forty hues, no repeats. This ran on nine system colours
     cycling every ninth roundel, so the same red came round four times in a
     field of forty and the grid read as a pattern rather than as a register of
     distinct disciplines.

     The lightness is solved per hue, not fixed. HSL lightness is not
     perceptual: at a single L value red carries white at 5.9 to 1 and yellow
     at 2.75, so a uniform L would have given a spectrum where a third of the
     icons were hard to see. Each hue's L is instead chosen so that white sits
     between 4.48 and 5.57 to 1 on every one of them. That is what makes forty
     saturated colours read as one family instead of as confetti â€” equal weight,
     different hue.

     Generated at S68 with a golden-angle hue sequence, so no two adjacent
     roundels land in the same part of the wheel.
     --------------------------------------------------------------------- */

  .speccircles__item {
    --hue: hsl(210 68% 44%);
    background: var(--hue);
    /* Per-disc: light hues carry the dark glyph, dark hues the white one.
       Pinning every disc to white was what forced the whole field dark. */
    color: var(--on-hue, #fff);
  }


  /* --- depth ------------------------------------------------------------- */

  /* Every third roundel sits further back. Depth comes from scale and opacity,
     not coloured glow: on this dark plane tonal separation reads as a field,
     while hue-coloured shadows read as decoration.

     0.62 was too far back. Half the point of giving forty specialties forty
     colours is that the colour is legible, and a third of the field at 62
     percent on a near-black ground went grey. Depth is still there at 0.82;
     it just no longer costs the hue. */
  /* Depth follows size now rather than DOM position: the packer already
     varies the diameters, so the small discs are the ones that recede. */
  .speccircles__item { opacity: 0.94; }

  /* The ring used to be skipped on the outlined roundels, which no longer
     exist: every roundel is a filled disc now, so every one gets the hairline
     that lifts it off the plane. */
  .speccircles__item {
    box-shadow: 0 0 0 1px rgb(255 255 255 / 0.16);
  }

  /* Phones run the portrait solve. Fifteen discs instead of thirty, in a box
     that is taller than it is wide, because the wide packing at 375px renders
     each disc about ten pixels across. The extra discs are not scaled down,
     they are removed: a specialty you cannot identify is not doing the job
     the section exists for. */
  @media (max-width: 699px) {
    .speccircles {
      aspect-ratio: 380 / 470;
      max-inline-size: 26rem;
    }

    .speccircles__item--wide { display: none; }

    .speccircles__item {
      left: var(--mx, var(--x));
      top: var(--my, var(--y));
      inline-size: var(--mdia, var(--dia));
    }

    /* The phone solve places a disc somewhere else, so its label needs its
       own anchoring; reusing the wide hint sent long names off the side. */
    .speccircles__item[data-mlab~="above"]::after {
      inset-block-start: auto;
      inset-block-end: calc(100% + 10px);
      translate: -50% 4px;
    }

    .speccircles__item[data-mlab~="below"]::after {
      inset-block-end: auto;
      inset-block-start: calc(100% + 10px);
      translate: -50% -4px;
    }

    .speccircles__item[data-mlab~="center"]::after {
      inset-inline-start: 50%;
      inset-inline-end: auto;
    }

    .speccircles__item[data-mlab~="start"]::after {
      inset-inline-start: 0;
      inset-inline-end: auto;
      translate: 0 4px;
    }

    .speccircles__item[data-mlab~="end"]::after {
      inset-inline-start: auto;
      inset-inline-end: 0;
      translate: 0 4px;
    }

    .speccircles__item[data-mlab~="below"][data-mlab~="start"]::after,
    .speccircles__item[data-mlab~="below"][data-mlab~="end"]::after {
      translate: 0 -4px;
    }

    .speccircles__item[data-label]:hover::after {
      translate: -50% 0;
    }

    .speccircles__item[data-mlab~="start"]:hover::after,
    .speccircles__item[data-mlab~="end"]:hover::after {
      translate: 0 0;
    }
  }


  /* Hover names the specialty. Drawn with a pseudo-element rather than the
     title attribute: the native tooltip takes a second to appear and cannot be
     styled. The roundels stay decoration and out of the tab order; the
     register is printed in full on /doctors/, which is where the button
     below goes. */
  .speccircles__item[data-label]:hover {
    scale: 1.14;
    opacity: 1;
    z-index: 3;
  }

  .speccircles__item[data-label]::after {
    content: attr(data-label);
    position: absolute;
    inset-block-end: calc(100% + 10px);
    inset-inline-start: 50%;
    translate: -50% 4px;
    z-index: 4;
    padding: 6px 10px;
    border-radius: var(--radius-chip);
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition:
      opacity var(--dur-micro) var(--ease-out-quint),
      translate var(--dur-micro) var(--ease-out-quint);
  }

  .speccircles__item[data-label]:hover::after {
    opacity: 1;
    translate: -50% 0;
  }

  /* Edge behaviour. A disc at the top of the box would have its label clipped
     by the section above, and one at either end would run its name off the
     side, so the packer tags each disc with where its label has to go and
     these four rules honour it. Without them the discs the eye reaches first,
     the ones on the outside, are exactly the ones whose names cannot be read. */
  .speccircles__item[data-lab~=below]::after {
    inset-block-end: auto;
    inset-block-start: calc(100% + 10px);
    translate: -50% -4px;
  }

  .speccircles__item[data-lab~=below]:hover::after {
    translate: -50% 0;
  }

  .speccircles__item[data-lab~=start]::after {
    inset-inline-start: 0;
    translate: 0 4px;
  }

  .speccircles__item[data-lab~=start]:hover::after {
    translate: 0 0;
  }

  .speccircles__item[data-lab~=below][data-lab~=start]::after {
    translate: 0 -4px;
  }

  .speccircles__item[data-lab~=below][data-lab~=start]:hover::after {
    translate: 0 0;
  }

  .speccircles__item[data-lab~=end]::after {
    inset-inline-start: auto;
    inset-inline-end: 0;
    translate: 0 4px;
  }

  .speccircles__item[data-lab~=end]:hover::after {
    translate: 0 0;
  }

  .speccircles__item[data-lab~=below][data-lab~=end]::after {
    translate: 0 -4px;
  }

  .speccircles__item[data-lab~=below][data-lab~=end]:hover::after {
    translate: 0 0;
  }


  .speccircles__more {
    background: transparent !important;
    border: 1px dashed var(--line-strong);
    color: var(--slate-700) !important;
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 700;
    letter-spacing: -0.01em;
    box-shadow: none !important;
    scale: 1 !important;
    opacity: 1 !important;
  }

  /* A disc sized as a percentage of a 1180px band is 170px wide and holds the
     phrase comfortably. The same 14.38% inside a card column is 50px, and
     "& many more" broke onto three overflowing lines. Beside a column of text
     it stops being a disc and becomes a pill that fits its own words. */
  .speccircles--inline .speccircles__more {
    inline-size: auto;
    aspect-ratio: auto;
    padding: 0.375rem 0.875rem;
    border-radius: var(--radius-pill);
    font-size: var(--size-ui-sm);
    white-space: nowrap;
  }

  /* The white counter and the fade-to-black mask are properties of the dark
     band this sits in, not of the component, so they are scoped to it rather
     than shipped by default. The mask reaches further up now that the field
     is taller and the offsets are bigger. */
  .dark .speccircles {
    -webkit-mask-image: linear-gradient(to bottom, #000 82%, var(--scrim) 94%, rgb(0 0 0 / 0.2) 100%);
    mask-image: linear-gradient(to bottom, #000 82%, var(--scrim) 94%, rgb(0 0 0 / 0.2) 100%);
  }

  .dark .speccircles__more {
    border-color: rgb(255 255 255 / 0.45);
    color: var(--paper) !important;
  }

  /* ======================================================================
     Stat cards. Vitaal closes with a 2x2 where exactly one card is filled
     in the brand colour and the rest stay pale. One filled card reads as
     emphasis; four filled cards read as noise.
     ====================================================================== */

  .statcards {
    display: grid;
    gap: var(--space-6);
  }

  @media (min-width: 720px) {
    .statcards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  .statcard {
    padding: var(--space-6);
    border-radius: var(--radius-card);
    /* The page is white, so a white card needs a fill rather than a border to
       exist at all. The reference uses the same faint grey here. */
    background: var(--base);
    border: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-block-size: 8rem;
  }

  .statcard--filled {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);
  }

  .statcard__value {
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 5vw, 3.5rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.035em;
    font-variant-numeric: tabular-nums;
    color: var(--accent);
  }

  /* White, not cyan. A figure set at 3.5rem in brand cyan is the single
     largest block of saturated colour on the page, and at that size cyan stops
     reading as an accent and starts reading as decoration â€” the exact "neon on
     dark" signature the design detector flags and the thing that makes a page
     look generated rather than made.

     The White-on-Colour Rule already says text on a brand colour is white; a
     number is text. Cyan stays on the small link underneath, where scarcity is
     what makes it read as a signal at all. */
  .statcard--filled .statcard__value { color: var(--paper); }

  .statcard__body {
    color: var(--text-muted);
    font-size: var(--size-ui);
    margin-block-start: auto;
  }

  /* No override. .statcard--filled is on the .dark token list now, so the
     --text-muted this card already reads resolves to #b9c6d8 on its own. */

  /* The reference ends every stat card with a small underlined link. Ours goes
     to the page that substantiates the figure rather than to a case study we
     do not have, which is the same shape doing honest work. */
  .statcard__link {
    display: inline-flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-block-start: auto;
    /* 44px including the padding above it. The link sat at 35px, which the
       touch-target assertion catches and a thumb finds out about later. */
    min-block-size: 44px;
    padding-block-start: var(--space-4);
    font-family: var(--font-display);
    font-size: var(--size-micro);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 4px;
  }

  .statcard__link .icon {
    inline-size: 14px;
    block-size: 14px;
  }

  .statcard__link:hover {
    color: var(--accent);
  }

  /* White, after trying both accents and rejecting both.

     Cyan on navy is the single most over-used pairing in software marketing.
     Amber replaced it and is no better: a saturated warm label on a navy card
     is the same gesture in a different hue, and it reads as a palette chosen
     to look designed rather than to say anything.

     The card already stands apart by being the only filled one in the grid.
     The link does not have to carry that job as well, so it takes the plainest
     thing available and lets the underline mark it as a link. */
  .statcard--filled .statcard__link {
    color: var(--paper);
    text-decoration-color: rgb(255 255 255 / 0.5);
  }

  .statcard--filled .statcard__link:hover {
    color: var(--paper);
    text-decoration-color: var(--paper);
  }

  /* ======================================================================
     Closing CTA band, the coloured strip Vitaal ends on before the footer.
     ====================================================================== */

  /* Teal, not navy. The reference closes on a saturated band that is a
     different colour from both the dark break above it and the footer below,
     and that colour change is what stops the last third of the page reading
     as one long dark run. --accent is the AA-safe teal, so white type on it
     clears 5.9:1 as a surface. */
  .ctaband {
    background: var(--accent);
    color: var(--paper);
    padding-block: var(--space-16);
  }

  .ctaband__inner {
    display: grid;
    gap: var(--space-8);
    align-items: center;
  }

  @media (min-width: 860px) {
    .ctaband__inner {
      grid-template-columns: minmax(0, 1fr) auto;
      gap: var(--space-12);
    }
  }

  .ctaband__title {
    font-size: clamp(1.75rem, 3.4vw, 2.5rem);
    line-height: 1.12;
    letter-spacing: -0.03em;
    color: var(--paper);
    max-width: 22ch;
  }

  .ctaband .btn--primary {
    background: var(--paper);
    color: var(--ink);
  }

  /* The fill stays white and the label takes the brand colour. Swapping the
     fill to cyan on hover put navy letterforms on saturated cyan for as long
     as the pointer was there, and swapping it to deep teal would have dropped
     the button to almost the value of the navy band behind it. */
  .ctaband .btn--primary:hover {
    background: var(--paper);
    color: var(--accent);
  }

  .ctaband .btn--secondary {
    background: transparent;
    color: var(--paper);
    border-color: rgb(255 255 255 / 0.3);
  }

  .ctaband .btn--secondary:hover {
    border-color: var(--paper);
    color: var(--paper);
  }

  /* ======================================================================
     Document header, for legal and compliance pages.
     No colour plane: these should read as documents, not campaigns.
     ====================================================================== */

  .dochead {
    padding-block: var(--space-16) var(--space-12);
    border-block-end: 1px solid var(--line);
  }

  .dochead__kicker {
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .dochead__title {
    margin-block-start: var(--space-4);
    font-size: var(--size-display);
    line-height: var(--leading-display);
    letter-spacing: var(--tracking-display);
  }

  .dochead__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-muted);
    max-width: 54ch;
  }

  .dochead__meta {
    margin-block-start: var(--space-8);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    font-size: var(--size-small);
    color: var(--text-meta);
  }
}

}
/* ---- students.css ---- */
@layer components {
/* ==========================================================================
   Students
   --------------------------------------------------------------------------
   The one page that earns a playful register. Structure borrowed from the
   CoinDuo system: physical 3D buttons, answer tiles, progress mechanics,
   generous radii, heavier body weight.

   Colour is entirely Docintosh, not CoinDuo. Coinbase Blue is replaced by
   brand teal as the single action-and-correct colour; amber and gold are
   dropped outright rather than recoloured, because a warm streak signal was
   never doing work the teal could not do. What survives is the behavioural
   design, which is the part that matters.
   ========================================================================== */

@layer components {
  body[data-audience="students"] {
    /* Teal takes the role Coinbase Blue plays: action, correct, achievement.
       Used scarcely, so that when it appears something has happened. */
    --s-action: #0a6d77;
    --s-action-press: #085a63;
    --s-action-shadow: #063f47;      /* the 4px underside of a physical button */
    --s-pop: #15c7d8;                /* brand cyan: fills and marks, never text */
    --s-pop-soft: #e6f9fb;
    --s-correct: #0e8f6f;
    --s-canvas: #fafbfc;

    --accent: var(--s-action);
    --accent-ink: var(--s-action-press);
    --plane: linear-gradient(158deg, #0d3f47 0%, #072a31 48%, var(--plane-teal-base) 100%);
    --plane-pop: var(--s-pop);
  }

  body[data-audience="students"] .section--sunken,
  body[data-audience="students"] .section--paper {
    background: var(--s-canvas);
  }

  /* ======================================================================
     Physical button. The 4px underside is behavioural design, not
     decoration: it tells a thumb the control is pressable before it is
     pressed, and it confirms the press on the way down.

     Every rule below excludes .nav__cta. The bar at the top of the page is
     chrome shared with every other route, and an audience that restyles it
     stops the site reading as one site: the students nav button was arriving
     teal with a hard underside while the identical control was a flat white
     pill everywhere else. The playful register belongs to the page, not to
     the furniture around it. tests/e2e/nav.spec.js holds this line.
     ====================================================================== */

  body[data-audience="students"] .btn--primary:not(.nav__cta) {
    background: var(--s-action);
    color: var(--paper);
    box-shadow: 0 4px 0 var(--s-action-shadow);
    transition:
      transform var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  body[data-audience="students"] .btn--primary:not(.nav__cta):hover {
    background: var(--s-action-press);
    color: var(--paper);
  }

  body[data-audience="students"] .btn--primary:not(.nav__cta):active {
    transform: translateY(4px);
    box-shadow: 0 0 0 var(--s-action-shadow);
  }

  /* On the dark plane the pill goes white, and its underside goes cyan so the
     physicality survives against the darker field. */
  body[data-audience="students"] .plane .btn--primary {
    background: #fff;
    color: var(--ink);
    box-shadow: 0 4px 0 rgb(21 199 216 / 0.55);
  }

  body[data-audience="students"] .plane .btn--primary:hover {
    background: var(--s-pop);
    color: var(--ink);
  }

  body[data-audience="students"] .plane .btn--primary:active {
    transform: translateY(4px);
    box-shadow: 0 0 0 rgb(21 199 216 / 0.55);
  }

  @media (prefers-reduced-motion: reduce) {
    body[data-audience="students"] .btn--primary:not(.nav__cta):active {
      transform: none;
    }
  }

  /* ======================================================================
     Softer, rounder surfaces. The register is friendlier here than on the
     doctor and brand pages, and radius carries most of that.
     ====================================================================== */

  body[data-audience="students"] .card,
  body[data-audience="students"] .quote,
  body[data-audience="students"] .tile,
  body[data-audience="students"] .notice {
    border-radius: var(--radius-card);
  }

  body[data-audience="students"] .quiz {
    border-radius: 28px;
  }

  /* Body copy sits one step heavier: less parsing overhead when the page is
     asking someone to read a clinical stem. */
  body[data-audience="students"] .step__body,
  body[data-audience="students"] .tile__body,
  body[data-audience="students"] .card__body {
    font-weight: 450;
  }

  /* ======================================================================
     Quiz card
     ====================================================================== */

  body[data-audience="students"] .quiz__timer {
    background: var(--s-pop-soft);
    color: var(--s-action-press);
  }

  /* 56px tap targets, per the source system. A tile is chosen with a thumb. */
  body[data-audience="students"] .quiz__opt {
    min-block-size: 56px;
    display: flex;
    align-items: center;
    border-radius: var(--radius-input);
    border-width: 2px;
    border-color: var(--line);
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  body[data-audience="students"] .quiz__opt--picked {
    border-color: var(--s-action);
    background: var(--s-pop-soft);
    color: var(--s-action-press);
    font-weight: 600;
  }

  /* Correct and incorrect states always carry a word as well as a colour.
     Colour alone is not a signal to a reader who cannot separate these hues. */
  .quiz__opt--correct {
    border-color: var(--s-correct);
    background: var(--tint-success);
    color: var(--ink-success);
    font-weight: 600;
  }

  .quiz__opt--wrong {
    border-color: var(--error);
    background: var(--tint-error);
    color: var(--ink-error);
  }

  .quiz__verdict {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-start: var(--space-4);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--tint-success);
    color: var(--ink-success);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 700;
  }

  /* ======================================================================
     Progress
     ====================================================================== */

  .progress {
    display: flex;
    align-items: center;
    gap: var(--space-4);
  }

  .progress__track {
    flex: 1;
    block-size: 12px;
    border-radius: var(--radius-pill);
    background: var(--line);
    overflow: hidden;
  }

  .progress__fill {
    display: block;
    block-size: 100%;
    border-radius: var(--radius-pill);
    background: var(--s-action);
    inline-size: var(--pct, 40%);
    transition: inline-size var(--dur-state) var(--ease-out-quint);
  }

  .plane .progress__track { background: rgb(255 255 255 / 0.16); }
  .plane .progress__fill { background: var(--s-pop); }

  .progress__label {
    font-family: var(--font-display);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    font-size: var(--size-small);
  }

  /* ======================================================================
     Reward chips. What CoinDuo spends on streaks and XP, spent here on the
     two things a student actually collects.
     ====================================================================== */

  .rewards {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
  }

  .reward {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--paper);
    border: 2px solid var(--line);
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--size-small);
  }

  .reward__num {
    font-variant-numeric: tabular-nums;
    color: var(--s-action);
  }

  .reward svg { color: var(--s-action); flex: none; }

  .plane .reward {
    background: rgb(255 255 255 / 0.1);
    border-color: rgb(255 255 255 / 0.2);
    color: var(--paper);
  }

  .plane .reward svg,
  .plane .reward__num { color: var(--s-pop); }

  /* ======================================================================
     Podium. Straight from the Thinking Thursday app: top three, crown on
     first, score pills beneath. The database's recommendation for this
     product type is Claymorphism plus micro-interactions, which is what the
     chunky radii and soft double shadows below are doing.
     ====================================================================== */

  .podium {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: end;
    gap: var(--space-4);
    max-inline-size: 30rem;
    margin-inline: auto;
  }

  .podium__slot {
    display: grid;
    justify-items: center;
    gap: var(--space-2);
    text-align: center;
  }

  /* First place is centred and raised, so the shape reads before the labels. */
  .podium__slot[data-place="1"] { order: 2; }
  .podium__slot[data-place="2"] { order: 1; padding-block-end: var(--space-6); }
  .podium__slot[data-place="3"] { order: 3; padding-block-end: var(--space-10, 2.5rem); }

  .podium__avatar {
    position: relative;
    inline-size: 68px;
    block-size: 68px;
    border-radius: 50%;
    /* --surface-2 has never existed anywhere in this codebase, so every
       render of this card has used the fallback. Naming the value it actually
       uses is the honest fix; a fallback for a token nobody defines is a
       hard-coded colour wearing a token's clothes. */
    background: var(--tint-page-mist);
    border: 4px solid #fff;
    box-shadow: 0 6px 0 rgb(4 24 28 / 0.18), 0 10px 24px rgb(4 24 28 / 0.2);
    display: grid;
    place-content: center;
    font-family: var(--font-display);
    font-weight: 800;
    color: var(--s-action);
  }

  .podium__slot[data-place="1"] .podium__avatar {
    inline-size: 88px;
    block-size: 88px;
  }

  .podium__crown {
    position: absolute;
    inset-block-start: -26px;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    color: var(--s-pop);
  }

  .podium__rank {
    position: absolute;
    inset-block-end: -12px;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    inline-size: 28px;
    block-size: 28px;
    border-radius: 50%;
    display: grid;
    place-content: center;
    background: var(--s-action);
    color: var(--paper);
    border: 3px solid #fff;
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
  }

  .podium__slot[data-place="1"] .podium__rank { background: var(--s-pop); color: var(--ink); }

  .podium__name {
    margin-block-start: var(--space-3);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 700;
    color: var(--paper);
  }

  .podium__score {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    background: rgb(255 255 255 / 0.14);
    border: 1px solid rgb(255 255 255 / 0.22);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
  }

  .podium__slot[data-place="1"] .podium__score {
    background: var(--s-pop);
    border-color: var(--s-pop);
    color: var(--ink);
  }

  /* ======================================================================
     Progress ring, the app's per-player accuracy dial.
     ====================================================================== */

  .ring {
    position: relative;
    inline-size: 44px;
    block-size: 44px;
    flex: none;
  }

  .ring svg { transform: rotate(-90deg); }

  .ring__track { fill: none; stroke: var(--line); stroke-width: 5; }

  .ring__bar {
    fill: none;
    stroke: var(--s-action);
    stroke-width: 5;
    stroke-linecap: round;
    stroke-dasharray: 113;
    stroke-dashoffset: calc(113 - (113 * var(--pct, 70) / 100));
  }

  .dark .ring__track { stroke: rgb(255 255 255 / 0.16); }
  .dark .ring__bar { stroke: var(--s-pop); }

  .ring__label {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    font-family: var(--font-display);
    font-size: var(--size-micro);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
  }

  /* Rows carry a ring, so they need a little more room than the plain board. */
  .board__row--rich {
    grid-template-columns: 2rem 1fr auto auto;
    gap: var(--space-3);
    padding-block: var(--space-3);
  }

  /* Two lines in one cell rather than four columns. A board with separate
     columns for questions and time needs about 380px before the headings stop
     colliding, and this card is 300px wide inside its own padding. */
  .board__who {
    display: grid;
    gap: 1px;
    min-inline-size: 0;
  }

  .board__who b {
    font-family: var(--font-display);
    font-weight: 700;
    line-height: 1.2;
  }

  .board__who em {
    font-style: normal;
    /* --size-eyebrow, not --size-micro. 11px is reserved for uppercase tracked
       labels; this is sentence case with digits in it. */
    font-size: var(--size-eyebrow);
    line-height: 1.3;
    font-variant-numeric: tabular-nums;
    color: var(--text-meta);
    /* One long line of meta must not push the ring off the row. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Below 480 the longest row ("AIIMS Delhi Â· 8/10 Â· 5:11") clips its time,
     and the time is the half of the line worth reading. Two lines beat an
     ellipsis that eats the data. */
  @media (max-width: 479px) {
    .board__who em {
      white-space: normal;
      overflow: visible;
    }
  }

  /* The leading row is a filled brand colour. --text-meta measures 2.4:1 on it
     and --text-on-dark only 3.50:1 â€” deep teal is dark enough that the muted
     on-dark greys, which are tuned against navy, have nowhere left to go. The
     meta takes white and separates from the name by size and weight instead. */
  .board__row[data-rank="1"] .board__who em { color: var(--paper); }

  /* The leading row is a filled brand colour, so a teal ring on it disappears.
     Track and bar both invert against their own background â€” and they invert
     the other way now that the fill is deep teal carrying white rather than
     bright cyan carrying navy. */
  .board__row[data-rank="1"] .ring__track { stroke: rgb(255 255 255 / 0.28); }
  .board__row[data-rank="1"] .ring__bar { stroke: var(--paper); }
  .board__row[data-rank="1"] .ring__label { color: var(--paper); }

  /* ======================================================================
     Achievement row: the "what you collect" grid, rounder and bolder than
     the equivalent on the doctor pages.
     ====================================================================== */

  .achieve {
    display: grid;
    gap: var(--space-6);
  }

  @media (min-width: 760px) {
    .achieve { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  .achieve__item {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-5, 1.25rem);
    align-items: start;
    padding: var(--space-6);
    border-radius: var(--radius-card);
    background: var(--paper);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-rest);
  }

  .achieve__icon {
    display: grid;
    place-content: center;
    inline-size: 52px;
    block-size: 52px;
    border-radius: var(--radius-soft);
    background: var(--s-pop-soft);
    color: var(--s-action);
    flex: none;
  }

  .achieve__title {
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 700;
  }

  .achieve__body {
    margin-block-start: var(--space-2);
    color: var(--text-muted);
    font-size: var(--size-small);
  }

  /* ======================================================================
     Rebuild, against the supplied reference design.

     Its moves, in the order they matter:
       1  A hero that is the wordmark itself at display scale, with a meta row
          rather than a lead paragraph
       2  A full-bleed colour panel carrying the whole proposition, art right
       3  A stat card beside running feature text
       4  Three cards of different kinds in one row: dark, product, quote
       5  A photographic band with a heavy veil
       6  Copy and a word grid beside a circular relationship diagram
       7  Coloured perk blocks
       8  A dark closing panel with a radial glow and a dotted mask

     The reference is monospace-led: eyebrows, figures and axis labels all sit
     in JetBrains Mono. We self-host two families and adding a third for
     decoration is not worth the request, so .eyebrow carries that job in Plus
     Jakarta Sans at the same size and tracking. The texture is slightly softer
     and nothing else changes.
     ====================================================================== */

  /* --- 1. wordmark hero ------------------------------------------------ */

  .swordmark {
    padding-block: var(--space-12) var(--space-8);
  }

  .swordmark__top {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-8);
    flex-wrap: wrap;
  }

  /* Not .eyebrow. It labels the audience beside the meta row rather than
     announcing a section, and the budget rule exists to stop section labels
     repeating down the page. */
  /* The one kicker on the site that is not set in caps. Three of its four
     terms are already acronyms â€” MBBS, NEET-PG, FMGE â€” so uppercasing the
     line only shouts the two words that were not, and turns a 36-character
     run into unbroken capitals. Left alone, the acronyms carry themselves and
     the qualifications stay the part that reads loudest. */
  /* A filled pill rather than grey meta text. It is the line that says who the
     page is for, and set as meta it read as a caption on the row beside it. */
  .swordmark__kicker {
    display: inline-flex;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--ink);
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--paper);
  }

  /* The wordmark hero is already the page's loudest move, so when the page was
     pushed louder this is what went furthest: 10.66vw against the old 8.6, and
     a 156px ceiling against 104. Tracking tightens as it grows, because at this
     size the old -0.05em opens visible gaps between the letters. */
  .swordmark__title {
    margin-block: var(--space-3) var(--space-6);
    font-size: clamp(3rem, 10.66vw, 9.75rem);
    line-height: 0.9;
    letter-spacing: -0.06em;
    font-weight: 800;
  }

  /* The mask each word rises from behind. `overflow: hidden` on a line box
     this tight would clip descenders, so the box is padded and pulled back by
     the same amount: the y of "Thursday" survives, and there is still nothing
     visible above the line. */
  .swordmark__line {
    display: block;
    overflow: hidden;
    padding-block-end: 0.12em;
    margin-block-end: -0.12em;
  }

  .swordmark__line > span {
    display: block;
  }

  .swordmark__meta {
    display: flex;
    gap: var(--space-8);
    flex-wrap: wrap;
  }

  .swordmark__meta div { min-inline-size: 6rem; }

  .swordmark__meta b {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 700;
    letter-spacing: -0.01em;
  }

  .swordmark__meta span {
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  /* --- 2. colour panel ------------------------------------------------- */

  /* The reference's colour panel is a photograph under a one-sided gradient,
     not a flat fill with art beside it. Same construction here, which also
     saves the height a second column of card was costing. */
  .spanel {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-media);
    background: var(--s-action);
    color: var(--paper);
    display: flex;
    align-items: flex-end;
    min-block-size: 38rem;
  }

  .spanel__img {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: 72% center;
    /* The artwork ships in a flat brand blue. Rotating the hue lands it on
       our teal and leaves the white card alone, because white has no hue to
       rotate. Recolouring in CSS beats shipping a second encode of the same
       picture.

       The angle is measured, not chosen. CSS hue-rotate is a matrix
       approximation rather than a true HSL rotation and badly under-rotates
       saturated blue: the arithmetic answer of -33deg lands on hue 205, still
       visibly blue. Sampling the rendered pixel across the range put -50deg on
       rgb(7,105,115), against a target of rgb(10,109,119). */
    filter: hue-rotate(-50deg) saturate(0.92);
  }

  .spanel__fade {
    position: absolute;
    inset: 0;
    background: linear-gradient(96deg, var(--accent-ink) 0%, var(--accent) 34%, rgb(10 109 119 / 0.55) 56%, rgb(10 109 119 / 0) 78%);
  }

  .spanel__body {
    position: relative;
    padding: var(--space-8);
    max-inline-size: 40rem;
  }

  @media (min-width: 860px) {
    .spanel__body { padding: var(--space-16); }
  }

  /* Portrait: the crop lands in the bright core and the copy loses its
     ground, so the fade turns vertical and the art moves left. */
  @media (max-width: 859px) {
    .spanel { min-block-size: 34rem; }
    .spanel__img { object-position: 58% center; }
    .spanel__fade {
      background: linear-gradient(180deg, rgb(6 71 79 / 0.35) 0%, rgb(10 109 119 / 0.86) 46%, var(--accent-ink) 82%);
    }
  }

  .spanel h2 {
    color: var(--paper);
    font-size: clamp(2rem, 4.4vw, 3.25rem);
    letter-spacing: -0.03em;
  }
  .spanel .eyebrow { color: rgb(255 255 255 / 0.72); }

  /* Its own class, not .spanel p. The element selector also matched the
     .quiz__stem inside the mockup, which put white text on a white card and
     erased the question entirely. */
  .spanel__lead {
    margin-block: var(--space-4) var(--space-6);
    color: rgb(255 255 255 / 0.88);
    font-size: var(--size-lead);
    line-height: 1.55;
    max-width: 34ch;
  }

  .spanel__note {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-block-start: var(--space-6);
    font-size: var(--size-ui-sm);
    color: rgb(255 255 255 / 0.75);
  }

  .spanel__pulse {
    inline-size: 8px;
    block-size: 8px;
    border-radius: 50%;
    background: var(--s-pop);
    box-shadow: 0 0 0 4px rgb(21 199 216 / 0.24);
    flex: none;
  }

  /* --- 3. split: card beside running text ------------------------------ */

  .ssplit {
    display: grid;
    gap: var(--space-8);
    align-items: center;
  }

  @media (min-width: 900px) {
    .ssplit {
      grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
      gap: var(--space-12);
    }
  }

  .scard {
    border-radius: var(--radius-media);
    overflow: hidden;
    background: var(--paper);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-rest);
  }

  .scard__top {
    background: var(--ink-deep);
    color: var(--paper);
    padding: var(--space-6);
  }

  .scard__head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
  }

  .scard__ico {
    inline-size: 42px;
    block-size: 42px;
    border-radius: 50%;
    background: var(--s-action);
    display: grid;
    place-items: center;
    flex: none;
  }

  .scard__head strong {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 700;
  }

  .scard__head em {
    display: block;
    font-style: normal;
    font-size: var(--size-ui-sm);
    color: var(--text-on-dark);
  }

  .scard__bot { padding: var(--space-6); }

  .scircrow {
    display: flex;
    gap: var(--space-3);
    margin-block: var(--space-6);
  }

  .scirc {
    inline-size: 44px;
    block-size: 44px;
    border-radius: 50%;
    background: var(--s-pop-soft);
    color: var(--s-action);
    display: grid;
    place-items: center;
    flex: none;
  }

  /* --- 4. three-card row ----------------------------------------------- */

  .strio {
    display: grid;
    gap: var(--space-6);
    align-items: stretch;
  }

  @media (min-width: 900px) {
    .strio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }

  .strio__card {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-block-size: 19rem;
    padding: var(--space-6);
    border-radius: var(--radius-media);
    background: var(--paper);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-rest);
  }

  .strio__card--dark {
    background: var(--ink-deep);
    border-color: transparent;
    color: var(--paper);
  }

  .strio__card--dark h3 { color: var(--paper); }

  /* Filled rather than tinted. White on --accent measures 6.05:1, so the card
     can carry its heading and body at full strength. */
  .strio__card--tint {
    background: var(--accent);
    border-color: var(--accent);
  }

  .strio__card--tint h3,
  .strio__card--tint .strio__body { color: var(--paper); }

  /* A translucent white chip was the instinct and it is exactly backwards:
     lightening the fill under white text measured 4.34:1. The chip inverts
     instead, which is also what .reward looks like everywhere else on the
     page â€” white ground, coloured label, 6.05:1. */
  .strio__card--tint .reward {
    background: var(--paper);
    border-color: var(--paper);
    color: var(--accent);
  }

  .strio__card--media { padding: 0; overflow: hidden; }

  .strio__card h3 {
    font-size: 1.375rem;
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.02em;
  }

  .strio__shot {
    inline-size: 100%;
    block-size: 12.5rem;
    object-fit: cover;
    /* The stack sits low and left of centre in the source frame; a centred
       cover crop showed mostly empty table. */
    object-position: 46% 62%;
    /* One saturated colour in the whole photograph, the dot on the top card,
       and it arrives in the reference brand blue. Same rotation as the panel
       artwork, and the white cards and grey ground have no hue to move. */
    filter: hue-rotate(-50deg);
  }

  .strio__body {
    margin-block-start: var(--space-3);
    font-size: var(--size-small);
    line-height: 1.5;
    color: var(--text-muted);
  }

  .strio__cap { padding: var(--space-5, 1.25rem) var(--space-6) var(--space-6); }

  .strio__cap b {
    display: block;
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 700;
  }

  .strio__cap span {
    display: block;
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    color: var(--text-muted);
    line-height: 1.5;
  }

  /* Floating activity chips, the reference's motif for "this is live". */
  .sbubbles {
    position: relative;
    block-size: 9.5rem;
    margin-block-end: var(--space-4);
  }

  .sbub {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 9px 14px;
    border-radius: var(--radius-pill);
    background: var(--ink-mid);
    border: 1px solid rgb(255 255 255 / 0.13);
    color: var(--paper);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
  }

  .sbub i {
    inline-size: 7px;
    block-size: 7px;
    border-radius: 50%;
    background: var(--s-pop);
    flex: none;
  }

  .sbub span { color: var(--text-on-dark); font-weight: 600; }

  /* --- 5. photographic band -------------------------------------------- */

  /* Copy left, faces right.

     It was centred with a heavy uniform veil, which is what the reference
     does, and it did not survive this photograph: the brightest pixel behind
     the headline is a window at pure white, so guaranteeing AA centred needs
     an alpha of 0.60 across the whole frame and that erases the picture. axe
     cannot see this, because axe does not evaluate contrast over background
     images. Moving the copy onto the blurred corridor on the left lets the
     veil clear to nothing over the faces. */
  .sbanner {
    position: relative;
    display: grid;
    align-content: center;
    justify-items: start;
    min-block-size: 22rem;
    padding: var(--space-16) 0;
    color: var(--paper);
    overflow: hidden;
  }

  .sbanner img {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: 68% 26%;
  }

  .sbanner__veil {
    position: absolute;
    inset: 0;
    background: linear-gradient(96deg, rgb(4 24 29 / 0.95) 0%, rgb(4 24 29 / 0.88) 32%, rgb(4 24 29 / 0.45) 58%, rgb(4 24 29 / 0) 80%);
    /* Thinned so more of the photograph survives behind the headline. The
       first two stops are unchanged, so the text still sits on 0.88 or more. */
    opacity: 0.72;
  }

  .sbanner__in h2 {
    font-size: clamp(2.25rem, 5.4vw, 4rem);
    letter-spacing: -0.04em;
  }

  .sbanner__in {
    position: relative;
    max-inline-size: 34rem;
    padding-inline: var(--gutter);
    margin-inline: auto;
  }

  @media (min-width: 1180px) {
    .sbanner__in { margin-inline-start: calc((100vw - var(--container)) / 2); }
  }

  /* Portrait: there is no left third to hide in, so the veil goes back to
     vertical and the copy re-centres under it. */
  @media (max-width: 700px) {
    .sbanner { justify-items: center; text-align: center; }
    .sbanner__veil {
      background: linear-gradient(180deg, rgb(4 24 29 / 0.62) 0%, rgb(4 24 29 / 0.72) 45%, rgb(4 24 29 / 0.9) 100%);
    }
  }

  .sbanner h2 { color: var(--paper); }

  .sbanner p {
    margin-block-start: var(--space-6);
    color: rgb(255 255 255 / 0.85);
    font-size: var(--size-lead);
    line-height: 1.6;
  }

  /* --- 6. word grid and diagram ---------------------------------------- */

  .sgrid2 {
    display: grid;
    gap: var(--space-8);
    align-items: center;
  }

  @media (min-width: 900px) {
    .sgrid2 { grid-template-columns: 1fr 1fr; gap: var(--space-12); }
  }

  /* Hairline-separated, which is a single background showing through 1px
     gaps rather than eight borders that double up at every join. */
  .swordgrid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    margin-block-start: var(--space-6);
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    overflow: hidden;
  }

  .swordgrid div {
    background: var(--paper);
    padding: var(--space-5, 1.25rem);
  }

  .swordgrid b {
    display: block;
    font-family: var(--font-display);
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: -0.025em;
  }

  .swordgrid span {
    display: block;
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    color: var(--text-muted);
    line-height: 1.45;
  }

  .sdiagram {
    inline-size: 100%;
    /* 520px is the SVG's native viewBox width, preserving the reading sizes
       set in the artwork wherever the radial is allowed to appear. */
    max-inline-size: 32.5rem;
    margin-inline: auto;
  }

  /* The radial is a desktop-shaped explanation: below this point its SVG text
     would scale below the site's 12px reading floor. The list repeats the same
     four steps as real text, while display:none keeps only one treatment in
     the accessibility tree at a time. */
  .sdiagram-list {
    display: none;
    list-style: none;
    margin: 0;
    padding: 0;
    border-block-start: 1px solid var(--line);
  }

  .sdiagram-list li {
    display: grid;
    gap: var(--space-1);
    padding-block: var(--space-4);
    border-block-end: 1px solid var(--line);
  }

  .sdiagram-list b {
    font-family: var(--font-display);
    font-size: var(--size-body);
    font-weight: 600;
    color: var(--text);
  }

  .sdiagram-list span {
    font-size: var(--size-small);
    line-height: 1.5;
    color: var(--text-muted);
  }

  @media (max-width: 619px) {
    .sdiagram { display: none; }
    .sdiagram-list { display: block; }
  }

  /* --- 7. perk blocks --------------------------------------------------- */

  .sperks {
    display: grid;
    gap: var(--space-5, 1.25rem);
    margin-block-start: var(--space-8);
  }

  /* Three cards, so one column then three. The old ladder was 2 then 4, which
     was right for four cards and leaves a hole and an orphan for three: a
     blank fourth column on desktop, and a single card stranded on its own row
     at the middle width. */
  @media (min-width: 900px) { .sperks { grid-template-columns: repeat(3, 1fr); } }

  /* Grid with the rows pinned to the top, not a flex column with
     space-between. Bottom-anchoring the text meant a card whose title wrapped
     to two lines started its text higher than its neighbours, so four titles
     sat at four different heights and the row read as broken. With the icon a
     fixed size and the text starting straight after it, every title lands on
     the same line and the slack falls where it belongs: at the bottom. */
  /* The icon sat 20px from two edges with 24px of nothing under it, which read
     as a mark floating in the corner rather than as the head of the block it
     labels. Even padding all round, and the gap to the title pulled in under
     the padding so the icon reads as attached to the text, not to the corner.
     min-block-size went with it: the three cards already stretch to match each
     other on the grid, so an 11rem floor only added dead space at the bottom
     of the tallest one. */
  .sperk {
    display: grid;
    grid-template-rows: auto auto;
    align-content: start;
    gap: var(--space-4);
    padding: var(--space-6);
    border-radius: var(--radius-media);
    color: var(--paper);
  }

  .sperk__g {
    inline-size: 42px;
    block-size: 42px;
    border-radius: 12px;
    background: rgb(255 255 255 / 0.18);
    display: grid;
    place-items: center;
  }

  /* The glyph inherited the global .icon size and sat small inside a 42px
     tile, which is what made the tile look empty at a glance. */
  .sperk__g .icon {
    inline-size: 21px;
    block-size: 21px;
  }

  .sperk b {
    display: block;
    margin-block-end: var(--space-2);
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.015em;
  }

  /* Full white, not 86%. That fade was tuned against the navy fills this row
     used to carry, where it still cleared AA comfortably; on the saturated
     fills it drops the rose card to 3.76:1. Weight and size separate the
     title from the body â€” a saturated fill has no contrast to spend on
     fading text. */
  /* `.sperk p span`, not `.sperk span`. The icon tile is a span too, and the
     looser selector scores (0,1,1) against .sperk__g's (0,1,0) â€” so it won,
     forced the tile to display: block, and its place-items: center went inert.
     The glyph sat at the top of its own squircle for exactly that reason. */
  .sperk p span {
    display: block;
    font-size: var(--size-small);
    line-height: 1.5;
    color: var(--paper);
  }

  /* One family in three tones, not three unrelated hues.

     This row used to run navy, teal, blue and green: four large saturated
     fills side by side, chosen so each card would look distinct. What it
     actually produced was the row reading as four things from four different
     products, which is precisely the "colour chosen per element" signature
     that makes a page look assembled by a machine. DESIGN.md's elevation rule
     is tonal first for the same reason.

     Depth now carries the distinction instead of hue. All three sit on the
     navy ramp, and because they are one family the row reads as one component
     with three entries.

     Contrast improves as a side effect. Measured with the subtext at 86
     percent white, the three now run 13.98, 12.77 and 10.28 to 1. The four
     they replace ran 12.77, 4.94, 8.77 and 6.47: all passing, but the teal was
     within half a point of the 4.5 floor, so any later nudge to that fill or
     to the subtext opacity would have taken it under without anything
     failing loudly. */
  /* Three navy blocks side by side read as one block. These are the three
     fills already measured for the /doctors/ tiles â€” white clears 4.5:1 on
     each (4.70, 5.65, 6.05) â€” so the row separates without a new colour
     entering the system. */
  .sperk--a { background: var(--sys-pink-deep); }
  .sperk--c { background: var(--sys-indigo); }
  .sperk--d { background: var(--accent); }

  /* ======================================================================
     Compaction

     The page ran to 6,250px with six sections of full section padding. Two
     thirds of that was air rather than content, so the vertical scale is cut
     here rather than section by section in the markup: one override, and the
     whole page tightens together instead of drifting apart.
     ====================================================================== */

  body[data-audience="students"] {
    --section-y: var(--space-12);
    --section-y-tight: var(--space-8);
  }

  @media (min-width: 900px) {
    body[data-audience="students"] {
      --section-y: var(--space-16);
      --section-y-tight: var(--space-12);
    }
  }

  body[data-audience="students"] .section-head { margin-block-end: var(--space-8); }

  /* ======================================================================
     Quote cards

     Six of them now, up from three, because six is what docintosh.com
     actually publishes. Six of the previous card would have added more height
     than the compaction saved, so the portrait moves onto the same line as
     the name and the whole card tightens around it.
     ====================================================================== */

  body[data-audience="students"] .quote {
    padding: var(--space-6);
    border-radius: 20px;
  }

  body[data-audience="students"] .quote__text {
    font-size: var(--size-ui);
    line-height: 1.5;
  }

  body[data-audience="students"] .quote__who {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--line);
    gap: var(--space-3);
  }

  /* The photographs are portrait, from six different phones. A square avatar
     with the crop held high is the only setting that suits all six: a face
     sits in the upper half of a portrait frame, and centring the crop cuts
     foreheads. */
  .quote__photo {
    inline-size: 40px;
    block-size: 40px;
    border-radius: 50%;
    object-fit: cover;
    object-position: center 22%;
    flex: none;
    background: var(--base);
  }
}

@layer components {
  /* ======================================================================
     Student quotes, quieted
     ----------------------------------------------------------------------
     Six bordered, shadowed cards in a 3x2 grid is the identical-card-grid
     the design system bans outright, and on a page that is already loud it
     was the loudest thing on it. Every quote and every portrait survives;
     what goes is the card around them.

     A ruled list instead. The rule does the separating a border was doing,
     the portrait shrinks to the size of the name beside it, and the quote is
     allowed to be the only thing with weight.

     Three CSS multi-column columns at the widest size, not CSS grid: a grid
     would force every row to the height of its tallest cell, which with six
     quotes of very different lengths reintroduces the identical-block
     rhythm the card grid was removed for. `columns` lets each quote take
     exactly the height its own text needs and flows the next one straight
     underneath it in the same column, so the six naturally stagger against
     each other the way uneven column heights read as considered rather than
     templated. Nothing here scrolls, loops or times out on its own â€” a
     ruled list is not a marquee, and reading it never becomes a race against
     motion the reader did not start.
     ====================================================================== */

  body[data-audience="students"] .squotes {
    border-block-start: 1px solid var(--line);
    column-gap: var(--space-16);
  }

  @media (min-width: 760px) {
    body[data-audience="students"] .squotes {
      columns: 2;
    }
  }

  @media (min-width: 1024px) {
    body[data-audience="students"] .squotes {
      columns: 3;
    }
  }

  body[data-audience="students"] .squotes .quote {
    background: none;
    border: 0;
    border-block-end: 1px solid var(--line);
    border-radius: 0;
    box-shadow: none;
    padding: var(--space-6) 0;
    gap: var(--space-4);
    break-inside: avoid;
  }

  body[data-audience="students"] .squotes .quote__text {
    font-size: var(--size-ui-lg);
    line-height: 1.6;
    color: var(--text);
  }

  body[data-audience="students"] .squotes .quote__who {
    gap: var(--space-3);
  }

  body[data-audience="students"] .squotes .quote__photo {
    inline-size: 32px;
    block-size: 32px;
    border-radius: 50%;
    object-fit: cover;
  }

  body[data-audience="students"] .squotes .quote__name {
    font-size: var(--size-small);
  }

  /* The role is the same five characters under all six names. Once the cards
     are gone it is the only thing left repeating, so it goes quiet. */
  body[data-audience="students"] .squotes .quote__role {
    font-size: 0.75rem;
    color: var(--text-meta);
  }

  /* ======================================================================
     Louder

     Two shared components pushed up with the rest of the page. Both are
     scoped to the audience: .eyebrow and .section-head carry every other
     page on the site, and a filled eyebrow is a decision about this page,
     not about the component.
     ====================================================================== */

  body[data-audience="students"] .eyebrow {
    display: inline-flex;
    padding: 4px var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--tint-teal-soft);
    color: var(--accent);
  }

  /* The panel's eyebrow sits on teal already and keeps its own treatment. */
  body[data-audience="students"] .spanel .eyebrow {
    padding: 0;
    background: none;
    color: rgb(255 255 255 / 0.72);
  }

  body[data-audience="students"] .section-head h2,
  body[data-audience="students"] .sgrid2 h2,
  body[data-audience="students"] .ssplit h2 {
    font-size: clamp(2rem, 4vw, 3rem);
    letter-spacing: -0.03em;
  }
}

}
/* ---- editorial.css ---- */
@layer components {
/* ==========================================================================
   Editorial â€” the /doctors/ structure
   --------------------------------------------------------------------------
   The reference is a case-study page, and it reads like one: centred openers,
   enormous vertical air, and composition doing the work that colour does
   elsewhere on this site. Its moves, in order of how much they matter:

     1  Centred display heading with a short centred subtitle beneath. Not a
        left-aligned hero with a media column. This alone makes the page feel
        unlike /brands/ and /, both of which open left.
     2  A heading in the left column with running text in the right, separated
        by nothing but whitespace.
     3  An asymmetric group: two light cards stacked in a narrow left column
        against one tall near-black card on the right.
     4  Numbered circles on a connecting hairline, captions centred beneath.
     5  A huge figure with tiles fanned around it at varying angles.
     6  Two large cards whose interface art bleeds off the bottom edge.
     7  A dark closing card with geometric confetti in the corners.

   Scoped to body[data-audience="doctors"] or to the block classes themselves,
   so nothing here leaks onto another audience.
   ========================================================================== */

@layer components {

  /* ======================================================================
     1. Centred opener
     ====================================================================== */

  .dochero {
    padding-block: var(--space-24) var(--space-16);
    text-align: center;
  }

  .dochero__inner {
    max-width: 46rem;
    margin-inline: auto;
  }

  .dochero__title {
    margin-block-start: var(--space-4);
    font-size: var(--size-display);
    line-height: var(--leading-display);
    letter-spacing: var(--tracking-display);
  }

  .dochero__lead {
    margin-block-start: var(--space-6);
    margin-inline: auto;
    max-width: 40rem;
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-muted);
  }

  .dochero .cluster {
    justify-content: center;
    margin-block-start: var(--space-8);
  }

  /* A wide tinted plate carrying the photograph. The reference floats product
     art on a pale wash rather than bleeding a photo to the edges, which keeps
     the page feeling like a document with figures in it. */
  .mediaband {
    margin-block-start: var(--space-16);
    border-radius: var(--radius-media);
    background: linear-gradient(168deg, var(--tint-page-cool) 0%, var(--tint-page-mist) 55%, var(--tint-page-soft) 100%);
    padding: var(--space-8);
    position: relative;
  }

  @media (min-width: 768px) {
    .mediaband {
      padding: var(--space-12) var(--space-16);
    }
  }

  .mediaband__frame {
    position: relative;
    aspect-ratio: 5 / 3;
    border-radius: 20px;
    overflow: hidden;
  }

  .mediaband__frame picture {
    display: contents;
  }

  .mediaband__frame img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  /* .fragment--float ships with a start inset for its use on the home hero.
     Both insets set on a constrained box resolves to start, which put this
     card on the wrong side of the plate: clear it explicitly. */
  .mediaband .fragment--float {
    position: absolute;
    inset-inline-start: auto;
    inset-inline-end: var(--space-6);
    inset-block-end: calc(var(--space-6) * -1);
    max-inline-size: 19rem;
  }

  @media (max-width: 767px) {
    .mediaband .fragment--float {
      position: static;
      margin-block-start: var(--space-6);
      max-inline-size: none;
    }
  }

  /* ======================================================================
     Credibility markers. This is the slot the reference fills with a
     greyscale row of client logos. We have no confirmed client logos, and
     inventing them is out of the question, so the row carries the four
     things a doctor is actually checking before they sign up.
     ====================================================================== */

  .markers {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-4) var(--space-12);
    padding-block: var(--space-12);
    border-block: 1px solid var(--line);
    margin-block-start: var(--space-16);
  }

  .markers__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--size-micro);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    /* --text-meta measured 4.81:1 here. It clears AA, but this is 11px
       uppercase on wide tracking, which is the least forgiving text on the
       site, and the row exists to be read rather than glanced at. */
    color: var(--slate-700);
  }

  .markers__item svg {
    flex: none;
    color: var(--accent);
  }

  /* ======================================================================
     2. Editorial split. Heading left, running text right, nothing between
     them but air.
     ====================================================================== */

  .editorial {
    display: grid;
    gap: var(--space-8);
  }

  @media (min-width: 900px) {
    .editorial {
      grid-template-columns: 1fr 1fr;
      gap: var(--space-16);
      align-items: start;
    }
  }

  .editorial__title {
    font-size: var(--size-h2);
    line-height: var(--leading-h2);
    letter-spacing: var(--tracking-h2);
    max-width: 14ch;
  }

  .editorial__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    color: var(--text-muted);
    max-width: 52ch;
  }

  /* ======================================================================
     3. Asymmetric group
     ====================================================================== */

  .asym {
    display: grid;
    gap: var(--space-4);
  }

  @media (min-width: 900px) {
    .asym {
      /* Narrow left, wide right. The imbalance is the point: two equal
         columns would read as a comparison rather than as a composition. */
      grid-template-columns: 0.78fr 1.22fr;
      align-items: stretch;
    }
  }

  .asym__stack {
    display: grid;
    gap: var(--space-4);
    align-content: stretch;
  }

  .asym__card {
    background: var(--base);
    border-radius: var(--radius-media);
    padding: var(--space-8);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .asym__card h3 {
    font-size: var(--size-h4);
    letter-spacing: -0.02em;
  }

  .asym__card p {
    margin-block-start: var(--space-3);
    font-size: var(--size-small);
    line-height: 1.6;
    color: var(--text-muted);
    max-width: 34ch;
  }

  .asym__art {
    margin-block-start: var(--space-8);
    margin-block-end: calc(var(--space-8) * -1);
    margin-inline-end: calc(var(--space-8) * -1);
  }

  /* ======================================================================
     Testimonial card, on the reference's shape: the heading held at the top, a
     ring below it, a saturated object at the ring's centre, and the satellites
     hung around the outside.

     What is deliberately absent is chrome. An earlier pass framed every
     satellite as a bordered, rounded, frosted panel sitting inside a bordered,
     rounded card â€” the look of a layout assembled out of components rather
     than composed. Here a quote is type with a face beneath it and nothing
     drawn around it. The only filled rectangles are the rating and the two
     stat tiles: three, which is as many as the reference fills.

     The glass is spent on light instead of on edges. Two blurred colour orbs
     under the plane give the composition depth without adding a line, and a
     backdrop-filter over flat navy would only ever return flat navy.
     ====================================================================== */

  .voiceglow {
    position: absolute;
    z-index: 0;
    border-radius: 999px;
    filter: blur(60px);
    opacity: 0.5;
    pointer-events: none;
  }

  .voiceglow--a {
    inline-size: 56%;
    aspect-ratio: 1;
    inset-block-start: -10%;
    inset-inline-end: -8%;
    background: var(--accent-bright);
  }

  .voiceglow--b {
    inline-size: 50%;
    aspect-ratio: 1;
    inset-block-end: -12%;
    inset-inline-start: -10%;
    background: var(--sys-pink-deep);
  }

  .voicehead {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  /* The nav's wordmark, not the standalone glyph. On a plane this dark the
     lockup is the clearer signature and it is already drawn for light-on-dark
     by scripts/make-logo-light.js. */
  .voicehead__logo {
    inline-size: 118px;
    block-size: auto;
    margin-block-end: var(--space-2);
  }

  .voicehead p {
    font-size: var(--size-ui-sm);
    color: var(--text-on-dark);
    max-width: 34ch;
  }

  .voicering {
    position: relative;
    flex: 1;
    min-block-size: 400px;
    margin-block-start: var(--space-5, 1.25rem);
  }

  .voicering__line {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    inline-size: 70%;
    aspect-ratio: 1;
    /* -90deg starts the stroke at twelve o'clock. A circle drawn from three
       o'clock reads as a loading spinner; drawn from the top it reads as a
       line being traced. */
    translate: -50% -50%;
    rotate: -90deg;
    overflow: visible;
  }

  .voicering__line circle {
    fill: none;
    stroke: rgb(255 255 255 / 0.15);
    /* vector-effect keeps the hairline at 1px however the circle is scaled,
       so the ring does not thicken with the card. */
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
  }

  /* The reference puts its product's own icon at the centre. Ours puts the one
     number a visitor can go and check for themselves. */
  .voicering__core {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    margin: 0;
    display: grid;
    justify-items: center;
    gap: 2px;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-card);
    /* --accent, not --accent-bright. White on the bright cyan measures 2.06:1;
       on this it is 6.05:1, which is what "white text on teal" has to mean. */
    background: var(--accent);
    color: var(--paper);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.24), 0 16px 40px rgb(0 0 0 / 0.35);
  }

  .voicering__core b {
    font-family: var(--font-display);
    font-size: var(--size-h4);
    letter-spacing: -0.03em;
    line-height: 1.05;
  }

  .voicering__core span {
    font-size: var(--size-ui-sm);
    font-weight: 500;
  }

  .voicestars {
    display: inline-flex;
    gap: 1px;
  }

  .voicestars svg {
    inline-size: 13px;
    block-size: 13px;
    fill: var(--amber);
  }

  .voicering__q {
    position: absolute;
    margin: 0;
    inline-size: 58%;
  }

  .voicering__q blockquote {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--size-ui);
    line-height: 1.45;
    color: var(--paper);
    text-wrap: pretty;
  }

  .voicering__q blockquote::before { content: "\201C"; }
  .voicering__q blockquote::after { content: "\201D"; }

  .voicering__q figcaption {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-start: var(--space-3);
  }

  /* 30px. The smaller of the two source portraits is 62px wide, so anything
     larger upscales one doctor into mush; the component is sized for the asset
     that exists rather than the asset it would like. */
  .voicering__face {
    inline-size: 30px;
    block-size: 30px;
    flex: none;
    border-radius: 999px;
    object-fit: cover;
    object-position: center 22%;
    background: var(--ink-mid);
    box-shadow: 0 0 0 1px rgb(255 255 255 / 0.25);
  }

  .voicering__q figcaption span {
    display: grid;
    font-size: var(--size-ui-sm);
    line-height: 1.3;
    color: var(--text-on-dark);
  }

  .voicering__q figcaption b {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--paper);
  }

  .voicering__q--a { inset-block-start: 0; inset-inline-start: 0; }

  /* The lower quote reads back toward the centre of the ring, so it sets to
     the right edge and its face leads from the other side. */
  .voicering__q--b {
    inset-block-end: 0;
    inset-inline-end: 0;
    text-align: end;
  }

  .voicering__q--b figcaption { flex-direction: row-reverse; }
  .voicering__q--b figcaption span { justify-items: end; }

  .voicering__tile {
    position: absolute;
    margin: 0;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-card);
    font-family: var(--font-display);
    line-height: 1.15;
    box-shadow: 0 14px 34px rgb(0 0 0 / 0.32);
  }

  .voicering__tile b {
    display: block;
    font-size: var(--size-h4);
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
  }

  .voicering__tile span {
    font-size: var(--size-ui-sm);
    font-weight: 500;
  }

  /* Both deepened until white clears AA at label size: --sys-pink carries it
     at 3.65:1 and --sys-indigo at 4.2, neither of which is enough for 13px. */
  .voicering__tile--a {
    inset-block-start: 26%;
    inset-inline-end: 0;
    background: var(--sys-pink-deep);
    color: var(--paper);
  }

  .voicering__tile--b {
    inset-block-end: 24%;
    inset-inline-start: 0;
    background: var(--sys-indigo);
    color: var(--paper);
  }

  /* Policy, not testimony: it rode the old ring as a third chip and read as a
     third quote. Under a rule, at footnote size, it is what it is.

     Selected through the parent on purpose. `.asym__dark > p` carries a 40ch
     measure and scores (0,1,1); a bare `.voicenote` is (0,1,0) and loses,
     which wrapped this footnote to four words a line. */
  .asym__dark > .voicenote {
    display: flex;
    gap: var(--space-2);
    align-items: flex-start;
    max-width: none;
    margin-block-start: var(--space-5, 1.25rem);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid rgb(255 255 255 / 0.14);
    font-size: var(--size-ui-sm);
    line-height: 1.45;
    color: var(--text-on-dark);
  }

  .voicenote .icon {
    flex: none;
    inline-size: 16px;
    block-size: 16px;
    margin-block-start: 2px;
    color: var(--accent-bright);
  }

  /* A circle cannot reflow. Below the two-column breakpoint the satellites
     stop being satellites and stack in reading order. */
  @media (max-width: 899px) {
    .voicering {
      min-block-size: 0;
      display: grid;
      gap: var(--space-5, 1.25rem);
    }

    .voicering__line { display: none; }

    .voicering__core,
    .voicering__q,
    .voicering__tile {
      position: static;
      translate: none;
      inline-size: auto;
    }

    /* Stacked, the rating stops being a medallion and becomes a row. */
    .voicering__core {
      justify-self: start;
      grid-auto-flow: column;
      align-items: center;
      gap: var(--space-3);
    }

    .voicering__q--b { text-align: start; }
    .voicering__q--b figcaption { flex-direction: row; }
    .voicering__q--b figcaption span { justify-items: start; }

    .voicering__tile {
      display: flex;
      align-items: baseline;
      gap: var(--space-2);
    }

    .voicering__tile b { display: inline; }
  }

  .asym__dark {
    background: var(--ink-deep);
    border-radius: var(--radius-media);
    padding: var(--space-8);
    color: var(--paper);
    display: flex;
    flex-direction: column;
    /* The card is the frame the glow is cropped to, and `isolation` keeps that
       blurred light from stacking against anything outside it. */
    position: relative;
    overflow: hidden;
    isolation: isolate;
  }

  /* Everything except the light sits above it. */
  .asym__dark > *:not(.voiceglow) {
    position: relative;
    z-index: 1;
  }

  @media (min-width: 768px) {
    .asym__dark {
      padding: var(--space-12);
    }
  }

  .asym__dark h3 {
    font-size: var(--size-h4);
    letter-spacing: -0.02em;
    color: var(--paper);
  }

  .asym__dark > p {
    margin-block-start: var(--space-3);
    color: var(--text-on-dark);
    max-width: 40ch;
  }

  /* Specialty chip cloud for the left card. */
  .chipcloud {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-8);
  }

  .chipcloud span {
    padding: 0.375rem 0.75rem;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    background: var(--paper);
    font-size: 0.75rem;
    color: var(--text-muted);
    white-space: nowrap;
  }

  .chipcloud span:last-child {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--paper);
  }

  /* ======================================================================
     4. Numbered rail
     ====================================================================== */

  .steprail {
    display: grid;
    gap: var(--space-12);
    margin-block-start: var(--space-16);
    counter-reset: steprail;
  }

  @media (min-width: 768px) {
    .steprail {
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-8);
    }
  }

  .steprail__item {
    text-align: center;
    counter-increment: steprail;
    position: relative;
    padding-block-start: var(--space-16);
  }

  /* The numeral. Drawn, so the list markup stays a plain <ol> and the count
     is available to assistive technology from the structure itself. */
  .steprail__item::before {
    content: counter(steprail);
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    inline-size: 48px;
    block-size: 48px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 700;
  }

  /* The connecting hairline, drawn between circles rather than under them. */
  @media (min-width: 768px) {
    .steprail__item::after {
      content: "";
      position: absolute;
      inset-block-start: 24px;
      inset-inline-start: calc(50% + 32px);
      inline-size: calc(100% - 64px);
      block-size: 1px;
      background: var(--line-strong);
    }

    .steprail__item:last-child::after {
      display: none;
    }
  }

  .steprail__title {
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 600;
  }

  .steprail__body {
    margin-block-start: var(--space-3);
    margin-inline: auto;
    max-width: 30ch;
    font-size: var(--size-small);
    line-height: 1.6;
    color: var(--text-muted);
  }

  /* ======================================================================
     5. Big-number moment with fanned tiles.

     The reference fans gift cards around a payout total. Ours fans specialty
     tiles around the membership figure, and the substitution is deliberate:
     see tests/output/invariants â€” no member-facing page shows a sum of money.
     ====================================================================== */

  /* The top padding is the arc's headroom. Tiles are positioned against the
     zero-height container that follows it, so anything offset further up than
     this value climbs out of the section and lands on the paragraph above. */
  .bigfan {
    position: relative;
    text-align: center;
    /* Guard. The sway is bounded, but nothing else in the fan should be able
       to widen the page either. */
    overflow: clip;
    padding-block: var(--space-24) var(--space-12);
  }

  .bigfan__tiles {
    position: relative;
    block-size: 0;
    /* Tiles overlap the figure's line box on wide screens and tuck away on
       narrow ones, where there is no room to fan anything. */
    display: none;
  }

  @media (min-width: 1024px) {
    .bigfan__tiles {
      display: block;
    }
  }

  /* The fan turns. Two nested rotations: the ring carries the arrangement
     round, and each tile counter-rotates at the same rate so its label stays
     upright. One animation on a parent would sweep the words upside down at
     the half-way point. */
  .bigfan__tiles {
    animation: fanring 14s ease-in-out infinite alternate;
    transform-origin: 50% 260px;
  }

  .bigfan__tile {
    position: absolute;
    animation: fantile 14s ease-in-out infinite alternate;
    transform-origin: 50% 50%;
  }

  .bigfan__card {
    display: block;
    inline-size: 118px;
    padding: var(--space-4) var(--space-3);
    border-radius: var(--radius-input);
    background: var(--paper);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-rest);
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
    text-align: center;
    transform: rotate(var(--rot, 0deg));
  }

  /* The closing tile. The set is open-ended, and a fan of exactly eight reads
     as the whole list. */
  .bigfan__card--more {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);
  }

  /* A sway, not a full turn. A 360deg orbit throws a 118px tile roughly
     440px from the centre at the quarter points, which pushes the page
     sideways at every viewport the fan is visible at. Four degrees each way
     reads as alive and cannot leave the container. */
  @keyframes fanring {
    from { rotate: -4deg; }
    to { rotate: 4deg; }
  }

  @keyframes fantile {
    from { rotate: 4deg; }
    to { rotate: -4deg; }
  }

  .bigfan__value {
    position: relative;
    font-family: var(--font-display);
    font-size: clamp(3rem, 11vw, 7rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.045em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
  }

  .bigfan__note {
    margin-block-start: var(--space-8);
    margin-inline: auto;
    max-width: 34rem;
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  /* ======================================================================
     6. Showcase. Two large cards whose interface art bleeds off the bottom.
     ====================================================================== */

  .showcase {
    display: grid;
    gap: var(--space-4);
    margin-block-start: var(--space-16);
  }

  @media (min-width: 900px) {
    .showcase {
      grid-template-columns: repeat(2, 1fr);
    }
  }

  .showcase__card {
    background: var(--base);
    border-radius: var(--radius-media);
    padding: var(--space-12) var(--space-8) 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-block-size: 26rem;
  }

  .showcase__card h3 {
    font-size: 1.75rem;
    letter-spacing: -0.025em;
  }

  .showcase__card > p {
    margin-block-start: var(--space-4);
    color: var(--text-muted);
    max-width: 38ch;
  }

  .showcase__note {
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  /* The bleed. A fixed slice off the bottom is what makes the card read as a
     window onto something larger rather than as a box with a picture in it. */
  .showcase__art {
    margin-block-start: auto;
    padding-block-start: var(--space-12);
    margin-inline: calc(var(--space-4) * -1);
  }

  .showcase__art .fragment {
    margin-block-end: calc(var(--space-12) * -1);
  }

  /* ======================================================================
     7. Dark closing card
     ====================================================================== */

  .ctacard {
    position: relative;
    margin-block-start: var(--space-16);
    /* The panel sat flush against the footer, so the two dark surfaces read as
       one block with a rule through it. */
    margin-block-end: var(--space-16);
    border-radius: var(--radius-media);
    background: var(--ink-deep);
    color: var(--paper);
    padding: var(--space-16) var(--space-8);
    text-align: center;
    overflow: hidden;
    isolation: isolate;
  }

  @media (min-width: 768px) {
    .ctacard {
      padding: var(--space-24) var(--space-16);
    }
  }

  .ctacard h2 {
    font-size: var(--size-h2);
    line-height: var(--leading-h2);
    letter-spacing: var(--tracking-h2);
    color: var(--paper);
  }

  .ctacard p {
    margin-block-start: var(--space-4);
    margin-inline: auto;
    max-width: 40rem;
    color: var(--text-on-dark);
  }

  .ctacard .cluster {
    justify-content: center;
    margin-block-start: var(--space-8);
  }

  /* Confetti. The reference throws saturated primary shapes into the corners.
     Ours are navy-on-navy and cyan at low opacity: enough to break the
     rectangle, not enough to turn a doctor's page into a children's one. */
  .ctacard__shape {
    position: absolute;
    z-index: -1;
    border-radius: var(--radius-chip);
    pointer-events: none;
  }

  .ctacard__shape--a {
    inline-size: 120px; block-size: 120px;
    inset-block-start: -30px; inset-inline-start: 6%;
    background: rgb(21 199 216 / 0.16);
    transform: rotate(24deg);
  }

  .ctacard__shape--b {
    inline-size: 84px; block-size: 84px;
    inset-block-end: -20px; inset-inline-start: 18%;
    background: rgb(255 255 255 / 0.05);
    border-radius: 999px;
  }

  .ctacard__shape--c {
    inline-size: 150px; block-size: 150px;
    inset-block-end: -50px; inset-inline-end: 8%;
    background: rgb(22 48 79 / 0.9);
    transform: rotate(-18deg);
  }

  .ctacard__shape--d {
    inline-size: 64px; block-size: 64px;
    inset-block-start: 18%; inset-inline-end: 16%;
    background: rgb(21 199 216 / 0.1);
    border-radius: 999px;
  }

  /* ======================================================================
     8. Device mockups

     The reference makes the product its argument: three of its seven sections
     are dominated by phone frames you can actually read.

     Ours are drawn in CSS rather than screenshotted. A screenshot of the real
     app carries a credit balance, per-activity credit values, a referral
     bounty, an ad slot and a named pharmaceutical supporter, none of which may
     appear pre-login. Drawing them keeps the density and drops exactly those.

     All decoration: aria-hidden, no focusable children.
     ====================================================================== */

  .phoneframe {
    inline-size: 100%;
    max-inline-size: 268px;
    aspect-ratio: 9 / 18.5;
    padding: 9px;
    border-radius: var(--radius-device);
    background: var(--ink-deep);
    box-shadow: 0 0 0 1px rgb(255 255 255 / 0.06) inset, var(--shadow-raised);
    overflow: hidden;
  }

  .phoneframe__screen {
    block-size: 100%;
    border-radius: calc(var(--radius-device) - 8px);
    background: var(--page);
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .appbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 22px 14px 12px;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    flex: none;
  }

  .appbar .icon { inline-size: 14px; block-size: 14px; opacity: 0.75; }
  .appbar__spacer { margin-inline-start: auto; }

  .appfeed {
    display: grid;
    gap: 8px;
    padding: 12px;
    background: var(--base);
    flex: 1;
    align-content: start;
    overflow: hidden;
  }

  .appcard {
    padding: 10px;
    border-radius: 12px;
    background: var(--paper);
    border: 1px solid var(--line);
  }

  .appcard__head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-end: 6px;
  }

  .appcard__title {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
  }

  .appcard__foot {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-start: 8px;
    padding-block-start: 8px;
    border-block-start: 1px solid var(--line);
    font-size: var(--size-mock-sm);
    color: var(--slate-500);
  }

  .appcard__dot {
    inline-size: 5px;
    block-size: 5px;
    border-radius: 50%;
    background: var(--sys-green);
    flex: none;
  }

  .appcard__foot--closing { color: var(--sys-red-deep); }
  .appcard__foot--closing .appcard__dot { background: var(--sys-red); }

  .appmeta {
    margin-inline-start: auto;
    font-size: var(--size-mock-sm);
    color: var(--slate-500);
    white-space: nowrap;
  }

  /* Type chips in the app colours. Text takes the -deep partner of its own
     hue, so it clears AA against the tint rather than against the fill. */
  .appchip {
    padding: 2px 7px;
    border-radius: 999px;
    font-family: var(--font-display);
    font-size: var(--size-mock-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .appchip[data-t="survey"]  { background: var(--chip-survey); color: var(--sys-red-deep); }
  .appchip[data-t="review"]  { background: var(--chip-review); color: var(--sys-mint-deep); }
  .appchip[data-t="profile"] { background: var(--chip-profile); color: var(--sys-indigo-deep); }
  .appchip[data-t="coach"]   { background: var(--chip-coach); color: var(--sys-green-deep); }
  .appchip[data-t="case"]    { background: var(--chip-case); color: var(--sys-blue-deep); }
  .appchip[data-t="quiz"]    { background: var(--chip-quiz); color: var(--sys-orange-deep); }

  .apppoll {
    padding: 12px;
    background: var(--paper);
    border-block-start: 1px solid var(--line);
    flex: none;
  }

  .apppoll__q {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    margin-block-end: 8px;
  }

  .apppoll__opt {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 8px;
    margin-block-end: 5px;
    border-radius: var(--radius-chip);
    border: 1px solid var(--line);
    font-size: var(--size-mock);
    color: var(--slate-700);
  }

  .apppoll__opt::before {
    content: "";
    flex: none;
    inline-size: 11px;
    block-size: 11px;
    border-radius: 50%;
    border: 1.5px solid var(--line-strong);
  }

  .apppoll__opt[data-picked="true"] {
    border-color: var(--ink);
    background: var(--base);
    color: var(--ink);
    font-weight: 600;
  }

  .apppoll__opt[data-picked="true"]::before { border: 3.5px solid var(--ink); }

  .appdone {
    flex: 1;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: 10px;
    padding: 20px 16px;
    text-align: center;
    background: var(--base);
  }

  .appdone__mark {
    inline-size: 54px;
    block-size: 54px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--tint-mint);
    color: var(--sys-green-deep);
  }

  .appdone__mark .icon { inline-size: 26px; block-size: 26px; }

  .appdone__title {
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 700;
    color: var(--ink);
  }

  .appdone__body { font-size: var(--size-mock); color: var(--slate-500); }

  .appdone__cert {
    inline-size: 100%;
    margin-block-start: 4px;
    padding: 9px;
    border-radius: var(--radius-input);
    background: var(--paper);
    border: 1px solid var(--line);
    font-size: var(--size-mock-sm);
    color: var(--slate-700);
    text-align: start;
  }

  .appdone__cert b {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-micro);
    color: var(--ink);
  }

  /* --- the hero trio --------------------------------------------------- */

  .phonetrio {
    position: relative;
    margin-block-start: var(--space-12);
    padding-block: var(--space-12);
    border-radius: var(--radius-media);
    background: var(--base);
    display: grid;
    justify-items: center;
    /* Rotation must never widen the layout box, or a tilted phone pushes the
       page sideways at 375px. Clip here, transform only. */
    overflow: clip;
  }

  .phonetrio__rail {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    /* .dochero centres its copy, and the trio sits inside it. Without this
       every card title, chip and poll option in all three screens inherits
       that centring, which no app has ever done. */
    text-align: start;
  }

  /* One phone below the two-column breakpoint. Three at 375px would be 90px
     each and unreadable, which defeats the section. */
  .phonetrio__side { display: none; }

  @media (min-width: 900px) {
    .phonetrio__side { display: block; }

    /* The frames used to take their width from the text inside them. With the
       screens redrawn as bars there is no intrinsic width left to resist flex
       shrinking, and three phones collapsed into one overlapping stack. Size
       them explicitly: the drawing should not depend on its own content. */
    .phonetrio__rail > .phoneframe {
      flex: none;
      inline-size: 268px;
      max-inline-size: none;
    }

    .phonetrio__side:first-child {
      transform: rotate(-7deg) translate(48px, 30px);
      z-index: 1;
    }

    .phonetrio__side:last-child {
      transform: rotate(7deg) translate(-48px, 30px);
      z-index: 1;
    }

    .phonetrio__lead {
      position: relative;
      z-index: 2;
      inline-size: 292px;
    }
  }

  /* Floating props. The reference floats gold coins here; coins are its
     earn-money motif and the thing this page moves away from, so these carry
     the two facts a doctor is actually weighing. */
  .phonetrio__prop {
    position: absolute;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-radius: 999px;
    background: var(--paper);
    box-shadow: var(--shadow-raised);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 700;
    color: var(--ink);
    white-space: nowrap;
  }

  .phonetrio__prop .icon {
    inline-size: 15px;
    block-size: 15px;
    color: var(--accent);
  }

  .phonetrio__prop--a { inset-block-start: 20%; inset-inline-start: 5%; }
  .phonetrio__prop--b { inset-block-end: 24%; inset-inline-end: 6%; }

  @media (max-width: 899px) {
    .phonetrio__prop--a { inset-block-start: 6%; inset-inline-start: 3%; }
    .phonetrio__prop--b { inset-block-end: 8%; inset-inline-end: 3%; }
  }

  /* --- laptop ---------------------------------------------------------- */

  /* A whole laptop, seen slightly from above.

     It used to be a lid alone: rounded at the top, square at the bottom, bled
     past the card's padding so the cut edge read as a full-bleed screenshot
     rather than as a machine. Half a device is not a mockup of a device.

     The 3D is one perspective and one rotateX on the wrapper. Everything
     inside inherits it, so the screen content tilts with the lid instead of
     being drawn in false perspective, and the deck below picks up the same
     vanishing point. transform only: nothing here changes the layout box, so
     the tilt cannot widen the page at 375px. */
  .laptopframe {
    inline-size: 100%;
    max-inline-size: 420px;
    margin-inline: auto;
    transform: perspective(1400px) rotateX(9deg);
    transform-style: preserve-3d;
  }

  .laptopframe__lid {
    padding: 7px 7px 0;
    border-radius: var(--radius-input) var(--radius-input) var(--radius-hair) var(--radius-hair);
    background: var(--ink-deep);
    box-shadow:
      0 0 0 1px rgb(255 255 255 / 0.07) inset,
      0 18px 40px rgb(4 18 31 / 0.28);
  }

  .laptopframe__screen {
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-hair);
    background: var(--page);
    overflow: hidden;
    display: grid;
    grid-template-columns: 70px minmax(0, 1fr);
  }

  /* The deck. Wider than the lid and tapered, which is what reads as depth:
     a laptop seen from above shows its base spreading toward the viewer. The
     notch is the trackpad lip every machine has. */
  .laptopframe__base {
    display: block;
    inline-size: 112%;
    margin-inline: -6%;
    block-size: 13px;
    border-radius: 0 0 var(--radius-focus) var(--radius-focus);
    background: linear-gradient(180deg, var(--ink-mid) 0%, var(--ink-deep) 46%, var(--black) 100%);
    clip-path: polygon(0 0, 100% 0, 96.5% 100%, 3.5% 100%);
    position: relative;
    box-shadow: 0 10px 22px rgb(4 18 31 / 0.34);
  }

  .laptopframe__base::after {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    translate: -50% 0;
    inline-size: 16%;
    block-size: 3px;
    border-radius: 0 0 4px 4px;
    background: rgb(255 255 255 / 0.16);
  }

  .appside {
    padding: 10px 8px;
    background: var(--base);
    border-inline-end: 1px solid var(--line);
    display: grid;
    gap: 3px;
    align-content: start;
  }

  .appside__brand {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-end: 8px;
    padding-inline-start: 5px;
    font-family: var(--font-display);
    font-size: var(--size-micro);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--ink);
  }

  .appside__item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px;
    border-radius: var(--radius-chip);
    font-size: var(--size-mock-sm);
    color: var(--slate-700);
  }

  .appside__item .icon { inline-size: 11px; block-size: 11px; }

  .appside__item[data-on="true"] {
    background: var(--paper);
    color: var(--ink);
    font-weight: 600;
    box-shadow: var(--shadow-rest);
  }

  .appside__label {
    margin-block: 8px 2px;
    padding-inline-start: 5px;
    font-family: var(--font-display);
    font-size: var(--size-mock-xs);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--slate-500);
  }

  .appmain {
    padding: 12px;
    display: grid;
    gap: 10px;
    align-content: start;
    overflow: hidden;
  }

  .appmain__head {
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--ink);
  }

  .appmain__sub { font-size: var(--size-mock-sm); color: var(--slate-500); }

  .appmain__grid {
    display: grid;
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    gap: 8px;
    align-items: start;
  }

  .appmain__cards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .appmain__rail { display: grid; gap: 8px; }

  .apprail {
    padding: 10px;
    border-radius: var(--radius-input);
    background: var(--paper);
    border: 1px solid var(--line);
  }

  .apprail__title {
    font-family: var(--font-display);
    font-size: var(--size-mock);
    font-weight: 700;
    color: var(--ink);
    margin-block-end: 6px;
  }

  /* ======================================================================
     9. Map of India

     The reference draws a dotted globe with two pins on arcing paths. Same
     construction, different landmass: a network that reaches one country
     should not be illustrated with a planet.
     ====================================================================== */

  .indiamap {
    display: block;
    inline-size: 100%;
    max-inline-size: 300px;
    margin-inline: auto;
    margin-block-start: var(--space-4);
  }

  .indiamap__dots circle { fill: var(--slate-300); }

  /* Three dots lit, so the map reads as reach rather than as a static shape.
     Picked by index against the generated grid, which is stable: regenerating
     the map from the same boundary and column count produces the same order. */
  .indiamap__dots circle:nth-child(168),
  .indiamap__dots circle:nth-child(402) { fill: var(--sys-blue); r: 4.6; }
  .indiamap__dots circle:nth-child(288) { fill: var(--sys-orange); r: 4.6; }

  /* The drift component is sized for a full-bleed dark band. Inside a card it
     has roughly a third of the width, so the roundels shrink to match rather
     than overflowing and being clipped by the card. */
  .asym__card .speccircles__row { --d: 52px; gap: var(--space-2); }

  /* ======================================================================
     10. Confetti, for the closing panel
     ====================================================================== */

  .confetti {
    position: absolute;
    inset: 0;
    overflow: clip;
    border-radius: inherit;
    pointer-events: none;
  }

  .confetti span {
    position: absolute;
    inline-size: var(--s, 34px);
    block-size: var(--s, 34px);
    background: var(--c, var(--sys-blue));
    transform: rotate(var(--r, 0deg));
  }

  .confetti span[data-shape="circle"] { border-radius: 999px; }
  .confetti span[data-shape="pill"] { border-radius: var(--radius-pill) var(--radius-pill) var(--radius-pill) var(--radius-chip); }
  .confetti span[data-shape="tri"] { clip-path: polygon(50% 0, 100% 100%, 0 100%); }

  /* Below the two-column breakpoint the panel is barely wider than its copy,
     and confetti in the corners lands on the headline. */
  @media (max-width: 767px) {
    .confetti { display: none; }
  }
}

@layer components {
  /* ======================================================================
     Wordless app skeleton
     ----------------------------------------------------------------------
     The drawn screens used to carry real titles, live deadlines and a named
     certificate: an open-invitations feed with dates on it is what a member
     sees *after* signing in, and putting it on a public page both surfaces
     account-only logic and invites a reader to treat an illustration as a
     listing.

     Everything readable is now a bar. What survives is the only thing the
     mockups were ever for: the shape of the product. A coloured chip still
     says which kind of activity a row is, because the chip colours are the
     activity-type system used elsewhere on the page, and a colour is not a
     claim about what is currently open.
     ====================================================================== */

  .skel {
    display: block;
    border-radius: 999px;
    background: currentColor;
    opacity: 0.16;
  }

  /* Widths are deliberately uneven. A stack of identical bars reads as a
     loading state; uneven ones read as text that happens to be too small. */
  .skel--xs { inline-size: 22%; }
  .skel--sm { inline-size: 38%; }
  .skel--md { inline-size: 62%; }
  .skel--lg { inline-size: 84%; }
  .skel--full { inline-size: 100%; }

  .skel--h1 { block-size: 3px; }
  .skel--h2 { block-size: 5px; }
  .skel--h3 { block-size: 7px; }

  .skel--strong { opacity: 0.3; }

  .skel-stack {
    display: grid;
    gap: 4px;
  }

  /* The chip keeps its type colour and loses its word. Sized to the same box
     the labelled version occupied so the card's rhythm is unchanged. */
  .appchip--blank {
    inline-size: 34px;
    block-size: 9px;
    padding: 0;
    border-radius: 999px;
  }

  .appchip--blank[data-t="survey"]  { background: var(--chip-survey-solid); }
  .appchip--blank[data-t="review"]  { background: var(--chip-review-solid); }
  .appchip--blank[data-t="profile"] { background: var(--chip-profile-solid); }
  .appchip--blank[data-t="coach"]   { background: var(--chip-coach-solid); }
  .appchip--blank[data-t="case"]    { background: var(--chip-case-solid); }
  .appchip--blank[data-t="quiz"]    { background: var(--chip-quiz-solid); }

  /* A poll mid-answer, with nothing to read. The picked row keeps the navy
     ring the labelled version had. */
  .skelpoll {
    display: grid;
    gap: 6px;
    padding: var(--space-3);
  }

  .skelpoll__opt {
    display: flex;
    align-items: center;
    gap: 7px;
    min-block-size: 22px;
    padding-inline: 8px;
    border: 1px solid var(--line);
    border-radius: var(--radius-chip);
    color: var(--slate-700);
  }

  .skelpoll__opt::before {
    content: "";
    flex: none;
    inline-size: 11px;
    block-size: 11px;
    border-radius: 50%;
    border: 1.5px solid var(--line-strong);
  }

  .skelpoll__opt[data-picked="true"] {
    border-color: var(--ink);
    background: var(--base);
  }

  .skelpoll__opt[data-picked="true"]::before {
    border: 3.5px solid var(--ink);
  }

  .skelpoll__opt .skel { opacity: 0.22; }

  /* ======================================================================
     Compaction, /doctors/ only.

     The page ran to 6,488px, and the mockups were most of it: a phone trio
     647px tall in the first viewport, an asymmetric group of 1,161px, a
     showcase pair of 1,004px. The art is meant to show the product, not to be
     the product, and at those sizes a reader scrolls past three screens of
     grey bars before reaching the argument.

     Everything below shrinks the art and closes the rhythm. No content is
     removed; the section order and every word are unchanged.
     ====================================================================== */

  body[data-audience="doctors"] {
    --section-y: var(--space-12);
    --section-y-tight: var(--space-8);
  }

  @media (min-width: 900px) {
    body[data-audience="doctors"] {
      --section-y: var(--space-16);
      --section-y-tight: var(--space-12);
    }
  }

  /* The hero as one band.

     The opener, the device plate and the credibility row used to be three
     stacked things with air between each. They are now flush: the copy sits
     directly on the tinted plate and the markers meet the plate's foot.

     The devices are whole. An earlier pass cropped them at the plate's bottom
     edge to buy height back, which reads well but costs the viewer the one
     thing this section is for â€” seeing what the product looks like. Shrinking
     the frames buys the same height honestly: three complete phones, fanned,
     at a size where the feed still resolves. */
  .dochero {
    padding-block: 0;
    margin-block-start: var(--space-4);
  }

  .dochero__inner {
    padding-block: var(--space-12) var(--space-8);
  }

  /* .eyebrow is display: flex, which makes it a block-level flex container:
     the section's text-align: center could never reach it, so the label sat
     hard left under a centred headline and a centred lead. Centring belongs at
     the flex level. */
  .dochero .eyebrow {
    justify-content: center;
  }

  .dochero__lead {
    margin-block-start: var(--space-3);
    max-width: 34rem;
  }

  .dochero .cluster {
    margin-block-start: var(--space-5, 1.25rem);
  }

  .phonetrio {
    margin-block-start: 0;
    /* Whole devices, not cropped ones. Bottom padding runs deeper than top
       because the two side frames are translated 18px down: a transform does
       not grow the layout box, so that drop eats into the padding rather than
       into the band's height. */
    padding-block: var(--space-8) var(--space-12);
    /* Square at the foot: the markers meet it there, so a radius would leave
       two hairline gaps where the band and the row fail to touch. */
    border-radius: var(--radius-media) var(--radius-media) 0 0;
  }

  /* Sizing the frames.

     Nothing constrains the band's height now, so the frames set it: at 9/18.5
     every pixel of width costs two of band. 148px is the point where three
     whole phones and their fan still read as devices without the band growing
     taller than the copy above it. The fan offsets scale with them â€” the
     original Â±48/30 was drawn against a 268px frame and would throw the sides
     clear of the lead at this size. */
  @media (min-width: 900px) {
    .phonetrio__rail > .phoneframe {
      inline-size: 148px;
    }

    .phonetrio__side:first-child {
      transform: rotate(-8deg) translate(16px, 20px);
    }

    .phonetrio__side:last-child {
      transform: rotate(8deg) translate(-16px, 20px);
    }

    .phonetrio__lead {
      inline-size: 162px;
    }

    /* The props were pinned to the band's own edges, which worked while the
       phones were 268px and filled it. At 148px the trio occupies barely a
       third of the plate and edge-pinned props drift off into empty tint.
       Anchor them to the centre instead, just outside the fan, so they hold
       the composition together at any band width. */
    .phonetrio__prop--a {
      inset-block-start: 22%;
      inset-inline-start: auto;
      inset-inline-end: calc(50% + 210px);
    }

    .phonetrio__prop--b {
      inset-block-end: 26%;
      inset-inline-end: auto;
      inset-inline-start: calc(50% + 210px);
    }
  }

  /* One frame below 900px, so it can afford to be a little larger than the
     three â€” but a definite width either way. Left at `inline-size: 100%` in a
     centred grid item it is shrink-to-fit, and the aspect ratio resolves off
     whatever height the row happens to have. */
  @media (max-width: 899px) {
    .phonetrio__rail > .phoneframe {
      flex: none;
      inline-size: min(160px, 46vw);
    }
  }

  .markers {
    margin-block-start: 0;
    padding-block: var(--space-5, 1.25rem);
    border-block-start: 0;
    gap: var(--space-3) var(--space-8);
  }

  /* The bento. Tighter cards, and the art inside them no longer sets the
     card's height. */
  .asym__card {
    padding: var(--space-6);
  }

  .asym__card p {
    margin-block-start: var(--space-2);
  }

  .asym__art {
    margin-block-start: var(--space-4);
    margin-block-end: calc(var(--space-6) * -1);
    margin-inline-end: calc(var(--space-6) * -1);
  }

  /* 32px, not 24: the ring inside needs the extra breathing room at its
     widest point or the quote cards touch the card's edge. */
  .asym__dark {
    padding: var(--space-8);
  }

  .asym__card .speccircles {
    margin-block: var(--space-4) 0 !important;
  }

  /* The fan and its figure. */
  .bigfan {
    margin-block-start: var(--space-8);
  }

  /* The showcase pair: art at a size that leaves the copy in charge.

     Both frames are sized off the hero's. The trio at the top of the page sets
     a phone at 148px, and a second phone further down at 188px read as a
     different, larger device rather than as the same product seen again. The
     laptop takes the same 0.79 reduction so the pair still balances. */
  .showcase__card {
    padding: var(--space-5, 1.25rem);
  }

  .showcase__card h3 {
    font-size: 1.5rem;
  }

  .showcase__card > p {
    margin-block-start: var(--space-3);
  }

  .showcase__art {
    margin-block-start: var(--space-4);
  }

  .showcase__art .phoneframe {
    max-inline-size: 148px !important;
  }

  .showcase__art .laptopframe {
    max-inline-size: 330px;
  }

  /* The product does something when you show interest in it.

     This is the most persuasive motion available on this page, and it is also
     the one most easily overdone: it runs on hover and focus only, never on
     load and never on a loop, so nobody sits through it who did not ask.
     Reduced motion removes it entirely â€” see the block at the foot of this
     file. */
  .showcase__art .appfeed {
    transition: transform 900ms var(--ease-out-quint);
  }

  .showcase__card:hover .appfeed,
  .showcase__card:focus-within .appfeed {
    transform: translateY(-58px);
  }

  /* On the desk, the poll answers itself. */
  .showcase__art .skelpoll__opt {
    transition:
      background-color var(--dur-state) var(--ease-out-quint),
      border-color var(--dur-state) var(--ease-out-quint);
  }

  .showcase__card:hover .skelpoll__opt:nth-of-type(2),
  .showcase__card:focus-within .skelpoll__opt:nth-of-type(2) {
    border-color: var(--accent);
    background: var(--tint-teal-soft);
  }

  @media (prefers-reduced-motion: reduce) {
    .showcase__art .appfeed,
    .showcase__card:hover .appfeed,
    .showcase__card:focus-within .appfeed {
      transition: none;
      transform: none;
    }
  }


  /* The bento columns have to end together.

     The left stack is two cards, the right is one, and the map was setting the
     left column's height on its own: the dark card stretched to match and
     ended with four hundred pixels of empty navy under the last quote. A card
     that is mostly nothing reads as a card that is missing something. */
  .indiamap {
    max-inline-size: 216px;
    margin-block-start: var(--space-2);
  }

  /* The showcase art was pinned to the bottom of a 26rem card with 3rem of
     padding above it, so both cards opened with a band of empty grey between
     the copy and the device. The card is shorter now and the art sits directly
     under what describes it. */
  .showcase__card {
    min-block-size: 0;
    padding-block-end: var(--space-5, 1.25rem);
  }

  .showcase__art {
    margin-block-start: var(--space-4);
    padding-block-start: 0;
  }


  /* The dark card is stretch-matched to the two-card column beside it, and the
     ring is what now spends that height: `.voicering { flex: 1 }` lets the
     circle grow into whatever the column gives it instead of the card
     bottoming out in empty navy. */
  .asym__dark {
    display: flex;
    flex-direction: column;
  }

}

}
/* Auth. The component first, then the two pages that use it, both after
   components.css because they tighten .field rather than redefine it. */
/* ---- components/otp-input/otp-input.css ---- */
@layer components {
/* ==========================================================================
   OTP input
   --------------------------------------------------------------------------
   The supplied component re-tokenised. Every hard-coded value in the original
   is mapped to something the rest of the site already owns:

     #e7e5e4 / #d6d3d1 borders   ->  --line / --line-strong
     #4568ff active               ->  --accent, the site has no blue
     #ef4444 / #10b981            ->  --error / --success
     #44403c character            ->  --text
     its own prefers-color-scheme ->  the .dark token flip, so the component
                                      inverts inside a navy panel and nowhere
                                      else

   That last one matters. The original switched to dark on the visitor's OS
   preference, which would have turned the cells dark on a white page. Here
   the surrounding surface decides, which is how every other component on the
   site behaves.
   ========================================================================== */

@layer components {
  .otp-input {
    display: inline-flex;
    flex-direction: column;
  }

  .otp-cells {
    position: relative;
    display: flex;
    gap: var(--space-2);
  }

  .otp-cells.shake {
    animation: otpShake 0.32s ease;
  }

  .otp-cell {
    position: relative;
    /* 48px, which clears the 44px touch target with the row still reading as
       compact. The original 3rem x 2.5rem is under it on the short side. */
    block-size: 48px;
    inline-size: 44px;
  }

  /* The visual break between the two triples. Margin rather than a wider gap
     so the cells either side keep their own rhythm. */
  .otp-cell.grouped {
    margin-inline-start: var(--space-3);
  }

  .otp-cell input {
    inline-size: 100%;
    block-size: 100%;
    border-radius: var(--radius-input);
    border: 2px solid var(--border);
    background: var(--surface-sunken);
    box-shadow: inset 0 1px 2px rgb(7 28 56 / 0.07);
    text-align: center;
    font-size: var(--size-body);
    /* The input holds the value; the character is drawn by the span beside
       it, which is what allows the per-character entrance. Hiding the text
       here rather than the input means autofill, paste and the software
       keyboard all still behave. */
    color: transparent;
    caret-color: transparent;
    transition:
      background-color var(--dur-micro) var(--ease-out-quint),
      border-color var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint);
  }

  .otp-cell input::selection {
    background: transparent;
  }

  .otp-cell input:disabled {
    opacity: 0.5;
  }

  .otp-cell input.filled {
    border-color: var(--line-strong);
    background: var(--surface);
    box-shadow: none;
  }

  .otp-cell input.active {
    border-color: var(--accent);
    background: var(--surface);
    box-shadow: none;
  }

  /* The focus ring stays. The active border is a state, not a focus
     indicator, and removing the outline would leave a keyboard user with a
     2px colour change to find. */
  .otp-cell input:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }

  .otp-cells.error .otp-cell input {
    border-color: var(--error);
    background: var(--surface);
    box-shadow: none;
  }

  .otp-cells.success .otp-cell input {
    border-color: var(--success);
    background: var(--surface);
    box-shadow: none;
  }

  .otp-cell-display {
    pointer-events: none;
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
  }

  .otp-char {
    font-family: var(--font-display);
    font-size: var(--size-body);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    opacity: 0;
    filter: blur(6px);
    transform: translateY(10px) scale(0.97);
    transition:
      opacity 220ms cubic-bezier(0.23, 1, 0.32, 1),
      filter 220ms cubic-bezier(0.23, 1, 0.32, 1),
      transform 220ms cubic-bezier(0.23, 1, 0.32, 1);
  }

  .otp-char.shown {
    opacity: 1;
    filter: blur(0);
    transform: translateY(0) scale(1);
  }

  .otp-caret {
    block-size: 18px;
    inline-size: 1.5px;
    border-radius: var(--radius-hair);
    background: var(--text);
    animation: otpCaretBlink 1.06s linear infinite;
  }

  .otp-status {
    margin-block-start: var(--space-2);
    /* Reserved, so the row below does not jump when a message appears. */
    min-block-size: 1rem;
    font-size: 0.75rem;
    line-height: 1.35;
    opacity: 0;
    transform: translateY(3px);
    transition:
      opacity 180ms ease,
      transform 180ms ease;
  }

  .otp-status.visible {
    opacity: 1;
    transform: translateY(0);
  }

  .otp-status.error { color: var(--error); }
  .otp-status.success { color: var(--success); }
  .otp-status.idle { color: var(--text-meta); }

  .otp-sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* Inside a navy panel the cells take the panel's own aliases. --error and
     --success are unchanged there: both clear AA on navy, and a second pair
     of state colours would be two things to keep in step. */
  .dark .otp-cell input {
    box-shadow: inset 0 1px 2px var(--scrim-soft);
  }

  @keyframes otpShake {
    0% { transform: translateX(0); }
    25% { transform: translateX(-5px); }
    50% { transform: translateX(4px); }
    75% { transform: translateX(-3px); }
    100% { transform: translateX(0); }
  }

  @keyframes otpCaretBlink {
    0%, 50% { opacity: 1; }
    50.01%, 100% { opacity: 0; }
  }
}

/* Unlayered, so it beats the layered rules above exactly the way the site's
   other reduced-motion overrides do. A wrong code still shakes nothing, the
   caret still marks position, and the character still appears. */
@media (prefers-reduced-motion: reduce) {
  .otp-cells.shake { animation: none !important; }
  .otp-char { transition: opacity 1ms !important; filter: none !important; transform: none !important; }
  .otp-caret { animation: none !important; opacity: 1 !important; }
}

}
/* ---- sign-in.css ---- */
@layer components {
/* ==========================================================================
   Sign in
   --------------------------------------------------------------------------
   The supplied component's layout, re-tokenised.

   What changed, and why each one had to:

   - Fonts. The component asks for 'Geist'. The site self-hosts Figtree and Inter, and a test asserts nothing is fetched from Google, so the
     page inherits the site's faces rather than naming one.
   - Colour. The component's accent is #a78bfa, a violet. This palette has no
     purple in it by decision; links and accents are --accent teal. Its
     --foreground / --muted-foreground / --primary variables do not exist here
     either, so every one is mapped to the site's own semantic alias, which is
     what makes the panel invert correctly under .dark for free.
   - Animation. .animate-element sets opacity: 0 and relies on a CSS animation
     to bring it back, which strands the content if the animation never runs.
     The site's data-choreo does the same job gated on html.js, so it cannot
     hide anything a reader might not get back. The delay ladder survives as
     --choreo-index.
   - Height. 100dvh assumes the component owns the viewport. Here it sits
     between a nav and a footer, so it takes a minimum and grows.
   ========================================================================== */

@layer components {
  .signin {
    display: grid;
    /* Full bleed. A sign-in screen that stops at the 1180px container reads as
       a card floating on a page rather than as a place you have arrived. */
    margin-inline: calc(50% - 50vw);
    min-block-size: calc(100dvh - var(--nav-total, 76px));
  }

  @media (min-width: 900px) {
    .signin {
      /* The form gets the smaller share. It is a column of controls with a
         fixed comfortable width; the panel beside it can take the slack. */
      grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    }
  }

  /* Mobile first, and compact by default. The larger tier below is the
     exception, not the base: a phone has the least room and gets the tightest
     rhythm, which is also the one that has to be tested hardest. */
  .signin__panel {
    display: grid;
    align-items: center;
    justify-items: center;
    padding: var(--space-8) var(--gutter);
  }

  @media (min-width: 900px) {
    .signin__panel { padding: var(--space-12) var(--gutter); }
  }

  /* Short desktop windows, which is most laptops once the browser chrome is
     out of the way. Nothing is dropped; the air comes out. */
  @media (min-width: 900px) and (max-height: 820px) {
    .signin__panel { padding: var(--space-6) var(--gutter); }
  }

  .signin__form-wrap {
    inline-size: 100%;
    max-inline-size: 27rem;
  }

  .signin__title {
    font-size: clamp(1.75rem, 2.6vw, 2.25rem);
    line-height: 1.1;
    letter-spacing: -0.03em;
  }

  .signin__lead {
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    line-height: 1.5;
    color: var(--text-muted);
    max-inline-size: 38ch;
  }

  .signin__form {
    display: grid;
    gap: var(--space-4);
  }

  .signin__form .btn--full {
    margin-block-start: var(--space-4);
  }

  /* The component rounds its inputs to 1rem and tints them, which is a softer
     control than the site's 12px --radius-input. Kept: it is the one piece of
     the component's own character worth carrying over, and it is scoped here
     rather than changed globally. */
  .signin__form .field input {
    min-block-size: 44px;
    border-radius: var(--radius-soft);
    padding: var(--space-3) var(--space-4);
    background: var(--surface-sunken);
  }

  .signin__form .field input:focus-visible {
    background: var(--surface);
  }

  .signin__status {
    font-size: var(--size-small);
    line-height: 1.4;
    color: var(--text-muted);
    /* Reserved so the block below does not jump, but no taller than one line. */
    min-block-size: 1.25rem;
  }

  /* Tone is set by the script. Empty and neutral states take no colour at
     all, so an idle status line is not a permanently amber row. */
  .signin__status[data-tone="error"] { color: var(--error); }
  .signin__status[data-tone="success"] { color: var(--success); }
  .signin__status[data-tone="pending"] { color: var(--amber-deep); }

  /* --- the other way in -------------------------------------------------- */

  .signin__alt {
    margin-block-start: var(--space-6);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border);
    font-size: var(--size-small);
    color: var(--text-muted);
  }

  .signin__fine {
    margin-block-start: var(--space-3);
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  /* --- the panel --------------------------------------------------------- */

  .signin__aside {
    display: none;
    position: relative;
    isolation: isolate;
    overflow: clip;
    background: var(--ink-deep);
    padding: var(--space-12);
    align-content: end;
  }

  @media (min-width: 900px) {
    .signin__aside { display: grid; }
  }

  .signin__aside picture {
    position: absolute;
    inset: 0;
    z-index: -1;
  }

  .signin__photo {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: center 35%;
  }

  /* The scrim, not a filter on the image: type sits on the lower half and
     needs a floor to stand on, while the top of the photograph stays as
     bright as it was shot. */
  .signin__aside::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(
      to top,
      rgb(4 18 31 / 0.94) 0%,
      rgb(4 18 31 / 0.78) 38%,
      rgb(4 18 31 / 0.25) 72%,
      rgb(4 18 31 / 0.08) 100%
    );
  }

  .signin__quotes {
    display: grid;
    gap: var(--space-6);
    max-inline-size: 32rem;
  }

  /* The component's testimonial card is a frosted tile. Kept, in navy: the
     blur is doing real work here because there is a photograph behind it. */
  .signin__quote {
    margin: 0;
    padding: var(--space-6);
    border-radius: var(--radius-card);
    background: rgb(255 255 255 / 0.08);
    border: 1px solid rgb(255 255 255 / 0.14);
    backdrop-filter: blur(14px);
  }

  .signin__quote blockquote {
    margin: 0;
    color: var(--paper);
    font-size: 1.0625rem;
    line-height: 1.5;
  }

  .signin__quote figcaption {
    display: grid;
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
  }

  .signin__who {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--paper);
  }

  .signin__where {
    color: var(--text-meta);
  }

  /* Below 1200px the second quote crowds the first against the foot of a
     shorter panel. One is enough to do the job. */
  @media (max-width: 1199px) {
    .signin__quote:nth-child(2) { display: none; }
  }

  /* ======================================================================
     Added for the one-time-code flow and for /sign-up/, which uses this
     same panel and the same aside.

     Everything below extends the block above rather than replacing it: the
     split layout, the photograph, the frosted quotes and the soft 1rem
     controls are all unchanged. What is new is a social row, an OR rule, a
     second step for the code, and the field rows sign-up needs.
     ====================================================================== */

  /* Sign-up carries three columns of name and two of role, so its column is
     wider. The panel, the aside and the ratio between them are untouched. */
  .signin__form-wrap--wide {
    max-inline-size: 30rem;
  }

  .signin__social {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
    margin-block-start: var(--space-5, 1.25rem);
  }

  /* Same 1rem radius as the fields, so the row reads as part of the form
     rather than as three buttons that arrived from somewhere else. */
  .signin__provider {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-inline: var(--space-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-soft);
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    cursor: pointer;
    transition:
      background-color var(--dur-micro) var(--ease-out-quint),
      border-color var(--dur-micro) var(--ease-out-quint);
  }

  .signin__provider:hover {
    background: var(--surface-sunken);
    border-color: var(--text-meta);
  }

  .signin__provider svg { flex: none; }

  /* Below 420px three labelled buttons wrap into an unreadable row, so the
     mark carries the button and the word is taken off screen.

     This hides the label properly rather than with font-size: 0. Zero-height
     text is a rendering trick that different engines and translation tools
     treat inconsistently, and it fails badly in the one case that matters: if
     the provider mark does not load, a font-size: 0 button is a blank square
     with no visible or recoverable label. Taking the word off screen keeps it
     in the accessibility tree and lets it come back.

     Same declarations as .visually-hidden in base.css, which cannot be reused
     directly because this only applies inside a media query. */
  @media (max-width: 419px) {
    .signin__provider { gap: 0; }
    .signin__provider svg { inline-size: 20px; block-size: 20px; }

    .signin__provider-label {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
    }
  }

  .signin__or {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-3);
    margin-block: var(--space-4);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .signin__or::before,
  .signin__or::after {
    content: "";
    block-size: 1px;
    background: var(--border);
  }

  /* --- sign-up field rows ------------------------------------------------ */

  .signin__fields {
    display: grid;
    gap: var(--space-3);
  }

  .signin__pair {
    display: grid;
    gap: var(--space-3);
  }

  @media (min-width: 480px) {
    .signin__pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Title is a two-character select and does not need a third of the row. */
    .signin__pair--name { grid-template-columns: 5.5rem minmax(0, 1fr) minmax(0, 1fr); }
  }

  /* Tighter than the .field default, which was drawn for the demo form: a page
     someone fills in once at a desk. This is a control used on a phone between
     patients, so the label sits close and the gap between rows carries the
     separation instead. */
  .signin__form .field {
    gap: 4px;
  }

  .signin__form .field > label {
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    text-transform: none;
    color: var(--text-muted);
  }

  .signin__form .field select {
    min-block-size: 44px;
    border-radius: var(--radius-soft);
    padding-inline: var(--space-3);
    background: var(--surface-sunken);
  }

  /* The optional group. A fieldset and legend rather than a styled div: the
     two controls inside genuinely are a group, and saying "optional" once as
     the group's name beats repeating it on every label. Lighter and smaller
     than the required fields above, so the eye can skip it. */
  /* --- the card ----------------------------------------------------------
     Everything asked before a code is sent, inside one bordered box with a
     header row. The shape is the one every Indian health platform uses, and
     using it here costs nothing: a visitor recognises where the form starts
     and ends without reading a word of it.
     ---------------------------------------------------------------------- */

  .signin__card {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
    overflow: clip;
  }

  .signin__cardhead {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-5, 1.25rem) var(--space-6);
    border-block-end: 1px solid var(--border);
  }

  .signin__cardhead .signin__title {
    font-size: var(--size-h3);
    line-height: 1.2;
    letter-spacing: -0.02em;
  }

  .signin__cardalt {
    font-size: var(--size-small);
    color: var(--text-muted);
  }

  .signin__cardbody { padding: var(--space-6); }

  /* One line under the card, where a card footer would otherwise be. It is
     the audience gate, and it is the only sentence on the page that is not a
     label, a control or a legal name. */
  .signin__under {
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
    color: var(--text-meta);
    text-align: center;
  }

  /* --- role, as two answers rather than a menu --------------------------- */

  .signin__roles {
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
  }

  .signin__roles > legend {
    padding: 0;
    margin-block-end: 4px;
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--text-muted);
  }

  .signin__roles > legend,
  .signin__roles .field__error { grid-column: 1 / -1; }

  /*
   * The chosen answer is filled, not merely outlined.
   *
   * A tinted background behind navy text is a difference of about 4% in
   * luminance: it reads as "slightly different" rather than as "this one",
   * and on a ward phone in poor light it does not read at all. Navy fill,
   * white text and a drawn tick are unmistakable at arm's length, and they
   * stay unmistakable in greyscale â€” which is the test that matters, because
   * colour alone can never be the answer.
   */
  .signin__role {
    position: relative;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--space-3);
    min-block-size: 56px;
    padding: var(--space-3) var(--space-4);
    border: 2px solid var(--line-strong);
    border-radius: var(--radius-soft);
    background: var(--surface);
    cursor: pointer;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint);
  }

  .signin__role:hover { border-color: var(--accent); }

  .signin__role input {
    position: absolute;
    opacity: 0;
    inline-size: 1px;
    block-size: 1px;
  }

  /* The mark: an empty ring, then a filled ring with a white centre. */
  .signin__role::before {
    content: "";
    inline-size: 20px;
    block-size: 20px;
    border: 2px solid var(--line-strong);
    border-radius: var(--radius-pill);
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint);
  }

  .signin__role-title {
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
    line-height: 1.2;
  }

  .signin__role:has(input:checked) {
    border-color: var(--ink);
    background: var(--ink);
    color: var(--paper);
  }

  .signin__role:has(input:checked)::before {
    border-color: var(--paper);
    box-shadow: inset 0 0 0 4px var(--ink), inset 0 0 0 20px var(--paper);
  }

  /* The ring has to follow the hidden input, or a keyboard reader cannot see
     where they are. */
  .signin__role:has(input:focus-visible) {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }

  /* --- the number, with its country code --------------------------------
     A prefix drawn beside the control rather than a second select. There is
     one country to choose from, and a dropdown containing one answer is a
     question that should not have been asked.
     ---------------------------------------------------------------------- */

  .signin__phone {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: stretch;
    gap: var(--space-2);
  }

  .signin__prefix {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding-inline: var(--space-3);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-soft);
    background: var(--surface-sunken);
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  /* Hidden, not removed: the same control carries an email address when the
     swap is taken, and a "+91" beside one would be a lie. */
  .signin__phone[data-hidden="true"] .signin__prefix { display: none; }
  .signin__phone[data-hidden="true"] { grid-template-columns: minmax(0, 1fr); }

  .signin__swap {
    justify-self: start;
    margin-block-start: 2px;
  }

  /* 3:2, the flag's own ratio. A hairline is drawn inside the artwork,
     because the middle band is white and the control it sits on is not far
     off it. */
  .signin__flag {
    inline-size: 21px;
    block-size: 14px;
    flex: none;
    border-radius: 2px;
  }

  .signin__optional {
    border: 0;
    border-block-start: 1px solid var(--border);
    margin: 0;
    padding: var(--space-4) 0 0;
    min-inline-size: 0;
  }

  /* Sentence case, not uppercase. This legend is an instruction rather than a
     label, and a 34-character sentence set in caps loses the word shapes the
     eye reads by, which is the one thing a reassurance in a signup form
     cannot afford. Dropping the caps also takes it off --size-micro, which is
     reserved for uppercase tracked labels, and onto the 12px floor. */
  .signin__optional legend {
    padding: 0;
    font-size: var(--size-eyebrow);
    font-weight: 600;
    color: var(--text-meta);
  }

  .signin__optional .field {
    margin-block-start: var(--space-3);
  }

  .signin__optional .field > label,
  .signin__optional .field select {
    color: var(--text-meta);
  }

  /* The label is the checkbox's hit area, so it carries the target size. With
     a 44px row and one line of text, align-items: start leaves the box sitting
     at the top of a box twice its height and the text apparently floating
     above it; centred, the two line up whether the sentence wraps or not. */
  .signin__form .checkbox {
    align-items: center;
    gap: var(--space-3);
    font-size: var(--size-ui-sm);
    line-height: 1.5;
    color: var(--text-muted);
    min-block-size: 44px;
  }

  .signin__form .checkbox input {
    margin-block-start: 0;
  }

  /* --- the code step ----------------------------------------------------- */

  /* aria-hidden in the markup: the step count is orientation for a reader
     scanning the page, and a screen reader already gets the same information
     from the live-region status and the heading. Announcing "Step 1 of 2"
     before every field is noise. */
  .signin__step {
    margin-block-end: var(--space-5, 1.25rem);
    font-family: var(--font-display);
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .signin__sent {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    margin-block-end: var(--space-4);
    font-size: var(--size-small);
    color: var(--text-muted);
  }

  .signin__sent-label {
    font-size: var(--size-micro);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .signin__sent b {
    font-family: var(--font-display);
    color: var(--text);
    font-variant-numeric: tabular-nums;
  }

  .signin__otp {
    display: flex;
    /* Six 44px cells, five 8px gaps and one 12px group break come to 308px,
       which fits inside a 320px viewport's gutters. */
    justify-content: center;
  }

  .signin__coderow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-block-start: var(--space-4);
    font-size: var(--size-small);
  }

  .signin__resend {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
  }

  /* A button that reads as a link, because it is one of two equal choices at
     the foot of the step and neither should look like the primary action. */
  .signin__linkbtn {
    min-block-size: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--link);
    font-family: var(--font-body);
    font-size: var(--size-small);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  .signin__linkbtn:hover { color: var(--accent-ink); }

  .signin__linkbtn:disabled {
    color: var(--text-meta);
    text-decoration: none;
    cursor: default;
  }

  .signin__countdown {
    color: var(--text-meta);
    font-variant-numeric: tabular-nums;
  }
  /* Shorter still: a half-height window, or a 1280x640 laptop. This is the
     floor â€” below it the page scrolls rather than shrinking the type, because
     a sign-in nobody can read is worse than one that scrolls. */
  @media (min-width: 900px) and (max-height: 660px) {
    .signin__panel { padding: var(--space-4) var(--gutter); }
    .signin__lead { margin-block-start: 0; }
    .signin__social { margin-block-start: var(--space-3); }
    .signin__or { margin-block: var(--space-3); }
    .signin__form { gap: var(--space-3); }
    .signin__form .btn--full { margin-block-start: var(--space-3); }
    .signin__status { min-block-size: 1rem; }

    .signin__alt {
      margin-block-start: var(--space-4);
      padding-block-start: var(--space-3);
    }

    .signin__fine { margin-block-start: var(--space-2); }
  }
}

}
/* The flow /sign-up/ hands over to. After sign-in.css because it borrows
   that surface's softened control character rather than redefining it. */
/* ---- onboarding.css ---- */
@layer components {
/* ==========================================================================
   Onboarding
   --------------------------------------------------------------------------
   The screens between "account created" and "verified member", reached from
   /sign-up/ once a code is verified.

   Structure follows the pattern a doctor arriving from any other Indian
   health platform already knows: one question set per screen, a step count
   and a progress line in the bar, a reason panel in the margin, Back and
   Continue at the foot. What is different is how it reads â€” a white working
   surface with real type on it rather than a form dumped on grey.

   Rules held to, from DESIGN.md:
   - No eyebrow. The step count lives in the bar, not above the heading.
   - Fixed type sizes. Product UI at a consistent DPI; no fluid headings.
   - Flat at rest. Tone and hairlines before shadows.
   - White on every brand colour; nothing saturated carries a letterform on
     the navy close.
   ========================================================================== */
@layer components {

  /* ======================================================================
     Shell
     ====================================================================== */

  .ob {
    background: var(--page);
    min-block-size: 100dvh;
  }

  /* --- the bar ------------------------------------------------------------
     Not the site nav. Someone four fields into joining has one destination
     and one escape; a bar offering Doctors / Students / About invites them
     to leave the thing they are ninety seconds from finishing. */

  .ob__bar {
    position: sticky;
    inset-block-start: 0;
    z-index: 20;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--space-4);
    /* Aligned with the working column below it, not with the viewport edge:
       a wordmark that does not sit on the same vertical as the heading it
       introduces is the first thing a designer's eye catches. */
    padding-block: var(--space-2);
    padding-inline:
      max(var(--gutter), calc(50vw - 31.5rem), env(safe-area-inset-left))
      max(var(--gutter), calc(50vw - 31.5rem), env(safe-area-inset-right));
    background: var(--paper);
    border-block-end: 1px solid var(--border);
  }

  .ob__brand {
    display: inline-flex;
    align-items: center;
    min-block-size: 44px;
  }

  .ob__brand img {
    inline-size: auto;
    block-size: 26px;
  }

  /* Centre column: which screen this is, in tabular figures so the number
     does not shift width as it counts up. */
  .ob__count {
    justify-self: center;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--text-meta);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  /* justify-self, because the bar is a three-column grid and the sign-up
     screen has no step counter to fill the middle one. Without this the
     escape hatch drifts into the centre on that page alone. */
  .ob__baractions {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  .ob__exit {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-inline: var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition:
      color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__exit:hover {
    color: var(--ink);
    background: var(--surface-sunken);
  }

  @media (max-width: 719px) {
    /* On a phone the two escape hatches are worth more than the counter, and
       the words on them are worth less than the space. */
    .ob__count { display: none; }
    .ob__exit { padding-inline: var(--space-2); }
    .ob__exit span { display: none; }
  }

  @media (max-width: 419px) {
    .ob__exit { font-size: 0; gap: 0; }
    .ob__exit svg { inline-size: 20px; block-size: 20px; }
  }

  /* --- the progress line -------------------------------------------------
     A hairline, not a bar. It sits on the boundary the bar already draws, so
     the page gains a reading of progress without gaining a component. */

  .ob__progress {
    position: absolute;
    inset-block-end: -1px;
    inset-inline: 0;
    block-size: 2px;
    background: transparent;
    overflow: clip;
  }

  .ob__progress span {
    display: block;
    block-size: 100%;
    inline-size: var(--progress, 0%);
    background: var(--ink);
    transition: inline-size var(--dur-state) var(--ease-out-quint);
  }

  /* ======================================================================
     The working surface
     ====================================================================== */

  /*
   * A screen has to fit the viewport with no scrolling on a laptop. The rhythm
   * is therefore a variable rather than a fixed step: it opens at 1.25rem and
   * closes to 0.75rem as the window gets shorter. Nothing is dropped and no
   * type gets smaller; the air comes out.
   */
  .ob__main {
    --ob-gap: 1.25rem;
    --ob-top: var(--space-12);
    inline-size: 100%;
    max-inline-size: 63rem;
    margin-inline: auto;
    padding: var(--ob-top) var(--gutter) var(--space-16);
  }

  @media (min-width: 900px) {
    .ob__main { --ob-top: var(--space-10, 2.5rem); }
  }

  @media (min-width: 900px) and (max-height: 900px) {
    .ob__main { --ob-gap: 1rem; --ob-top: var(--space-8); }
  }

  @media (min-width: 900px) and (max-height: 780px) {
    .ob__main { --ob-gap: 0.75rem; --ob-top: var(--space-6); }
  }

  .ob__panel {
    display: grid;
    gap: var(--space-6);
    align-content: start;
  }

  .ob__panel[hidden] { display: none; }

  @media (min-width: 1040px) {
    .ob__panel {
      grid-template-columns: minmax(0, 34rem) minmax(0, 17rem);
      grid-template-areas:
        "head why"
        "body why"
        "nav  why";
      column-gap: var(--space-12);
      row-gap: var(--space-6);
      align-items: start;
    }

    .ob__head { grid-area: head; }
    .ob__body { grid-area: body; }
    .ob__why { grid-area: why; }
    .ob__nav { grid-area: nav; }
  }

  /* 1.75rem at every width, which is the floor of the system's --size-h2 and
     therefore on the ramp. A step heading that grows with the viewport is a
     marketing habit: this is a form, and the question should be the same size
     on a ward phone as on a desk. */
  .ob__head {
    max-inline-size: 34rem;
    margin-block-end: var(--space-6);
  }

  .ob__head h1 {
    font-size: 1.75rem;
    line-height: 1.15;
    letter-spacing: var(--tracking-h2);
    text-wrap: balance;
  }

  .ob__lead {
    margin-block-start: var(--space-2);
    max-inline-size: 52ch;
    font-size: var(--size-body);
    line-height: 1.55;
    color: var(--text-muted);
  }

  /* --- fields -------------------------------------------------------------
     The auth surface's control character, carried through: a 1rem radius on
     a sunken fill, which DESIGN.md allows on exactly these screens. The
     height is 52px because the reader is one-handed on a phone. */

  /* Between groups, not within them: the gap here is twice the gap inside
     .ob__fields, which is what makes the groups read as groups. */
  .ob__body { display: grid; gap: var(--space-6); }

  .ob__fields { display: grid; gap: var(--ob-gap, 1.25rem); }

  .ob__body .field { gap: 6px; }

  .ob__body .field > label {
    font-size: var(--size-ui-sm);
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text-muted);
  }

  /* Text controls only. A checkbox dragged to 52px tall sits two-thirds of a
     line below the sentence it belongs to, which is what happened here. */
  .ob__body .field input:not([type="checkbox"]):not([type="radio"]),
  .ob__body .field select {
    min-block-size: 48px;
    border-radius: var(--radius-soft);
    border-color: transparent;
    padding: var(--space-3) var(--space-4);
    background: var(--surface-sunken);
    font-size: var(--size-ui-lg);
    color: var(--ink);
  }

  .ob__body .field select { padding-inline: var(--space-4); }

  .ob__body .field input:not([type="checkbox"]):hover,
  .ob__body .field select:hover { border-color: var(--line-strong); }

  /* Focus lifts the control off the sunken fill, which is the cheapest way
     to say "this one, now" without a coloured glow. */
  .ob__body .field input:not([type="checkbox"]):focus-visible,
  .ob__body .field select:focus-visible {
    background: var(--surface);
    border-color: var(--accent);
  }

  .ob__body .field [aria-invalid="true"] { border-color: var(--error); }

  /* The browser's placeholder grey measures about 4:1 on this fill, under
     the floor for the text a reader is most likely squinting at. */
  .ob__body .field input:not([type="checkbox"])::placeholder { color: var(--text-meta); }

  /* --- combobox -----------------------------------------------------------
     A text input with a filtered list under it. The member types and the
     list gets shorter. The member can also type an answer that is not in the
     list, because no list of Indian institutions is complete.
     ---------------------------------------------------------------------- */

  .ob__combo {
    position: relative;
  }

  .ob__combo-list {
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: calc(100% + 4px);
    /* Wider than the field when the field is narrow. A degree column three
       words wide would otherwise wrap every option over three lines. */
    min-inline-size: max(100%, 20rem);
    max-inline-size: min(30rem, 85vw);
    z-index: 30;
    max-block-size: 17rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    margin: 0;
    padding: var(--space-1) 0;
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-input);
    box-shadow: var(--shadow-raised);
  }

  .ob__combo-heading {
    padding: var(--space-2) var(--space-4) 4px;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 700;
    color: var(--text-meta);
  }

  .ob__combo-option {
    padding: 10px var(--space-4);
    min-block-size: 40px;
    display: flex;
    align-items: center;
    font-size: var(--size-ui);
    color: var(--text);
    cursor: pointer;
  }

  .ob__combo-option:hover,
  .ob__combo-option.is-active {
    background: var(--tint-page-cool);
  }

  .ob__combo-option[aria-selected="true"] {
    font-weight: 600;
  }

  /* Above the field when there is no room below it, so the list never covers
     the button the member is about to press. */
  .ob__combo[data-drop="up"] .ob__combo-list {
    inset-block-start: auto;
    inset-block-end: calc(100% + 4px);
  }

  /* The year picker: a snapping column, taller rows, and the numbers in
     tabular figures so the digits do not shift as the column scrolls. */
  .ob__combo-list--picker {
    scroll-snap-type: y mandatory;
    max-block-size: 15rem;
    font-variant-numeric: tabular-nums;
  }

  .ob__combo-list--picker .ob__combo-option {
    scroll-snap-align: center;
    justify-content: center;
    min-block-size: 44px;
    font-size: var(--size-ui-lg);
    font-weight: 600;
    letter-spacing: 0.01em;
  }

  .ob__combo-empty {
    margin-block-start: 4px;
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }

  /* --- qualifications, one row each --------------------------------------- */

  .ob__quals {
    display: grid;
    gap: var(--space-3);
  }

  .ob__qual {
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0;
  }

  /* A second row and every row after it is separated by a rule, so repeated
     groups read as repeated rather than as one long form. */
  .ob__qual + .ob__qual {
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--border);
  }

  .ob__qual > legend {
    padding: 0;
    margin-block-end: 6px;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--text-meta);
  }

  /* One row needs no number. The legend stays in the accessibility tree. */
  .ob__quals[data-single="true"] .ob__qual > legend {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The second and later rows repeat the labels, which a member does not need
     to read three times. */
  .ob__qual + .ob__qual .field > label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Years of practice is a different question, so it sits under a rule of its
     own and takes only the width it needs. */
  .ob__split {
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border);
  }

  .ob__field--short { max-inline-size: 11rem; }

  .ob__qual-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 44px;
    block-size: 48px;
    border: 1px solid transparent;
    border-radius: var(--radius-soft);
    background: none;
    color: var(--text-meta);
    cursor: pointer;
    transition:
      color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__qual-remove:hover {
    color: var(--error);
    background: var(--tint-error);
  }

  .ob__add {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-block-size: 48px;
    padding-inline: var(--space-4);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-soft);
    background: none;
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 600;
    cursor: pointer;
    align-self: end;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__add:hover {
    border-color: var(--ink);
    color: var(--ink);
  }

  /* --- the one upload -----------------------------------------------------
     A real <input type="file"> laid transparently over a drawn face, so the
     native picker, the keyboard and the accessibility tree all still work
     while the control looks like the rest of the form. No drag-and-drop
     target: the member is on a phone, photographing a certificate.
     ---------------------------------------------------------------------- */

  .ob__upload {
    position: relative;
    border-radius: var(--radius-soft);
  }

  /* Specific enough to beat the field rule above, which would otherwise give
     this a 52px min-height and leave the bottom of the face unclickable. */
  .ob__body .field .ob__upload input {
    position: absolute;
    min-block-size: 0;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    opacity: 0;
    cursor: pointer;
  }

  .ob__upload-face {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-soft);
    background: var(--surface-sunken);
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__upload:hover .ob__upload-face { border-color: var(--slate-500); }

  .ob__body .field .ob__upload input:focus-visible + .ob__upload-face {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
    background: var(--surface);
  }

  .ob__upload-mark {
    inline-size: 24px;
    block-size: 24px;
    flex: none;
    color: var(--text-meta);
  }

  .ob__upload-text {
    display: grid;
    gap: 2px;
    min-inline-size: 0;
  }

  .ob__upload-title {
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 600;
    overflow-wrap: anywhere;
  }

  .ob__upload-note {
    font-size: var(--size-ui-sm);
    line-height: 1.4;
    color: var(--text-meta);
  }

  .ob__upload-action {
    margin-inline-start: auto;
    flex: none;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  /* Carried, not merely chosen: a solid edge and a mint ground, because the
     dashed box is an invitation and this is a receipt. */
  .ob__upload[data-state="held"] .ob__upload-face {
    border-style: solid;
    border-color: var(--tint-mint);
    background: var(--tint-success);
  }

  .ob__upload[data-state="held"] .ob__upload-mark { color: var(--ink-success); }
  .ob__upload[data-state="held"] .ob__upload-note { color: var(--ink-success); }

  .ob__upload[data-state="error"] .ob__upload-face {
    border-style: solid;
    border-color: var(--error);
    background: var(--tint-error);
  }

  /* A line of running text under a field that carries one small action in
     it. Not a button row: this is a sentence with a way out inside it. */
  .ob__aside-line {
    margin-block-start: calc(var(--space-2) * -1);
    font-size: var(--size-small);
    line-height: 1.6;
    color: var(--text-meta);
  }

  .ob__row { display: grid; gap: var(--ob-gap, 1.25rem); }

  @media (min-width: 560px) {
    .ob__row--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ob__row--name { grid-template-columns: 6.5rem minmax(0, 1fr) minmax(0, 1fr); }
    .ob__row--third { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .ob__row--qual {
      grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr) 7rem auto;
      align-items: end;
      gap: var(--space-3);
    }
  }

  /* A group of fields that belong together and are all optional. Said once,
     on the group, rather than "(optional)" appended to four labels. */
  .ob__group {
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0;
    display: grid;
    gap: var(--space-5, 1.25rem);
  }

  .ob__group > legend {
    padding: 0;
    margin-block-end: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--text-meta);
  }

  /* --- choice tiles -------------------------------------------------------
     Two or three mutually exclusive answers, big enough to hit with a thumb.
     A radio in a 44px row is a control; a radio in a tile is an answer. */

  .ob__tiles {
    display: grid;
    gap: var(--space-2);
  }

  @media (min-width: 560px) {
    .ob__tiles--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  /* Five settings, one under the other: a member reads them as a list of
     answers and picks one, which is faster than scanning a grid. */
  .ob__tile {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "title mark" "note mark";
    align-items: center;
    column-gap: var(--space-4);
  }

  .ob__tile .ob__tile-title { grid-area: title; }
  .ob__tile .ob__tile-note { grid-area: note; }

  /* The selected answer carries a mark, not only a fill. */
  .ob__tile::after {
    content: "";
    grid-area: mark;
    inline-size: 20px;
    block-size: 20px;
    border: 2px solid var(--line-strong);
    border-radius: var(--radius-pill);
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint);
  }

  .ob__tile:has(input:checked)::after {
    border-color: var(--ink);
    box-shadow: inset 0 0 0 4px var(--paper), inset 0 0 0 20px var(--ink);
  }

  .ob__tile {
    position: relative;
    display: grid;
    gap: 2px;
    padding: var(--space-3) var(--space-4);
    min-block-size: 56px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-input);
    background: var(--paper);
    cursor: pointer;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__tile:hover { border-color: var(--slate-500); }

  .ob__tile input {
    position: absolute;
    opacity: 0;
    inline-size: 1px;
    block-size: 1px;
  }

  .ob__tile-title {
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
  }

  .ob__tile-note {
    font-size: var(--size-small);
    line-height: 1.45;
    color: var(--text-meta);
  }

  .ob__tile:has(input:checked) {
    border-color: var(--ink);
    background: var(--tint-page-cool);
  }

  /* The ring has to follow the hidden input, or a keyboard reader cannot
     see where they are. */
  .ob__tile:has(input:focus-visible) {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }

  /* On a short laptop window the five settings and their notes are 40px too
     tall. The note goes and the title stays: the titles are the answer, and
     the notes are an aid to it. */
  @media (min-width: 900px) and (max-height: 820px) {
    .ob__tile { min-block-size: 48px; padding-block: var(--space-2); }
    .ob__tile-note { display: none; }
  }

  /* --- chips -------------------------------------------------------------
     Multi-select, and the one place in the flow where answering is
     genuinely pleasant: tap the things you want, skip the rest. */

  .ob__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .ob__chip {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-inline: var(--space-4);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-pill);
    background: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui);
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
    transition:
      border-color var(--dur-micro) var(--ease-out-quint),
      background-color var(--dur-micro) var(--ease-out-quint),
      color var(--dur-micro) var(--ease-out-quint);
  }

  .ob__chip:hover { border-color: var(--slate-500); }

  .ob__chip input {
    position: absolute;
    opacity: 0;
    inline-size: 1px;
    block-size: 1px;
  }

  .ob__chip svg {
    inline-size: 15px;
    block-size: 15px;
    flex: none;
    opacity: 0;
    transform: scale(0.7);
    transition:
      opacity var(--dur-micro) var(--ease-out-quint),
      transform var(--dur-micro) var(--ease-out-quint);
  }

  .ob__chip:has(input:checked) {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);
  }

  .ob__chip:has(input:checked) svg {
    opacity: 1;
    transform: none;
  }

  .ob__chip:has(input:focus-visible) {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }

  /* --- the reason panel ---------------------------------------------------
     The competitor's is bold teal on a teal fill in the margin, and it is
     the loudest thing on its screen. In the margin here it is a hairline and
     two paragraphs; only when it folds under the fields on a narrow screen
     does it take a tint, because there it needs the separation. */

  .ob__why {
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border);
  }

  .ob__why h2 {
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 700;
    color: var(--accent-ink);
  }

  .ob__why p {
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    line-height: 1.6;
    color: var(--text-muted);
  }

  .ob__why p + p { margin-block-start: var(--space-3); }

  @media (max-width: 1039px) {
    .ob__why {
      padding: var(--space-4);
      border: 1px solid var(--tint-teal-strong);
      border-radius: var(--radius-input);
      background: var(--tint-teal);
    }

    .ob__why p { color: var(--ink); }
  }

  @media (min-width: 1040px) {
    .ob__why { position: sticky; inset-block-start: 7rem; }
  }

  /* --- foot of the screen ------------------------------------------------- */

  .ob__nav {
    display: grid;
    gap: var(--space-3);
    padding-block-start: var(--ob-gap, 1.25rem);
    border-block-start: 1px solid var(--border);
  }

  .ob__nav .btn { inline-size: 100%; }

  @media (min-width: 560px) {
    /*
     * Explicit columns, not source order. Left to itself, a screen with no
     * skip put Continue in the middle column and a screen with one put it in
     * the third, so the primary action moved sideways between steps â€” the
     * one edge in this flow a reader tracks without noticing they are doing
     * it.
     */
    .ob__nav {
      grid-template-columns: auto 1fr auto;
      align-items: center;
      gap: var(--space-4);
    }

    .ob__nav .ob__back { grid-column: 1; }
    .ob__nav .ob__skip { grid-column: 2; justify-self: center; }
    .ob__nav .btn { grid-column: 3; inline-size: auto; }
  }

  .ob__back {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--text-muted);
    text-decoration: none;
    background: none;
    border: 0;
    cursor: pointer;
    order: 2;
  }

  .ob__back:hover { color: var(--ink); }
  .ob__back svg { transition: transform var(--dur-micro) var(--ease-out-quint); }
  .ob__back:hover svg { transform: translateX(-3px); }

  .ob__nav .btn { order: 1; }

  /* Skipping is offered in the middle of the row, at full size, in words
     that say what is lost: nothing. A skip that has to be hunted for is not
     a skip. */
  .ob__skip {
    display: inline-flex;
    align-items: center;
    justify-self: center;
    min-block-size: 44px;
    font-size: var(--size-small);
    color: var(--text-muted);
    background: none;
    border: 0;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
    order: 3;
  }

  .ob__skip:hover { color: var(--ink); }

  @media (min-width: 560px) {
    .ob__back { order: 0; }
    .ob__skip { order: 0; }
    .ob__nav .btn { order: 0; }
  }

  /* Moved into the action row by the script, above the buttons and spanning
     the row, so a message about this screen is read before the control that
     triggered it. */
  .ob__nav .ob__status {
    grid-column: 1 / -1;
    margin-block-end: var(--space-2);
  }

  .ob__status {
    font-size: var(--size-small);
    line-height: 1.4;
    color: var(--text-muted);
    min-block-size: 1.25rem;
  }

  .ob__status[data-tone="error"] { color: var(--error); }
  .ob__status[data-tone="success"] { color: var(--ink-success); }
  .ob__status[data-tone="pending"] { color: var(--amber-deep); }

  /* ======================================================================
     Sign up
     ====================================================================== */

  .ob__signup {
    max-inline-size: 30rem;
    margin-inline: auto;
  }

  .ob__signup .ob__head { text-align: center; }
  .ob__signup .ob__lead { margin-inline: auto; }

  .ob__legal {
    font-size: var(--size-ui-sm);
    line-height: 1.55;
    color: var(--text-meta);
  }

  .ob__signup .checkbox { font-size: var(--size-small); }

  /* The code field. Six characters, spaced, in tabular figures so the gaps
     do not dance as the digits arrive. */
  .ob__code {
    font-family: var(--font-display);
    font-size: 1.75rem !important;
    font-weight: 700;
    letter-spacing: 0.4em;
    text-align: center;
    font-variant-numeric: tabular-nums;
    min-block-size: 64px !important;
  }

  .ob__sent {
    font-size: var(--size-small);
    color: var(--text-muted);
    text-align: center;
  }

  .ob__sent b { color: var(--ink); }

  .ob__resend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-2) var(--space-4);
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  .ob__linkbtn {
    min-block-size: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--link);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  .ob__linkbtn:hover { color: var(--accent-ink); }

  /* ======================================================================
     The close
     ----------------------------------------------------------------------
     The one committed moment in the flow. A change of ground is how this
     system says a chapter has ended, and this is the only screen that is
     not a task.
     ====================================================================== */

  /* A body-level band, so it takes the container inset itself rather than
     borrowing a parent's. The gutter is the floor on a phone. */
  .ob__done {
    padding-block: var(--space-16);
    padding-inline:
      max(var(--gutter), calc(50vw - 31.5rem), env(safe-area-inset-left))
      max(var(--gutter), calc(50vw - 31.5rem), env(safe-area-inset-right));
    background: var(--plane-navy);
    color: var(--text-on-dark);
    min-block-size: 100dvh;
    display: grid;
    align-content: center;
  }

  .ob__done-inner { max-inline-size: 44rem; }

  /* The one screen that is allowed to be a moment takes the system's own
     display step rather than a bespoke size. */
  .ob__done h1 {
    color: var(--paper);
    font-size: var(--size-h2);
    line-height: var(--leading-h2);
    letter-spacing: var(--tracking-h2);
    text-wrap: balance;
  }

  .ob__done-lead {
    margin-block-start: var(--space-4);
    max-inline-size: 46ch;
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-on-dark-strong);
  }

  /* What happens next, in order, with a hairline per row rather than three
     cards saying the same shape three times. */
  .ob__next {
    list-style: none;
    margin-block-start: var(--space-8);
    padding: 0;
    max-inline-size: 40rem;
    border-block-start: 1px solid rgb(255 255 255 / 0.16);
  }

  .ob__next li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-4);
    align-items: start;
    padding-block: var(--space-4);
    border-block-end: 1px solid rgb(255 255 255 / 0.16);
  }

  .ob__next svg {
    inline-size: 20px;
    block-size: 20px;
    margin-block-start: 3px;
    /* A ring or a mark is not a letterform, so it keeps the accent even on
       navy. The words beside it stay white. */
    color: var(--accent-bright);
  }

  .ob__next-title {
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
    color: var(--paper);
  }

  .ob__next-note {
    margin-block-start: 2px;
    font-size: var(--size-small);
    line-height: 1.55;
    color: var(--text-on-dark);
  }

  .ob__done-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    margin-block-start: var(--space-8);
  }

  .ob__done-actions .ob__back { color: var(--text-on-dark-strong); }
  .ob__done-actions .ob__back:hover { color: var(--paper); }

  /* ======================================================================
     Motion
     ----------------------------------------------------------------------
     One authored moment, and it is the step change: the screen you asked
     for arrives from the direction you asked for it. Gated on html.js,
     which js/reveal.js withholds under prefers-reduced-motion, so a reader
     who asked for stillness gets the panel already in place.
     ====================================================================== */

  @keyframes ob-forward {
    from { opacity: 0; transform: translate3d(18px, 0, 0); }
    to { opacity: 1; transform: none; }
  }

  @keyframes ob-back {
    from { opacity: 0; transform: translate3d(-18px, 0, 0); }
    to { opacity: 1; transform: none; }
  }

  html.js .ob__panel[data-enter="forward"] {
    animation: ob-forward 320ms var(--ease-out-quint) both;
  }

  html.js .ob__panel[data-enter="back"] {
    animation: ob-back 320ms var(--ease-out-quint) both;
  }
}

}
/* Last, and deliberately. The about page borrows the India dot map from
   editorial.css and has to put back the three lit dots that make sense on
   /doctors/ and read as three cities here. Those rules and these are the same
   specificity, so the only thing separating them is source order. */
/* ---- about.css ---- */
@layer components {
/* ==========================================================================
   About, questions and contact
   --------------------------------------------------------------------------
   Built to the supplied contact mockup: a full-bleed tinted hero carrying a
   ruled grid and two soft glows, a white form card floating over its right
   edge, three micro-columns beneath, a map paired with the registered office,
   a split FAQ, and a photographic closing band.

   Rendered in our own palette. The mockup runs on #0052ff; the site runs on
   brand navy with teal for links, and swapping one for the other would give
   /about/ a primary colour no other page has. The structure is the reference's;
   the colour is ours.

   Scoped to .ab* throughout, so nothing here reaches another page.
   ========================================================================== */

@layer components {

  /* ======================================================================
     1. Hero
     ----------------------------------------------------------------------
     Full-bleed rather than a rounded panel. The mockup's tint runs to both
     edges, and that is what makes the page open as a field rather than as a
     document â€” which is the right feel for the one page that is mostly a
     way of getting hold of someone.
     ====================================================================== */

  .abhero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background: linear-gradient(180deg, rgb(7 28 56 / 0.07) 0%, rgb(7 28 56 / 0.03) 100%);
  }

  /* Two glows, warm-cool, off opposite corners. They stop the flat tint from
     reading as a grey box without introducing a second hue. */
  .abhero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
      radial-gradient(900px 460px at 78% -8%, rgb(10 109 119 / 0.14), transparent 70%),
      radial-gradient(700px 400px at 6% 110%, rgb(7 28 56 / 0.08), transparent 70%);
  }

  /* A soft tonal field gives the hero depth without a two-axis grid, whose
     repeated scaffold competes with the page's documentary character. */
  .abhero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: 0.5;
    background: radial-gradient(ellipse at 20% 0%, rgb(7 28 56 / 0.08), transparent 72%);
    mask-image: radial-gradient(760px 460px at 20% 0%, #000, transparent 75%);
  }

  .abhero__in {
    display: grid;
    gap: var(--space-12);
    padding-block: var(--space-16);
    align-items: start;
  }

  @media (min-width: 1040px) {
    .abhero__in {
      grid-template-columns: minmax(0, 1fr) 27rem;
      gap: var(--space-16);
      padding-block: var(--space-24) var(--space-16);
    }
  }

  .abhero__title {
    font-size: var(--size-hero);
    line-height: var(--leading-hero);
    letter-spacing: var(--tracking-hero);
  }

  .abhero__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-muted);
    max-width: 34ch;
  }

  /* --- the two direct lines ---------------------------------------------
     Rows rather than buttons. Email and phone are what most visitors came
     for; styling them as calls to action would make the page look like it
     wants something in return. */

  .abrows {
    margin-block-start: var(--space-8);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: flex-start;
  }

  .abrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-block-size: 44px;
    padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
    border-radius: var(--radius-pill);
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 500;
    color: var(--text);
    text-decoration: none;
    transition:
      background var(--dur-micro) var(--ease-out-quint),
      box-shadow var(--dur-micro) var(--ease-out-quint);
  }

  .abrow:hover {
    background: rgb(255 255 255 / 0.78);
    box-shadow: var(--shadow-rest);
  }

  .abrow__chip {
    flex: none;
    inline-size: 32px;
    block-size: 32px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--paper);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px rgb(7 28 56 / 0.07);
  }

  /* Tabular figures so the phone number reads as a number rather than as a
     word that happens to be made of digits. */
  .abrow--num {
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
  }

  /* Data and privacy has no column of its own: the mockup runs three, and a
     fourth would unbalance the row. It lives here instead, which is also
     where someone looking for it would think to look. */
  .abgrievance {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    margin-block-start: var(--space-4);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
    box-shadow: inset 0 -1px 0 rgb(7 28 56 / 0.35);
    transition: box-shadow var(--dur-micro) var(--ease-out-quint), color var(--dur-micro);
  }

  .abgrievance:hover {
    color: var(--accent);
    box-shadow: inset 0 -1px 0 var(--accent);
  }

  /* ======================================================================
     2. The floating card
     ====================================================================== */

  .getcard {
    background: var(--paper);
    border-radius: 28px;
    box-shadow:
      0 2px 6px rgb(7 28 56 / 0.06),
      0 30px 60px -18px rgb(7 28 56 / 0.22);
    padding: var(--space-6);
  }

  @media (min-width: 768px) {
    .getcard {
      padding: var(--space-8);
    }
  }

  .getcard__title {
    font-family: var(--font-display);
    font-size: 2rem;
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--text);
  }

  .getcard__sub {
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    color: var(--text-meta);
  }

  .getcard form {
    margin-block-start: var(--space-6);
  }

  /* Rhythm on the form's own children only. Applied to every .field it also
     reached the two inside .abpair, which pushed Last name half a row below
     First name and made the pair look like a mistake. */
  .getcard form > * + * {
    margin-block-start: var(--space-3);
  }

  .abpair {
    display: grid;
    gap: var(--space-3);
  }

  @media (min-width: 420px) {
    .abpair {
      grid-template-columns: 1fr 1fr;
    }
  }

  /* The mockup labels its fields with placeholders only. That is the one
     thing in it worth changing rather than copying: the label vanishes the
     moment anyone types, so the person checking their own answers before
     submitting has nothing left to check them against. Small persistent
     labels cost about 16px a field and remove the problem. */
  .getcard .field > label:not(.checkbox) {
    font-size: var(--size-eyebrow);
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .getcard input,
  .getcard textarea {
    border-radius: var(--radius-input);
    padding: 0.75rem 0.9rem;
  }

  .getcard textarea {
    min-block-size: 6rem;
  }

  .getcard form > .btn {
    inline-size: 100%;
    margin-block-start: var(--space-6);
  }

  .getcard__legal {
    margin-block-start: var(--space-4);
    font-size: 0.75rem;
    line-height: 1.6;
    text-align: center;
    color: var(--text-meta);
  }

  .getcard__legal a {
    color: var(--text);
    font-weight: 600;
  }

  /* ======================================================================
     3. Micro-columns
     ----------------------------------------------------------------------
     The contact section, at the cost of one row rather than one section.
     Two tiers: who you are, then which desk that reaches.
     ====================================================================== */

  .abtracks {
    margin-block-start: var(--space-16);
    padding-block-start: var(--space-8);
    border-block-start: 1px solid rgb(7 28 56 / 0.09);
    display: grid;
    gap: var(--space-8);
  }

  /* Two columns from 620px up and no wider breakpoint: this carried three
     columns until the brand-facing one was removed, and a third column
     definition with only two items would have left an empty slot on wide
     screens rather than doing nothing. */
  @media (min-width: 620px) {
    .abtracks {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--space-8) var(--space-12);
    }
  }

  /* The two bodies are different lengths, so the two links land on different
     baselines unless they are pushed. A ragged row of links reads as two
     unrelated blocks rather than one row of routes. */
  .abtracks > * {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  .abtrack__body {
    flex: 1;
  }

  .abtrack__who {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-micro);
    font-weight: 700;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--accent);
  }

  .abtrack__title {
    margin-block-start: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
  }

  .abtrack__body {
    margin-block-start: var(--space-2);
    font-size: var(--size-small);
    line-height: 1.6;
    color: var(--text-muted);
  }

  .abtrack__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
  }

  .abtrack__link:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  /* ======================================================================
     4. Location
     ====================================================================== */

  .ablocation {
    display: grid;
    gap: var(--space-12);
    align-items: center;
  }

  @media (min-width: 940px) {
    .ablocation {
      grid-template-columns: 1.05fr 1fr;
      /* 64px, not 96. At the wider gap the map column lost enough width to
         turn the plate portrait, and a map that is taller than it is wide
         reads as a photograph of a map rather than as a place. */
      gap: var(--space-16);
    }
  }

  /* D2. One column since the map plate and the office block were removed.
     The copy keeps a reading measure rather than running to the full width of
     the container, which at 1180px would be about 120 characters. */
  .ablocation--single {
    grid-template-columns: minmax(0, 1fr);
    justify-items: start;
  }

  @media (min-width: 940px) {
    .ablocation--single { grid-template-columns: minmax(0, 62ch); }
  }

  .abmap {
    position: relative;
    border-radius: var(--radius-media);
    overflow: hidden;
    box-shadow: inset 0 0 0 1px rgb(7 28 56 / 0.07), var(--shadow-rest);
  }

  .abmap img {
    display: block;
    inline-size: 100%;
    block-size: 22rem;
    object-fit: cover;
    filter: saturate(0.62) contrast(1.02);
  }

  @media (min-width: 940px) {
    .abmap img {
      block-size: 30rem;
    }
  }

  /* A white vignette, so the plate reads as a surface the cards sit on
     rather than as a photograph that has been cropped. */
  .abmap::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(120% 90% at 50% 45%, transparent 55%, rgb(255 255 255 / 0.55) 100%);
  }

  .abpin {
    position: absolute;
    inset-block-start: var(--space-6);
    inset-inline-start: var(--space-6);
    z-index: 2;
    inline-size: min(16.5rem, calc(100% - var(--space-12)));
    padding: var(--space-4);
    border-radius: var(--radius-card);
    background: rgb(255 255 255 / 0.92);
    backdrop-filter: blur(10px);
    box-shadow: var(--shadow-raised);
    font-size: var(--size-ui-sm);
  }

  .abpin__head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
  }

  .abpin__mark {
    flex: none;
    inline-size: 30px;
    block-size: 30px;
    border-radius: var(--radius-chip);
    display: grid;
    place-items: center;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: var(--size-ui-lg);
    font-weight: 700;
  }

  .abpin__name {
    display: block;
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
  }

  .abpin__tag {
    display: block;
    font-size: 0.78125rem;
    color: var(--text-meta);
  }

  .abpin hr {
    border: 0;
    border-block-start: 1px solid var(--line);
    margin-block: var(--space-4);
  }

  .abpin__addr {
    color: var(--text-muted);
    line-height: 1.55;
    font-style: normal;
  }

  .abpin__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    font-family: var(--font-display);
    font-size: var(--size-ui-sm);
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
  }

  .abpin__link:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .abmarker {
    position: absolute;
    inset-block-start: 60%;
    inset-inline-start: 46%;
    z-index: 2;
    inline-size: 18px;
    block-size: 18px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 4px var(--paper), 0 4px 12px rgb(10 109 119 / 0.45);
  }

  .abmarker::after {
    content: "";
    position: absolute;
    inset: -10px;
    border-radius: 50%;
    border: 1.5px solid rgb(10 109 119 / 0.5);
    animation: abping 2.6s var(--ease-out-quint) infinite;
  }

  @keyframes abping {
    from { transform: scale(0.6); opacity: 0.9; }
    to   { transform: scale(1.9); opacity: 0; }
  }

  .abcopy__title {
    font-size: var(--size-display);
    line-height: var(--leading-display);
    letter-spacing: var(--tracking-display);
  }

  .abcopy__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
    color: var(--text-muted);
    max-width: 42ch;
  }

  .aboffice {
    margin-block-start: var(--space-12);
    padding-block-start: var(--space-8);
    border-block-start: 1px solid var(--line);
  }

  .aboffice__label {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-meta);
  }

  .aboffice address {
    margin-block-start: var(--space-4);
    font-style: normal;
    font-size: var(--size-ui-lg);
    line-height: 1.8;
    color: var(--text-muted);
  }

  .aboffice address b {
    color: var(--text);
    font-weight: 600;
  }

  .abtags {
    margin-block-start: var(--space-6);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
  }

  .abtag {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-inline: var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--base);
    font-family: var(--font-display);
    font-size: var(--size-small);
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
    transition: background var(--dur-micro) var(--ease-out-quint), color var(--dur-micro);
  }

  .abtag:hover {
    background: rgb(10 109 119 / 0.1);
    color: var(--accent-ink);
  }

  /* ======================================================================
     5. Split FAQ
     ====================================================================== */

  .abfaqband {
    position: relative;
    background: var(--base);
    background-image: radial-gradient(600px 300px at 90% 0%, rgb(10 109 119 / 0.07), transparent 70%);
  }

  .abfaq {
    display: grid;
    gap: var(--space-12);
  }

  @media (min-width: 940px) {
    .abfaq {
      grid-template-columns: 24.5rem minmax(0, 1fr);
      gap: var(--space-24);
      align-items: start;
    }

    /* The heading stays with the questions rather than scrolling away at the
       first answer. */
    .abfaq__aside {
      position: sticky;
      inset-block-start: var(--space-24);
    }
  }

  .abfaq__title {
    font-size: clamp(2rem, 3.2vw, 2.75rem);
    line-height: 1.08;
    letter-spacing: -0.025em;
  }

  .abfaq__lead {
    margin-block-start: var(--space-6);
    font-size: var(--size-ui);
    line-height: var(--leading-lead);
    color: var(--text-muted);
  }

  .abasidecard {
    margin-block-start: var(--space-8);
    padding: var(--space-5, 1.25rem);
    border-radius: var(--radius-card);
    background: var(--paper);
    box-shadow: var(--shadow-rest);
    font-size: var(--size-small);
  }

  .abasidecard p {
    color: var(--text-muted);
  }

  .abasidecard a {
    display: inline-flex;
    align-items: center;
    min-block-size: 44px;
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    box-shadow: inset 0 -1px 0 rgb(10 109 119 / 0.28);
  }

  .abasidecard a:hover {
    box-shadow: inset 0 -1px 0 var(--accent);
  }

  /* --- the accordion, restyled ------------------------------------------
     The component is unchanged: same markup, same script, same behaviour
     with JavaScript off. What changes is that an open row lifts out of the
     ruled list onto a white card, which is how the mockup shows the reader
     where they are in a list of eight. */

  .abfaq .accordion {
    border-block-start: 1px solid var(--line-strong);
  }

  .abfaq .accordion__item {
    border-block-end: 1px solid var(--line-strong);
    border-radius: var(--radius-card);
    transition: background var(--dur-state) var(--ease-out-quint);
  }

  .abfaq .accordion__item:hover {
    background: rgb(255 255 255 / 0.6);
  }

  .abfaq .accordion__item:has(.accordion__trigger[aria-expanded="true"]) {
    background: var(--paper);
    box-shadow: var(--shadow-rest);
    border-color: transparent;
    margin-block: var(--space-2);
  }

  .abfaq .accordion__trigger {
    gap: var(--space-5, 1.25rem);
    align-items: flex-start;
    padding: var(--space-6) var(--space-6) var(--space-6) var(--space-5, 1.25rem);
    font-size: 1.125rem;
    line-height: 1.35;
    letter-spacing: -0.012em;
  }

  .abfaq .accordion__trigger:hover .abq__text {
    color: var(--accent);
  }

  /* Numbering is the mockup's, and it earns its place: it turns "one of
     several" into "the fourth of eight", which is the difference between a
     list a reader scans and one they can navigate. */
  .abq__num {
    flex: none;
    inline-size: 1.5rem;
    padding-block-start: 0.25rem;
    font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono", "Segoe UI Mono", monospace;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--text-meta);
  }

  .abq__text {
    flex: 1;
    transition: color var(--dur-micro) var(--ease-out-quint);
  }

  .abfaq .accordion__icon {
    inline-size: 28px;
    block-size: 28px;
    padding: 0.4375rem;
    box-sizing: border-box;
    border-radius: 50%;
    background: var(--line);
    color: var(--text);
    transition:
      background var(--dur-state) var(--ease-out-quint),
      color var(--dur-state) var(--ease-out-quint),
      transform var(--dur-state) var(--ease-out-quint);
  }

  .abfaq .accordion__trigger[aria-expanded="true"] .accordion__icon {
    background: var(--ink);
    color: var(--paper);
  }

  .abfaq .accordion__inner > div {
    padding: 0 var(--space-16) var(--space-6) 4rem;
    font-size: var(--size-ui);
    line-height: 1.7;
    max-width: 62ch;
  }

  @media (max-width: 640px) {
    .abfaq .accordion__trigger {
      gap: var(--space-3);
      padding: var(--space-5, 1.25rem) var(--space-4);
      font-size: 1.03125rem;
    }

    .abq__num { display: none; }

    .abfaq .accordion__inner > div {
      padding: 0 var(--space-4) var(--space-5, 1.25rem);
      font-size: var(--size-ui);
    }
  }

  /* ======================================================================
     6. Closing band
     ====================================================================== */

  .abcta {
    position: relative;
    overflow: hidden;
    background: var(--ink-deep);
    color: var(--text);
  }

  .abcta__img {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: 64% 45%;
  }

  /* Horizontal, and opaque where the words are. The photograph's own left
     third is already near-black, so the veil is doing very little work
     there and the two faces on the right stay legible as a photograph. */
  .abcta__veil {
    position: absolute;
    inset: 0;
    background: linear-gradient(
      90deg,
      #04121f 0%,
      rgb(4 18 31 / 0.94) 34%,
      rgb(4 18 31 / 0.55) 62%,
      rgb(4 18 31 / 0.15) 100%
    );
  }

  .abcta__in {
    position: relative;
    z-index: 2;
    padding-block: var(--space-24);
  }

  @media (min-width: 940px) {
    .abcta__in {
      padding-block: var(--space-32);
    }
  }

  .abcta__title {
    font-size: clamp(2rem, 4vw, 3.125rem);
    line-height: 1.06;
    max-width: 19ch;
  }

}

/* Unlayered, like the rest of the reduced-motion block in main.css: a user
   preference is not a style tier, and inside a layer the components layer
   would win. */
@media (prefers-reduced-motion: reduce) {
  .abmarker::after {
    animation: none;
  }
}

}
/* ---- notfound.css ---- */
@layer components {
/* ==========================================================================
   404
   --------------------------------------------------------------------------
   Built from the supplied empty-state component. What was kept is the idea:
   an oversized numeral masked to fade into the page, a short line under it,
   and two actions side by side. What changed is everything structural, and
   each change is a defect in the original rather than a preference:

     - `white-space: nowrap` on the description overflows any phone. The copy
       wraps instead, and the hard <br> is gone with it: a line break belongs
       to the measure, not to the sentence.
     - `min-height: 100vh` ignores the header and the footer that sit around
       this on a real site, and on mobile 100vh is the address-bar lie. This
       uses svh and subtracts the chrome it actually has.
     - `border-style: dashed` with no width and no colour draws nothing.
     - Its colours were `--primary`, `--foreground`, `--input` with hard-coded
       fallbacks. None of those tokens exist here, so every one would have
       shipped as a literal hex â€” the exact drift the audit just cleaned out.
       This uses the site's own tokens and its own .btn.
   ========================================================================== */

@layer components {

  .nf {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-6);
    text-align: center;
    text-wrap: balance;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding-block: var(--space-24) var(--space-16);
    /* The header is sticky and the footer follows this section, so a full
       viewport here would push the actions below the fold on a laptop. */
    min-block-size: min(70svh, 34rem);
  }

  /* One soft wash behind the numeral so it sits on something rather than
     floating on white. Same gesture as the hero plane's glow, a fraction of
     the strength. */
  .nf::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset-block-start: -20%;
    inset-inline-start: 50%;
    inline-size: min(46rem, 120%);
    aspect-ratio: 1;
    translate: -50% 0;
    background: radial-gradient(closest-side, var(--tint-teal) 0%, transparent 70%);
    opacity: 0.65;
    pointer-events: none;
  }

  /* The one borrowed effect worth borrowing: the numeral is masked so it
     dissolves downward instead of ending. It reads as something receding
     rather than as very large type. */
  .nf__code {
    font-family: var(--font-display);
    font-size: clamp(6rem, 22vw, 13rem);
    font-weight: 800;
    line-height: 0.86;
    letter-spacing: -0.06em;
    color: var(--ink);
    -webkit-mask-image: linear-gradient(to bottom, #000 42%, transparent 92%);
    mask-image: linear-gradient(to bottom, #000 42%, transparent 92%);
    user-select: none;
  }

  /* The title tucks under the numeral's faded tail only where there is room
     for it. At 375px the numeral is proportionally much closer to the title
     and the same negative margin put the heading across the solid strokes of
     the 4, which reads as a collision rather than as depth. */
  @media (min-width: 768px) {
    .nf__code {
      margin-block-end: calc(var(--space-6) * -1);
    }
  }

  .nf__title {
    font-size: var(--size-h3, 1.75rem);
    letter-spacing: -0.025em;
  }

  .nf__lead {
    margin-block-start: var(--space-3);
    max-width: 34rem;
    color: var(--text-muted);
    font-size: var(--size-lead);
    line-height: var(--leading-lead);
  }

  .nf__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
    margin-block-start: var(--space-6);
  }

  .nf__actions .icon {
    inline-size: 17px;
    block-size: 17px;
  }

  /* The routes below the fold. Quieter than the actions, because somebody who
     knows where they were going has already used the two buttons above. */
  .nf__routes {
    display: grid;
    gap: var(--space-3);
    margin-block-start: var(--space-12);
    padding-block-start: var(--space-8);
    border-block-start: 1px solid var(--line);
    inline-size: min(100%, 34rem);
    text-align: start;
  }

  @media (min-width: 640px) {
    .nf__routes {
      grid-template-columns: 1fr 1fr;
      gap: var(--space-3) var(--space-8);
    }
  }

  .nf__route {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    font-size: var(--size-ui-sm);
    color: var(--text-muted);
  }

  .nf__route a {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
  }

  .nf__note {
    margin-block-start: var(--space-8);
    font-size: var(--size-ui-sm);
    color: var(--text-meta);
  }
}

}
/* ---- consent-banner.css ---- */
@layer components {
/* ==========================================================================
   Consent banner
   --------------------------------------------------------------------------
   Exists only in a <template>, per js/analytics.js â€” with JavaScript
   disabled there is no way to load anything remote, so there is nothing to
   ask consent for and no banner ever appears. That mirrors the site's
   existing rule for [data-reveal]: content is never hidden by something that
   might not run, and here the inverse holds too â€” nothing is shown to ask
   permission for a capability that does not exist without the script that
   draws it.
   ========================================================================== */

@layer components {
  .consent-banner {
    position: fixed;
    inset-inline: var(--space-4);
    inset-block-end: var(--space-4);
    z-index: 150;
    margin-inline: auto;
    max-width: 640px;

    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-raised);

    /* Reuses the site's dark-plane alias flip rather than defining its own
       palette: this is the same navy every other dark surface uses. */
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);

    animation: consent-banner-in var(--dur-enter) var(--ease-out-quint);
  }

  .consent-banner[data-leaving="true"] {
    animation: consent-banner-out var(--dur-state) var(--ease-out-quint) forwards;
  }

  .consent-banner__text {
    flex: 1 1 320px;
    margin: 0;
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--text-muted);
  }

  .consent-banner__text a {
    color: var(--link);
  }

  .consent-banner__actions {
    display: flex;
    flex: 0 0 auto;
    gap: var(--space-3);
  }

  @media (prefers-reduced-motion: reduce) {
    .consent-banner {
      animation: none;
    }

    .consent-banner[data-leaving="true"] {
      animation: none;
      opacity: 0;
    }
  }

  @keyframes consent-banner-in {
    from {
      opacity: 0;
      transform: translateY(12px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  @keyframes consent-banner-out {
    to {
      opacity: 0;
      transform: translateY(12px);
    }
  }
}

}

@layer utilities {
  .text-center { text-align: center; }
  .text-muted { color: var(--text-muted); }
  .text-meta { color: var(--text-meta); }
  .measure { max-width: var(--measure); }
  .no-wrap { white-space: nowrap; }

  .mt-4 { margin-block-start: var(--space-4); }
  .mt-6 { margin-block-start: var(--space-6); }
  .mt-8 { margin-block-start: var(--space-8); }
  .mt-12 { margin-block-start: var(--space-12); }
  .mt-16 { margin-block-start: var(--space-16); }

  .full-bleed {
    margin-inline: calc(50% - 50vw);
    padding-inline: calc(50vw - 50%);
  }
}

/* ==========================================================================
   Reduced motion, deliberately unlayered.
   --------------------------------------------------------------------------
   Unlayered declarations beat every layer. An earlier version put this inside
   @layer base, where the components layer silently overrode it: the whole
   accessibility guarantee was inert and nothing said so.

   A user preference is not a style tier. It sits outside the cascade the rest
   of the system negotiates in.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.2s !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.2s !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  html {
    scroll-behavior: auto !important;
  }

  /* Movement dies, visibility survives. */
  [data-reveal],
  [data-choreo] {
    opacity: 1 !important;
    transform: none !important;
    animation-name: none !important;
  }

  /* The specialty marquee stops and becomes a plain horizontal scroller.
     The blanket rule above would otherwise run it once over 0.2s and leave it
     parked at -50%, i.e. showing the duplicate set and nothing else. The
     duplicate is removed rather than frozen, so what remains is the real list,
     exactly once, scrollable by hand. */
  .specmarquee {
    overflow-x: auto;
    mask-image: none;
  }

  .specmarquee__track {
    animation: none !important;
    transform: none !important;
  }

  .specmarquee__set[aria-hidden="true"] {
    display: none;
  }

  /* The fan stops where it stands. The blanket rule above would run both
     nested rotations once over 0.2s and leave the ring parked at 360deg,
     which is where it started, so freezing is the same picture. */
  .bigfan__tiles,
  .bigfan__tile {
    animation: none !important;
  }

  @view-transition {
    navigation: none;
  }
}
