@charset "utf-8";
/* ==========================================================================
   PROJECT GENESIS — Taban Fertility Center
   Design Token System + Core Stylesheet
   One stylesheet serves RTL (fa, ar) and LTR (en) via CSS logical properties.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. FOUNDATION TOKENS  (the DNA — never overridden per component)
   -------------------------------------------------------------------------- */
:root {
  /* -- Color foundation -------------------------------------------------
     The site sits in the dark. The brand violet reads far better against
     it than against ivory, and every stop below has been checked to at
     least 4.5:1 on the lightest surface it can land on. -------------- */
  --c-void: #0e0a1c;
  --c-deep: #14102a;
  --c-raised: #1e1838;
  --c-warm: #2a1730;

  --c-genesis-violet: #8b6eff;
  --c-deep-lavender: #8b6eff;
  --c-soft-orchid: #b79dff;
  --c-glass-violet: #cbbcff;
  --c-glass-lilac: #d3c6ff;
  --c-rose-quartz: #f2a8c8;
  --c-blush-petal: #f7c4db;
  --c-champagne: #f6dfb4;
  --c-silver-fog: #6d6690;
  --c-pearl: #f4f0ff;
  /* Raised surfaces. The approved build mixed `--c-pearl` (near-white) into
     transparent at 38–74%, which on a dark page is a near-white panel — and
     every one of them carried `--ink` (also near-white) as its text colour.
     The treatment page's day-by-day panels, its questions and its clinical
     asides were all white-on-white. These are the same idea done in the dark:
     a lift you can see, text you can read. */
  --glass-1: rgb(255 255 255 / 0.030);
  --glass-2: rgb(255 255 255 / 0.045);
  --glass-3: rgb(255 255 255 / 0.065);
  --c-ivory: #fffdf9;
  --c-graphite: #f4f0ff;
  --c-moon: #c3b8e4;
  --c-mist: #a79dcb;

  /* -- Semantic surface -------------------------------------------------- */
  --bg: var(--c-deep);
  --surface: rgb(255 255 255 / 0.04);
  --ink: #f4f0ff;
  --ink-2: #c3b8e4;
  --ink-3: #a79dcb;
  --accent: var(--c-soft-orchid);
  --accent-soft: var(--c-glass-violet);
  --hope: var(--c-rose-quartz);
  --line: rgb(255 255 255 / 0.12);
  --line-soft: rgb(255 255 255 / 0.07);
  /* > **Decision — «a wash of the opposite ink» is a token, because it is not
     >   white.**
     > *Why:* hover states, inactive dots and the dashed rings of the closing
     > aperture were all written as `rgb(255 255 255 / 0.09)` — correct on a
     > near-black page and *nothing at all* on ivory. They did not look wrong
     > in daylight; they were simply absent, which is worse, because a hover
     > state that does not appear reads as a link that is not a link.
     >
     > Three steps rather than one: a hover wants less than a hairline ring,
     > which wants less than a filled dot. */
  --tint-1: rgb(255 255 255 / 0.09);
  --tint-2: rgb(255 255 255 / 0.14);
  --tint-3: rgb(255 255 255 / 0.24);
  --select-bg: rgb(167 139 250 / 0.34);
  --select-ink: #fff;
  /* The edge of a form control — see `.field input`. 3.3:1 against the
     control's own fill and 3.7:1 against the page behind it. */
  --field-line: rgb(255 255 255 / 0.36);

  /* -- Atmosphere (interpolated per chapter by the engine) ---------------- */
  --atmos-1: #14102a;
  --atmos-2: #0e0a1c;
  --atmos-glow: 139 110 255;
  --atmos-warm: 242 168 200;

  /* -- Light / glow tokens ------------------------------------------------
     Raised from the light-theme values: on a dark ground a glow has to work
     considerably harder before it is felt at all. */
  --glow-primary: 0.10;
  --glow-secondary: 0.20;
  --glow-focus: 0.28;
  --glow-hero: 0.36;
  --glow-living: 0.46;

  /* -- Blur tokens ------------------------------------------------------- */
  --blur-xs: 4px;
  --blur-s: 8px;
  --blur-m: 16px;
  --blur-l: 32px;
  --blur-xl: 48px;
  --blur-atmosphere: 96px;
  --blur-dream: 160px;

  /* -- Shadow tokens (always colour-aware, never black) ------------------- */
  --sh-micro: 0 2px 12px rgb(4 2 12 / 0.40);
  --sh-soft: 0 12px 40px rgb(4 2 12 / 0.50);
  --sh-living: 0 20px 70px rgb(4 2 12 / 0.55), 0 0 70px rgb(150 120 255 / 0.12);
  /* > **Decision — a card at rest casts a shadow in daylight and none at
     >   night.**
     > *Why:* a raised panel on a dark page is *lighter than its ground*, so
     > the fill alone says «this is in front». Invert the page and that stops
     > working: white on ivory is a four-percent difference, and the only
     > thing left holding the card together is a hairline at seven percent
     > ink. The front page's fifth chapter read as six rectangles drawn on
     > paper rather than six cards lying on it. Above white, depth is cast,
     > not filled — which is why `.card` already carries `--sh-micro` at rest
     > and why the two registers that lift only on hover need to start from
     > somewhere in the day. */
  --lift-rest: none;
  --sh-hero: 0 40px 120px rgb(4 2 12 / 0.60), 0 0 110px rgb(150 120 255 / 0.14);
  --sh-float: 0 80px 200px rgb(4 2 12 / 0.66);

  /* -- Radius tokens ----------------------------------------------------- */
  --r-tiny: 8px;
  --r-soft: 16px;
  --r-med: 24px;
  --r-lg: 40px;
  --r-organic: 80px;
  --r-inf: 999px;

  /* -- Spacing scale (nothing else exists) -------------------------------- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 40px;
  --s-8: 48px;
  --s-9: 64px;
  --s-10: 80px;
  --s-11: 96px;
  --s-12: 120px;
  --s-13: 160px;
  --s-14: 240px;
  --s-15: 320px;

  /* -- Grid -------------------------------------------------------------- */
  --container: 1440px;
  --content: 720px;
  --reading: 680px;
  --gutter: var(--s-6);
  --edge: clamp(20px, 5vw, 80px);

  /* -- Type scale (fluid, anchored to the documented scale) --------------- */
  --t-display-xxl: clamp(2.9rem, 6.6vw, 6rem);
  --t-display-xl: clamp(2.6rem, 5.6vw, 5rem);
  --t-display-l: clamp(2.2rem, 4.6vw, 4.25rem);
  --t-display-m: clamp(1.9rem, 3.6vw, 3.4rem);
  --t-headline-xl: clamp(1.75rem, 3.6vw, 3.5rem);
  --t-headline-l: clamp(1.6rem, 3vw, 3rem);
  --t-headline-m: clamp(1.4rem, 2.4vw, 2.5rem);
  --t-title-xl: clamp(1.3rem, 1.9vw, 2rem);
  --t-title-l: clamp(1.2rem, 1.6vw, 1.75rem);
  --t-title-m: 1.5rem;
  --t-body-xl: 1.375rem;
  --t-body-l: 1.25rem;
  --t-body: 1.125rem;
  --t-reading: 1.0625rem;
  --t-small: 1rem;
  --t-caption: 0.875rem;
  --t-label: 0.8125rem;
  --t-micro: 0.75rem;

  /* -- Motion ------------------------------------------------------------ */
  --e-calm: cubic-bezier(0.22, 1, 0.36, 1);
  --e-breath: cubic-bezier(0.45, 0, 0.55, 1);
  --e-enter: cubic-bezier(0.16, 1, 0.3, 1);
  --d-fast: 240ms;
  --d-base: 600ms;
  --d-slow: 1200ms;
  --d-cinematic: 2400ms;
  --d-atmosphere: 1800ms;

  /* -- Runtime (written by the engine) ------------------------------------ */
  --sp: 0;          /* global scroll progress 0..1 */
  --px: 0.5;        /* pointer x, normalised */
  --py: 0.5;        /* pointer y, normalised */
  --header-h: 76px;

  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   1b. DAY — the same design in the light, not an inverted one

   > **Decision — the light theme redefines the semantic layer only.**
   > *Why:* almost every colour in this stylesheet already goes through eight
   > tokens — `--ink`, `--ink-2`, `--ink-3`, `--accent`, `--bg`, `--surface`,
   > `--line`, `--line-soft` — plus three glass steps. Ninety-five percent of
   > the text in the file is one of the first four. So the day palette is a
   > redefinition of those, checked for contrast, rather than a second
   > stylesheet that can drift from the first.
   >
   > The brand violet is the one thing that cannot simply carry over.
   > `#b79dff` on near-black is comfortable; on ivory it is 1.6:1 and
   > invisible. Every brand stop below is the same hue walked down in
   > lightness until it clears 4.5:1 on the palest ground it can land on.
   >
   > What stays dark on purpose: `.film`. It is a nine-act cinematic sequence
   > with its own sky, and a film is watched in the dark. Making it bright
   > would not be a light theme, it would be a different film.
   -------------------------------------------------------------------------- */
:root[data-theme="light"] {
  --c-void: #ffffff;
  --c-deep: #f7f4fd;
  --c-raised: #ffffff;
  --c-warm: #fdf4f8;

  --c-genesis-violet: #5a3ce0;
  --c-deep-lavender: #4a2fc4;
  --c-soft-orchid: #6d4ae4;
  /* Not a decoration token after all: the selected day on a treatment page's
     stage tabs takes it as `color`, where `#8b6eff` measured 3.34:1 on the
     panel it sits in. Its four other uses are `color-mix` fills at 18–46%,
     which a darker stop only improves. */
  --c-glass-violet: #5f3ee2;
  --c-glass-lilac: #a88fff;
  --c-rose-quartz: #c44a83;
  --c-blush-petal: #dd7fa9;
  --c-champagne: #8f6b1c;
  --c-silver-fog: #635889;
  --c-pearl: #1b1236;
  --c-ivory: #1b1236;
  --c-graphite: #1b1236;
  --c-moon: #453a68;
  --c-mist: #635889;

  /* The dark build lifts a panel with white at 3–6% over near-black. The
     inverse is not black at 3% over ivory — that reads as dirt. A raised
     surface in daylight is *whiter* than its ground, so the ground is tinted
     and the panels are white. */
  --glass-1: rgb(255 255 255 / 0.70);
  --glass-2: rgb(255 255 255 / 0.86);
  --glass-3: rgb(255 255 255 / 0.96);

  --bg: #f7f4fd;
  --surface: rgb(255 255 255 / 0.78);
  --ink: #1b1236;      /* 15.9:1 on --bg */
  --ink-2: #453a68;    /*  8.2:1 */
  --ink-3: #635889;    /*  5.1:1 */
  --accent: #5a3ce0;   /*  6.6:1 */
  --accent-soft: #7a5cf0;
  --hope: #c44a83;
  --line: rgb(27 18 54 / 0.14);
  --line-soft: rgb(27 18 54 / 0.07);
  /* The same three steps, in ink. Not a straight inversion of the alphas:
     dark-on-light is the stronger direction at equal opacity, so each step
     sits a little under its night counterpart. */
  --tint-1: rgb(27 18 54 / 0.06);
  --tint-2: rgb(90 60 224 / 0.20);
  --tint-3: rgb(90 60 224 / 0.34);
  /* Selection was `rgb(167 139 250 / .34)` with `color: #fff` in both themes.
     On ivory that is white type on pale lavender — selecting a paragraph made
     it *disappear*. The wash and the ink move together or neither moves. */
  --select-bg: rgb(122 92 240 / 0.26);
  --select-ink: #10082a;
  /* 3.4:1 against the control's fill, 3.2:1 against the page. Below 0.50
     one of the two drops under the 3:1 floor. */
  --field-line: rgb(27 18 54 / 0.50);

  --atmos-1: #f7f4fd;
  --atmos-2: #efe9fb;
  --atmos-glow: 120 90 230;
  --atmos-warm: 208 110 158;

  /* A glow on a dark ground has to fight to be felt. On a light one it only
     has to avoid looking like a stain, so every value comes down. */
  --glow-primary: 0.05;
  --glow-secondary: 0.09;
  --glow-focus: 0.14;
  --glow-hero: 0.16;
  --glow-living: 0.20;

  /* Violet-tinted rather than grey: a neutral shadow on a violet-tinted
     ground reads as a smudge. */
  --sh-micro: 0 2px 10px rgb(40 25 90 / 0.07);
  --sh-soft: 0 12px 34px rgb(40 25 90 / 0.10);
  --sh-living: 0 18px 56px rgb(40 25 90 / 0.12), 0 0 60px rgb(120 90 230 / 0.07);
  --sh-hero: 0 34px 96px rgb(40 25 90 / 0.14), 0 0 90px rgb(120 90 230 / 0.08);
  --lift-rest: var(--sh-micro);
  --sh-float: 0 60px 150px rgb(40 25 90 / 0.16);

  color-scheme: light;
}

/* The solid pill is near-white on the dark ground; in daylight that is a
   white button on a white page. It inverts, and only it — the ghost button
   already draws itself from `--ink` and `--line`. */
/* `.btn:not(.btn--ghost)`, not `.btn`.
   The solid pill is near-white on the dark ground, so in daylight it has to
   invert or it is a white button on a white page. But `:root[data-theme] .btn`
   outranks `.btn--ghost`, so the first version of this rule filled every ghost
   button with solid ink — on the hero, in the FAQ column, under every card.
   The ghost already draws itself from `--ink` and `--line` and needed nothing. */
:root[data-theme="light"] .btn:not(.btn--ghost) { --btn-bg: #1b1236; --btn-fg: #f7f4fd; }

/* The accent word is a gradient clipped to text, so it cannot inherit a
   token — the three stops are literal. They were chosen against the darkest
   grounds in the build and sit near 1.5:1 on ivory, which is not a faint
   word, it is an absent one. Same hue journey, walked down until the palest
   stop clears 4.5:1 on the lightest atmosphere. */
:root[data-theme="light"] .accent-word {
  background-image: linear-gradient(100deg, #4f2fd6 0%, #7b3fc8 46%, #b0356e 100%);
}

/* > **Decision — daylight gets no aperture. The cell is redrawn instead.**
   >
   > *What was here:* a near-opaque navy plate — `rgb(19 14 46 / 0.97)`, a
   > 28rem disc — painted on `.hero__canvas` itself in light mode, so the cell
   > could keep its night palette inside a window. The reasoning was that
   > recolouring fifteen gradient stops «would not have produced the same
   > object», and a hole cut in the page was cheaper.
   >
   > *Why it is gone:* it produced a black circle on an ivory page. Not a
   > microscope — a hole. Every other surface in the light theme had been
   > walked down to ink on paper, and this one element stayed a bruise in the
   > middle of the first screen, which is the single frame that decides
   > whether a visitor believes the rest. Worse, it was self-fulfilling: with
   > the plate under it there was no pressure to fix the drawing, so the
   > drawing stayed wrong and the plate stayed necessary.
   >
   > The fifteen stops are now `--hero-*` below, and the day inverts the
   > *model* rather than the hue: night is darkfield, where the specimen is
   > the only lit thing; day is brightfield, where the field is the lamp and
   > the specimen is denser than it. Same cell, drawn the way a stained slide
   > actually looks. Nothing is painted on the canvas element in either
   > theme — the ground is the page, in both. */

/* --------------------------------------------------------------------------
   2. TYPOGRAPHY — one voice across scripts

   A single variable family carries Persian, Arabic and Latin, so the
   rhythm between scripts is identical rather than merely similar.
   Self-hosted and split by unicode-range: a Persian page downloads only
   the Arabic slice, an English page only the Latin one. No third-party
   font request, so nothing blocks first paint.
   Swap in IRANSansX or Estedad here if the clinic licenses them —
   the rest of the system needs no change.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Vazirmatn";
  src: url("../fonts/vazirmatn-latin.6e6c2d02fedb.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2190-2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Vazirmatn";
  src: url("../fonts/vazirmatn-arabic.a78b7d32eae3.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+060C-06FF, U+0750-077F, U+08A0-08FF, U+200C-200F,
    U+FB50-FDFF, U+FE70-FEFF;
}
/* Estedad — the Persian and Arabic voice of the site.

   > **Decision — IRANSans is the site's face; Estedad is what every other
   > install of this theme gets.**
   > *Why:* the clinic bought the IRANSansWeb licence and its files are in
   > `fonts/iransans/`, so this site renders in the face it asked for. That
   > licence is the *site's*, not the theme's: the `taban.iransans` option
   > ships off and only `taban_seed` turns it on, so a second site running this
   > theme serves no licensed byte. Estedad is what it gets instead — SIL OFL,
   > shipped here with its licence, and the closest free face to IRANSans.
   > See `fonts/iransans/LICENCE-NOTE.md`.

   Same slicing discipline as the rest: Arabic range only, so an English page
   never downloads it. */
@font-face {
  font-family: "Estedad";
  src: url("../fonts/estedad-arabic-var.f3e0531cb081.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+060C-06FF, U+0750-077F, U+08A0-08FF, U+200C-200F,
    U+FB50-FDFF, U+FE70-FEFF;
}
/* The Latin accent face. Only the italic is shipped, because the accent is
   the only place it is ever used — 22 KB for the whole editorial voice. */
@font-face {
  font-family: "Instrument Serif";
  src: url("../fonts/instrument-serif-italic.bc891bdd08ad.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2000-206F;
}
/* The Persian & Arabic accent face — Amiri, a classical naskh.
   Arabic script has no italic, so the accent cannot be the Latin trick. Its
   own contrast between a plain voice and a literary one is naskh against a
   modern sans, and that is what this is: the same job Instrument Serif does
   in English, done the way the script actually does it.
   Scoped to the Arabic range, so an English page never fetches it. */
@font-face {
  font-family: "Amiri Accent";
  src: url("../fonts/amiri-accent.9ce18957fc77.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+00AB, U+00BB, U+060C, U+061B, U+061F, U+0621-063A,
    U+0640-0655, U+0660-066C, U+067E, U+0686, U+0698, U+06A9, U+06AF,
    U+06BE, U+06C0, U+06CC, U+06D5, U+06F0-06F9, U+200C-200D;
}
/* Metric-matched fallback keeps CLS at zero while the variable font arrives */
@font-face {
  font-family: "Genesis Fallback";
  src: local("Arial"), local("Helvetica"), local("Segoe UI");
  size-adjust: 97%;
  ascent-override: 92%;
  descent-override: 26%;
  line-gap-override: 0%;
}

:root {
  /* Read left to right, this is the order of preference and every entry is
     free of cost until it is reached:

     1. «IRANSans Taban» — the self-hosted licensed files. Declared by an
        `@font-face` the template only emits when `taban.iransans` is on, so
        with the option off the name resolves to nothing and costs no request.
     2. «IRANSansX» / «IRANSans» — the same face already installed on the
        visitor's own machine. No `@font-face`, therefore no network at all;
        a great many Iranian desktops have it.
     3. «Estedad» — what this repository actually ships. OFL.
     4. «Vazirmatn» — **do not remove.** IRANSansWeb has no em dash, no en
        dash and no ellipsis, and this site's copy is full of all three. They
        come from here. It is also the fallback that keeps an install
        mid-upgrade off a system sans.

     `--font-sans` is one line and this order is the whole font policy. */
  --font-sans: "IRANSans Taban", "IRANSansX", "IRANSans", "Estedad",
    "Vazirmatn", "Genesis Fallback",
    ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  --font-accent: "Instrument Serif", "Vazirmatn", Georgia, serif;
  --font-accent-rtl: "Amiri Accent", "Vazirmatn", Georgia, serif;
  --font-num: var(--font-sans);
}

/* --------------------------------------------------------------------------
   3. RESET — minimal, intentional
   -------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-block-start: calc(var(--header-h) + var(--s-5));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.6;
  font-weight: 400;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

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

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

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

button, input, select, textarea {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
}
/* --------------------------------------------------------------------------
   THE POINTER — a cell, swimming

   > **Decision — the cursor is a real cursor, not a `<div>` that follows one.**
   > *Why:* the usual way to do this is a fixed element chasing `pointermove`,
   > and it is always a frame or more behind the place the visitor is actually
   > pointing. On a site that asks people to fill in a medical consultation
   > form, a pointer that does not sit where the click will land is not a
   > flourish, it is a defect. A `cursor:` image is composited by the operating
   > system: exact, free, and still correct while the page is busy or scrolling.
   >
   > The hotspot is `4 4` — the tip of the head — so the cell's nucleus is what
   > touches the thing being pointed at.
   >
   > Two files, swapped on hover. The second is the same cell with its
   > flagellum at the other end of its beat, so hovering something clickable
   > reads as a stroke of movement instead of a colour change.

   `auto` is kept as the final fallback in every list: a browser that cannot
   load the SVG falls back to a working pointer rather than to none.
   -------------------------------------------------------------------------- */
:where(html) {
  cursor: url("../img/cursor-sperm.2a6fa3f8e834.svg") 10 10, auto;
}
/* No `:hover` needed — a cursor is only ever seen while pointing at the thing
   it is set on, so the state is implicit and the selector stays cheap. */
:where(a[href], button, summary, label[for], [role="button"],
       [tabindex]:not([tabindex="-1"]), .card, .portal, select, .faq__q) {
  cursor: url("../img/cursor-sperm-active.c0251f684a09.svg") 10 10, pointer;
}
/* Text entry keeps the I-beam. The caret's position inside a sentence is
   information the sperm cannot carry, and a visitor typing their medical
   history should not have to guess where the insertion point is. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]),
       textarea) {
  cursor: text;
}
:where(:disabled, [aria-disabled="true"]) { cursor: not-allowed; }

button { cursor: url("../img/cursor-sperm-active.c0251f684a09.svg") 10 10, pointer; }

/* The flagellum's own surface. Fixed, composited, and never a hit target —
   the head underneath it is the real cursor and must keep every click. */
.cell-pointer {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  inline-size: 100%;
  block-size: 100%;
}
:root { --cell-tail: 214, 200, 255; --cell-mid: 236, 180, 214; }
/* On ivory the hero's own tail colours vanish, so they walk down the scale
   exactly as the film's inks do. */
:root[data-theme="light"] { --cell-tail: 120, 96, 205; --cell-mid: 196, 96, 145; }
@media (prefers-reduced-motion: reduce) { .cell-pointer { display: none; } }

:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 3px solid var(--c-deep-lavender);
  outline-offset: 3px;
  border-radius: var(--r-tiny);
}

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

/* --------------------------------------------------------------------------
   4. TYPE ROLES
   -------------------------------------------------------------------------- */
.display-xxl,
.display-xl,
.display-l,
.display-m {
  font-weight: 200;
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.display-xxl { font-size: var(--t-display-xxl); }
.display-xl  { font-size: var(--t-display-xl); }
.display-l   { font-size: var(--t-display-l); }
.display-m   { font-size: var(--t-display-m); }

.headline-xl, .headline-l, .headline-m {
  font-weight: 300;
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.headline-xl { font-size: var(--t-headline-xl); }
.headline-l  { font-size: var(--t-headline-l); }
.headline-m  { font-size: var(--t-headline-m); }

.title-l { font-size: var(--t-title-l); font-weight: 500; line-height: 1.25; }
.title-m { font-size: var(--t-title-m); font-weight: 500; line-height: 1.25; }

.body-xl { font-size: var(--t-body-xl); line-height: 1.6; font-weight: 300; }
.body-l  { font-size: var(--t-body-l);  line-height: 1.6; font-weight: 300; }
.reading { font-size: var(--t-reading); line-height: 1.75; color: var(--ink-2); }

.caption { font-size: var(--t-caption); color: var(--ink-3); line-height: 1.5; }

/* Eyebrow — encodes the chapter number and its emotion, not decoration */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--ink-3);
  text-transform: uppercase;
}
.eyebrow::before {
  content: "";
  inline-size: var(--s-8);
  block-size: 1px;
  background: linear-gradient(
    to var(--flow-end, left),
    rgb(var(--atmos-glow) / 0.9),
    transparent
  );
}
[dir="rtl"] { --flow-end: left; }
[dir="ltr"] { --flow-end: right; }

/* The one aesthetic risk: emotional nouns are set apart — a light
   editorial accent in Latin, a luminous gradient in Persian & Arabic. */
.accent-word {
  /* Every stop is held above 3:1 against the lightest atmosphere, so the
     gradient stays legible rather than becoming decoration. */
  background: linear-gradient(
    100deg,
    #a98bff 0%,
    #c9a6ff 46%,
    #f2a8c8 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 300;
}
[dir="ltr"] .accent-word {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
}
/* Measured against Vazirmatn at the same size rather than guessed: the two
   letter bodies come out within 2% of each other and the descents within
   3%, so no baseline nudge is needed at all. What Amiri does do is run
   taller in the ascenders and thinner in the horizontals — high-contrast
   naskh — which reads optically a little small next to a geometric sans.
   `1.06em` is that optical correction and nothing more.

   Nothing here may touch `line-height`. In the hero the accent word is the
   whole of its own line, and a zero line-height collapsed that line to
   nothing — the word vanished from the front page. */
[dir="rtl"] .accent-word {
  font-family: var(--font-accent-rtl);
  font-weight: 400;
  font-size: 1.06em;
  letter-spacing: 0;
}

/* Numerals: keep Persian pages on Persian digits, Latin on Latin */
[lang="fa"] .num, [lang="ar"] .num { font-variant-numeric: normal; }
.num { font-feature-settings: "tnum" 1; letter-spacing: -0.02em; }

/* --------------------------------------------------------------------------
   5. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
.shell {
  inline-size: min(100% - (var(--edge) * 2), var(--container));
  margin-inline: auto;
}
.measure { max-inline-size: var(--reading); }
.measure-wide { max-inline-size: var(--content); }

.stack > * + * { margin-block-start: var(--flow, var(--s-5)); }
.stack-lg { --flow: var(--s-7); }
.stack-xl { --flow: var(--s-9); }

.skip-link {
  position: absolute;
  inset-block-start: var(--s-3);
  inset-inline-start: var(--s-3);
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: #f4f0ff;
  color: #14102a;
  border-radius: var(--r-inf);
  transform: translateY(-200%);
  transition: transform var(--d-fast) var(--e-calm);
}
.skip-link:focus-visible { transform: translateY(0); }

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

/* --------------------------------------------------------------------------
   6. THE WORLD — persistent canvas behind everything
   -------------------------------------------------------------------------- */
.world {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  contain: strict;
}
.world__canvas {
  inline-size: 100%;
  block-size: 100%;
}
/* Atmosphere layer: colour of the current chapter, cross-faded slowly */
.atmosphere {
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(
      120% 90% at calc(var(--px) * 100%) calc(var(--py) * 70%),
      rgb(var(--atmos-glow) / var(--glow-secondary)) 0%,
      transparent 62%
    ),
    radial-gradient(
      90% 70% at 82% 108%,
      rgb(var(--atmos-warm) / var(--glow-primary)) 0%,
      transparent 60%
    ),
    linear-gradient(180deg, var(--atmos-1) 0%, var(--atmos-2) 100%);
  transition:
    background-color var(--d-atmosphere) var(--e-calm);

}
/* A soft film grain keeps large flat areas alive without an image request */
.atmosphere::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}

.page { position: relative; z-index: 1; }

/* While the film is on screen it owns the entire viewport, so the page
   atmosphere below it is invisible yet still composited every frame.
   Removing it from the tree costs nothing visually and buys back the
   frame budget the film actually needs. */
[data-film="on"] .atmosphere { display: none; }

/* A backdrop-filter samples whatever is behind it every frame. Held above a
   canvas that repaints continuously, that one property costs roughly a third
   of the frame budget. Every film sky is dark, so a flat scrim is
   indistinguishable here and costs nothing. */
[data-film="on"] .header[data-lifted="true"] {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--film-scrim) 92%, transparent),
    color-mix(in oklab, var(--film-scrim) 66%, transparent));
}

/* The organism's own caption. Without it the cloud is beautiful motion;
   with it, the visitor knows they are watching a life being assembled.
   Decorative to assistive tech — the sections already carry the meaning. */
:root { --world-x: 72%; }
[dir="rtl"] { --world-x: 28%; }

.stage-caption {
  position: fixed;
  z-index: 1;
  inset-block-start: calc(50% + clamp(180px, 24vh, 268px));
  left: var(--world-x);
  translate: -50% 0;
  display: grid;
  justify-items: center;
  gap: var(--s-2);
  pointer-events: none;
  text-align: center;
  opacity: 0;
  transition: opacity var(--d-slow) var(--e-calm);
}
.stage-caption[data-visible="true"] { opacity: 1; }
.stage-caption b {
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0.02em;
}
.stage-caption i {
  font-size: var(--t-micro);
  font-style: normal;
  letter-spacing: 0.18em;
  color: var(--ink-3);
  text-transform: uppercase;
}
.stage-caption > span {
  display: grid;
  justify-items: center;
  gap: var(--s-1);
  transition: opacity var(--d-base) var(--e-calm), translate var(--d-base) var(--e-calm);
}
.stage-caption[data-changing="true"] > span { opacity: 0; translate: 0 6px; }

@media (max-width: 1080px) { .stage-caption { display: none; } }
@media (prefers-reduced-motion: reduce) { .stage-caption { transition: none; } }

/* --------------------------------------------------------------------------
   6b. THE FILM — a pinned stage the visitor scrolls *through*
   Each act carries its own palette, caption placement and mood, so arriving
   at one feels like entering a different room rather than watching the same
   object change shape.
   -------------------------------------------------------------------------- */
.film {
  position: relative;
  z-index: 2;
  /* nine acts, each roughly one screen of travel */
  block-size: 1160svh;
}
.film__stage {
  position: sticky;
  inset-block-start: 0;
  block-size: 100svh;
  overflow: clip;
  display: grid;
  isolation: isolate;
}
.film__canvas {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  z-index: 1;
  /* > **Decision — the film's ink density is one number, applied here.**
     > *Why:* `film.js` sets about eighteen alphas across nine acts, every one
     > of them tuned for an additive glow on a near-black sky. On ivory the
     > same alphas stop being glows and become *masses* — the fertilisation
     > act rendered as a saturated violet donut with the caption unreadable
     > across it. The relationships between those eighteen numbers are right;
     > only the overall level is wrong, and re-tuning them per theme would be
     > eighteen chances to drift.
     >
     > `opacity` on the canvas element is the whole correction in one token,
     > and it is free: the drawing is already its own compositing layer, so
     > the browser fades it on the GPU rather than repainting anything. The
     > alternative — routing every act through the offscreen buffer so a
     > global alpha could be applied in JS — would have cost a full-canvas
     > blit every frame to reach the same pixel. */
  opacity: var(--film-density, 1);
}

/* The stage owns its own light, independent of the page atmosphere. */
.film__sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(var(--sky-core-size, 120% 90%) at 50% 46%,
      var(--sky-core) 0%, transparent 64%),
    linear-gradient(180deg, var(--sky-1) 0%, var(--sky-2) 100%);
  transition: background 1400ms var(--e-calm);
}
/* A vignette pulls the eye to the centre — the single most cinematic device
   available, and the reason the stage reads as a frame rather than a page.
   > **Decision — the geometry is a token, because the day needs a tighter
   >   one.**
   > *Why:* `120% 88%` reaches its far stop past the corner of the frame, so
   > on a dark sky only the last few percent of the falloff is ever on screen
   > — which is exactly right there, because near-black at 40% over near-black
   > is already a deep corner. Above white the same geometry puts the tint
   > entirely off-screen and the stage renders as two flat tones. Daylight
   > brings the ellipse inside the frame and starts the falloff at a quarter
   > out, so the room actually closes. */
.film__sky::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(var(--sky-veil-size, 120% 88%) at 50% 50%,
    transparent var(--sky-veil-inner, 40%), var(--sky-vignette) 100%);
}

.film__stage {
  --sky-1: #14102a;
  --sky-2: #0f0c22;
  --sky-core: rgb(150 120 255 / 0.26);
  --sky-vignette: rgb(4 2 12 / 0.62);
  --sky-ink: #f6f2ff;
  --sky-ink-2: #c9bde8;
  --sky-rule: rgb(255 255 255 / 0.22);
}

/* The four colours the canvas draws with, as tokens rather than constants in
   the module. `film.js` reads them on start and again whenever the theme
   changes, so the nine acts follow the lights instead of being repainted in
   JavaScript. Components, not `rgb()`: the module composes its own alphas. */
:root {
  --film-scrim: #090614;
  --film-v: 118, 88, 255;    /* violet — structure */
  --film-o: 167, 139, 250;   /* orchid — the common line */
  --film-p: 236, 150, 190;   /* rose — life, warmth */
  --film-w: 255, 255, 255;   /* the near highlight */
  /* How hard the film prints. See `.film__canvas`. Night is the level every
     act was drawn for, so it is 1 by definition. */
  --film-density: 1;
  /* The halo the caption carries so it can be read *over* the drawing rather
     than beside it. At night the ink is near-white on a near-black stage and
     nothing is needed; the token exists so the day has somewhere to put its
     answer instead of inventing a second rule. */
  --caption-haze: transparent;
  --veil-glow: #1b1436;

  /* The hero's oocyte and the field swimming for it — `hero.js` reads these
     on start and again on every theme change.

     Night is **darkfield**: the specimen is the only thing lit, so every ink
     here is pale and the ground does the rest. `--hero-rim` is the travelling
     highlight on the zona and is the brightest thing in the frame; `--halo`
     and `--weight` are the two multipliers that let the day change the
     *model* rather than only the hue. See the note at the top of `hero.js`. */
  --hero-violet: 118, 88, 255;
  --hero-orchid: 167, 139, 250;
  --hero-rose: 236, 150, 190;
  --hero-pearl: 244, 240, 255;
  --hero-rim: 255, 255, 255;
  --hero-body: 146, 114, 255;
  --hero-halo: 1;
  --hero-weight: 1;
  --hero-corona: 1;
  /* The cytoplasm, centre → membrane. Brightest at the middle: at night the
     cell is lit from within. */
  --hero-cyto: 0.34, 0.21, 0.13, 0.05;

  /* The wash that lets type sit on a photograph, and the treatment the
     photograph itself gets.

     > **Decision — a scrim is a token, not a literal.**
     > *Why:* a scrim exists so text can be read over an image, which means its
     > polarity is tied to the text colour and therefore to the theme. Written
     > as literals — as these were, in two places — daylight got a near-black
     > wash under dark type, and the bottom third of every treatment plate on
     > the front page became unreadable. One set of stops, defined twice, and
     > both surfaces follow. */
  --scrim-0: rgb(18 8 40 / 0.22);
  --scrim-1: rgb(18 8 40 / 0.42);
  --scrim-2: rgb(14 6 32 / 0.82);
  --scrim-3: rgb(12 5 28 / 0.94);
  --scrim-edge: rgb(14 10 28 / 0.62);
  --scrim-glow: rgb(10 4 24 / 0.55);
  /* Laboratory photographs are lit white and cold. At night they are pulled
     down into a violet page; in daylight they are let back up. */
  --plate-img: saturate(0.72) contrast(1.04) brightness(0.84);
  --plate-img-lift: saturate(0.92) contrast(1.06) brightness(0.94);
}

/* Per-act worlds. Two of them go dark: the approach, where we are deep in
   the body, and the heartbeat, where everything else should fall away. */
/* Nine dark rooms. They differ by hue and depth, not by polarity, so the
   film belongs to the page instead of interrupting it. Act four is the one
   bright beat in the whole sequence — which is what makes it land. */
.film[data-act="0"] .film__stage { --sky-1:#0b0818; --sky-2:#100c24; --sky-core:rgb(150 120 255/.16); --sky-vignette:rgb(2 1 8/.70); }
.film[data-act="1"] .film__stage { --sky-1:#160f30; --sky-2:#1d1440; --sky-core:rgb(180 145 255/.34); --sky-vignette:rgb(4 2 12/.60); }
.film[data-act="2"] .film__stage { --sky-1:#0c0a20; --sky-2:#181140; --sky-core:rgb(140 110 255/.30); --sky-vignette:rgb(2 1 8/.72); }
.film[data-act="3"] .film__stage { --sky-1:#2b2158; --sky-2:#3b2d72; --sky-core:rgb(226 210 255/.52); --sky-vignette:rgb(10 6 26/.40); }
.film[data-act="4"] .film__stage { --sky-1:#101430; --sky-2:#151d3e; --sky-core:rgb(140 170 255/.26); --sky-vignette:rgb(2 3 12/.64); }
.film[data-act="5"] .film__stage { --sky-1:#0b1628; --sky-2:#102138; --sky-core:rgb(120 180 240/.28); --sky-vignette:rgb(1 4 12/.66); }
.film[data-act="6"] .film__stage { --sky-1:#22102a; --sky-2:#331640; --sky-core:rgb(236 150 190/.30); --sky-vignette:rgb(10 2 14/.62); }
.film[data-act="7"] .film__stage { --sky-1:#150c26; --sky-2:#2e1236; --sky-core:rgb(236 150 190/.26); --sky-vignette:rgb(6 2 12/.72); }
.film[data-act="8"] .film__stage { --sky-1:#1d1430; --sky-2:#2e2340; --sky-core:rgb(250 214 176/.34); --sky-vignette:rgb(8 4 14/.54); }

/* -- the film in daylight -------------------------------------------------
   > **Decision — the film gets a day, after all.**
   > *Why:* this section used to say the film stays dark in both themes, on the
   > grounds that a film is watched in the dark. That is a nice sentence and it
   > was the wrong call: it left a full-height black chapter in the middle of a
   > light page, which does not read as a cinema, it reads as a section that
   > was forgotten. If the site has a day, the film has one.
   >
   > Each act keeps its own hue and swaps polarity — the approach stays the
   > coolest room, the heartbeat stays rose, act three stays the one that opens
   > up. What changes is which end of the scale each sits at.

   The vignette inverts too: on a dark sky it is near-black at 60%, which on
   ivory would be a bruise. In daylight it is a soft violet haze that reads as
   depth rather than as dirt. */
:root[data-theme="light"] {
  --film-scrim: #f2eefb;
  --film-v: 78, 52, 200;
  --film-o: 108, 74, 205;
  --film-p: 190, 70, 126;
  --film-w: 38, 24, 76;
  /* > **Decision — the daylight film prints at two-thirds.**
     > *Why:* see `.film__canvas`. Two-thirds is where the fertilisation act
     > stops being a solid disc and becomes a cell you can see *into* — which
     > is also, not coincidentally, the point at which the caption crossing it
     > becomes readable. */
  --film-density: 0.70;
  /* Dark type over a drawing needs the drawing pushed away from it, and the
     honest way to do that on a stage is diffusion rather than a box: the same
     soft light that already fills the room, gathered close around the words.
     `--film-scrim` is that light, so the caption cannot drift from the sky it
     is standing in. */
  --caption-haze: var(--film-scrim);
  --veil-glow: #e7ddfa;

  /* The hero by day — **brightfield**, not a tinted darkfield.
     ------------------------------------------------------------------
     A stained slide under a brightfield scope is the exact picture this
     wants: the field is the lamp, and the specimen is *denser* than it. So
     every ink inverts its job rather than its hue. `--hero-pearl` was the
     near-white the nuclei and the sperm heads were drawn in and is now the
     darkest ink on the plate — the same shapes, subtracting light instead of
     adding it. The rose stays rose, because eosin is pink on both grounds and
     it is the one colour a reader will read as *tissue*.

     The halo multiplier is the correction that matters most. At night the
     halo is the glow a lit body throws into the fluid; by day the same shape
     at the same alpha is a violet disc a third of the screen wide, which is
     exactly what the first light theme drew. At a fifth of the opacity it
     becomes what it should be: the soft shadow of something suspended.

     The general weight goes *up*, because a dark ink on a pale ground needs
     more alpha to carry the same presence a pale ink has on black — the eye
     reads contrast, and there is less of it available above white than
     below it. */
  --hero-violet: 96, 62, 208;
  --hero-orchid: 108, 76, 196;
  --hero-rose: 198, 74, 132;
  --hero-pearl: 52, 32, 104;
  --hero-rim: 46, 28, 96;
  --hero-body: 120, 84, 214;
  --hero-halo: 0.22;
  --hero-weight: 1.45;
  /* The corona is the one thing `--hero-weight` must *not* reach. In
     darkfield those thirty-odd granulosa cells scatter the light and are
     meant to be the halo of warm points around the shell; give each of them
     45% more ink on a pale ground and they stop being points and merge into
     one pink cloud that swallows the zona. Brightfield is also simply truer
     here: a single layer of cells is the thinnest thing on the slide. */
  --hero-corona: 0.52;
  /* …and read from the outside in. The lamp is behind the slide, so the
     middle of the cell is the thinnest thing on it and the density collects
     at the membrane. Left in the night's order these four numbers drew a
     solid aubergine disc with the swimmers orbiting a hole. */
  --hero-cyto: 0.04, 0.08, 0.15, 0.26;
  /* `--scrim-0` is the top of the card, where there is no text and the
     drawing is all there is. At 34% white it was fogging the one part of the
     plate anybody sees. The lower three stops are unchanged — those are the
     ones the heading and the body copy actually stand on. */
  --scrim-0: rgb(255 253 250 / 0.10);
  --scrim-1: rgb(255 253 250 / 0.62);
  --scrim-2: rgb(253 251 255 / 0.91);
  --scrim-3: rgb(251 249 254 / 0.97);
  --scrim-edge: rgb(252 250 255 / 0.72);
  --scrim-glow: rgb(255 255 255 / 0.85);
  --plate-img: saturate(0.90) contrast(1.02) brightness(1.03);
  --plate-img-lift: saturate(1) contrast(1.03) brightness(1.07);
}
/* > **Decision — the daylight sky is lit from the centre and *deepens* at the
   > edge; it does not merely grey there.**
   > *Why:* the first daylight skies were a six-unit ramp — `#f4f1fb` to
   > `#eae5f6` — under a desaturated violet-grey vignette. Two flat tones and
   > a smudge: no depth, no direction, no room. A dark vignette is what makes
   > the night skies cinematic, and the instinct was to keep it and only
   > lighten it. That is the mistake. On a pale ground, *darkening the corners
   > reads as dirt on the lens*, not as depth.
   >
   > What produces depth above white is the same thing that produces it in a
   > watercolour: **saturation**, not value. So each daylight act now runs a
   > near-white lit core out to a corner that is more *coloured* than the
   > centre rather than darker than it, across a range four times wider than
   > before — and the vignette carries the act's own hue at enough alpha to be
   > a room instead of a haze. */
:root[data-theme="light"] .film__stage {
  --sky-core-size: 74% 60%;
  --sky-veil-size: 96% 80%;
  --sky-veil-inner: 22%;
  --sky-1: #fdfbff;
  --sky-2: #e4d9f6;
  --sky-core: rgb(255 255 255 / 0.72);
  --sky-vignette: rgb(122 92 205 / 0.34);
  --sky-ink: #1b1236;
  --sky-ink-2: #4a3f6b;
  --sky-rule: rgb(27 18 54 / 0.20);
}
:root[data-theme="light"] .film[data-act="0"] .film__stage { --sky-1:#fbf9ff; --sky-2:#ded2f2; --sky-core:rgb(255 255 255/.62); --sky-vignette:rgb( 96  74 180/.52); }
:root[data-theme="light"] .film[data-act="1"] .film__stage { --sky-1:#fefcff; --sky-2:#e2d1fa; --sky-core:rgb(255 255 255/.80); --sky-vignette:rgb(124  76 224/.44); }
:root[data-theme="light"] .film[data-act="2"] .film__stage { --sky-1:#faf8ff; --sky-2:#d8c9f5; --sky-core:rgb(255 255 255/.58); --sky-vignette:rgb( 88  66 190/.56); }
:root[data-theme="light"] .film[data-act="3"] .film__stage { --sky-1:#ffffff; --sky-2:#e6d8ff; --sky-core:rgb(255 255 255/.92); --sky-vignette:rgb(146 108 250/.36); }
:root[data-theme="light"] .film[data-act="4"] .film__stage { --sky-1:#fbfcff; --sky-2:#d3def7; --sky-core:rgb(255 255 255/.66); --sky-vignette:rgb( 58  98 200/.50); }
:root[data-theme="light"] .film[data-act="5"] .film__stage { --sky-1:#fbfdff; --sky-2:#cfe2f6; --sky-core:rgb(255 255 255/.70); --sky-vignette:rgb( 36 120 190/.48); }
:root[data-theme="light"] .film[data-act="6"] .film__stage { --sky-1:#fffbfd; --sky-2:#f7d8ea; --sky-core:rgb(255 255 255/.74); --sky-vignette:rgb(198  72 134/.44); }
:root[data-theme="light"] .film[data-act="7"] .film__stage { --sky-1:#fefafc; --sky-2:#f0cfe4; --sky-core:rgb(255 255 255/.62); --sky-vignette:rgb(178  64 124/.52); }
:root[data-theme="light"] .film[data-act="8"] .film__stage { --sky-1:#fffdfa; --sky-2:#f6e3d4; --sky-core:rgb(255 255 255/.86); --sky-vignette:rgb(196 132  64/.42); }

/* Captions are part of the composition, not a subtitle track under it. */
.film__captions {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  padding-inline: var(--edge);
  padding-block: calc(var(--header-h) + var(--s-6)) var(--s-9);
}
.act {
  grid-area: 1 / 1;
  inline-size: min(100%, var(--container));
  display: grid;
  align-content: center;
  gap: var(--s-4);
  opacity: 0;
  visibility: hidden;
  translate: 0 18px;
  transition:
    opacity 900ms var(--e-calm),
    translate 900ms var(--e-calm),
    visibility 900ms;
  color: var(--sky-ink);
}
.act[data-on="true"] { opacity: 1; visibility: visible; translate: 0 0; }

/* The diffusion that lets a caption sit *on* the drawing. Three offsetless
   shadows at rising radius: the tightest one buys the letterforms their own
   edge, the widest one clears the counters and the space between lines. It
   follows the text, which a scrim box cannot do — the acts are placed at the
   start, the end and the centre of the frame, and the words themselves are
   the only shape that is in the right place in all three. */
.act > * {
  text-shadow:
    0 0 0.35em var(--caption-haze),
    0 0 0.9em var(--caption-haze),
    0 0 2.2em var(--caption-haze);
}

/* Placement changes act to act so the frame never composes the same way twice */
.act--start   { justify-items: start;  text-align: start;  }
.act--end     { justify-items: end;    text-align: end;    }
.act--center  { justify-items: center; text-align: center; }
.act--low     { align-content: end; }
.act--high    { align-content: start; }

.act__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.18em;
  color: var(--sky-ink-2);
  text-transform: uppercase;
}
.act__eyebrow::before {
  content: "";
  inline-size: var(--s-7);
  block-size: 1px;
  background: var(--sky-rule);
}
.act__title {
  font-size: clamp(2.1rem, 5.2vw, 4.6rem);
  font-weight: 200;
  line-height: 1.06;
  letter-spacing: -0.02em;
  max-inline-size: 15ch;
  text-wrap: balance;
}
.act__line {
  font-size: var(--t-body-l);
  font-weight: 300;
  line-height: 1.6;
  color: var(--sky-ink-2);
  max-inline-size: 40ch;
}
/* The line that ties the biology on screen to what the clinic actually does.
   Without it the sequence is beautiful and irrelevant. */
.act__note {
  margin-block-start: var(--s-3);
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--sky-rule);
  font-size: var(--t-caption);
  line-height: 1.7;
  color: var(--sky-ink-2);
  max-inline-size: 38ch;
}
.act--end .act__note { padding-inline: 0 var(--s-4); border-inline: 0 2px solid var(--sky-rule); }
.act--center .act__title, .act--center .act__line { margin-inline: auto; }
.act--center .act__note { border: 0; padding-inline: 0; margin-inline: auto; }

/* Progress through the film, so the visitor always knows where they are */
.film__meter {
  position: absolute;
  z-index: 3;
  inset-block-end: var(--s-6);
  inset-inline: var(--edge);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  color: var(--sky-ink-2);
}
.film__meter span { flex: 0 0 auto; }
.film__meter i {
  flex: 1 1 auto;
  block-size: 1px;
  background: var(--sky-rule);
  position: relative;
  overflow: hidden;
}
.film__meter i::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: calc(var(--film-p, 0) * 100%);
  background: currentColor;
}

@media (max-width: 860px) {
  .film { block-size: 1020svh; }
  .film__captions { align-items: end; padding-block-end: var(--s-13); }
  .act { align-content: end; }
  .act__title { max-inline-size: 100%; }
  .act--end, .act--start { justify-items: center; text-align: center; }
  .act--end .act__note, .act--start .act__note {
    border: 0; padding-inline: 0; margin-inline: auto;
  }
  .film__captions { padding-block-end: var(--s-11); }
}

/* Without motion the film becomes what it always was underneath: a sequence
   of chapters, read one after another. Nothing is lost but the camera. */
@media (prefers-reduced-motion: reduce) {
  .film { block-size: auto; }
  .film__stage { position: static; block-size: auto; }
  .film__canvas, .film__meter { display: none; }
  .film__captions { display: block; padding-block: var(--s-11); }
  .act {
    grid-area: auto;
    opacity: 1;
    visibility: visible;
    translate: none;
    padding-block: var(--s-9);
    border-block-end: 1px solid var(--line-soft);
  }
  .act:last-child { border-block-end: 0; }
}

/* --------------------------------------------------------------------------
   7. HEADER / NAVIGATION
   -------------------------------------------------------------------------- */
.header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 60;
  block-size: var(--header-h);
  display: flex;
  align-items: center;
  transition: background var(--d-base) var(--e-calm),
              backdrop-filter var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm);
  border-block-end: 1px solid transparent;
}
.header[data-lifted="true"] {
  background: color-mix(in oklab, var(--atmos-1) 84%, transparent);
  -webkit-backdrop-filter: blur(var(--blur-m)) saturate(1.4);
  backdrop-filter: blur(var(--blur-m)) saturate(1.4);
  border-block-end-color: var(--line-soft);
}
.header__inner {
  inline-size: min(100% - (var(--edge) * 2), var(--container));
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--s-5);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-weight: 500;
  letter-spacing: -0.01em;
  /* Nothing in the masthead may wrap. In English «Taban Fertility Center» is
     three words where the Persian is four short ones, and every label in the
     bar is longer — left to itself the whole row broke into two lines. */
  flex: 0 0 auto;
  white-space: nowrap;
}
.brand__mark {
  inline-size: 38px;
  block-size: 34px;
  flex: 0 0 auto;
  /* The mark is a horizontal composition, so a square box would either crop
     the arrowhead or shrink the whole thing to fit it. */
  overflow: visible;
}
/* Only the glow moves. The old mark scaled its own core; a brand mark that
   scales is a brand mark being distorted, so the paths are left exactly as
   the clinic drew them and a drop-shadow does the living. It follows the
   alpha of the swooshes, so the light comes off the mark rather than
   sitting behind it in a box. */
.brand__mark { animation: brand-glow 5.5s var(--e-breath) infinite; }
@keyframes brand-glow {
  0%, 100% { filter: drop-shadow(0 0 5px rgb(139 110 255 / 0.28)); }
  50%      { filter: drop-shadow(0 0 11px rgb(139 110 255 / 0.58)); }
}
.brand:hover .brand__mark {
  filter: drop-shadow(0 0 13px rgb(139 110 255 / 0.7));
  animation-play-state: paused;
}
.brand__text { display: flex; flex-direction: column; line-height: 1.15; }
.brand__name { font-size: var(--t-small); font-weight: 500; }
.brand__sub {
  font-size: var(--t-micro);
  color: var(--ink-3);
  letter-spacing: 0.08em;
  font-weight: 400;
  white-space: nowrap;
}

.nav { margin-inline-start: auto; min-inline-size: 0; }
.nav__list {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  flex-wrap: nowrap;
}
.nav__link {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-caption);
  color: var(--ink-2);
  border-radius: var(--r-inf);
  transition: color var(--d-fast) var(--e-calm),
              background var(--d-fast) var(--e-calm);
}
.nav__link:hover,
.nav__link[aria-current="page"] {
  color: var(--ink);
  background: var(--tint-1);
}

.header__tools {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 0 0 auto;
  margin-inline-start: var(--s-4);
}

/* Round utility control — audio, language, menu */
.pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  block-size: 40px;
  min-inline-size: 40px;
  padding-inline: var(--s-4);
  border-radius: var(--r-inf);
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: var(--t-label);
  white-space: nowrap;
  color: var(--ink-2);
  transition: border-color var(--d-fast) var(--e-calm),
              color var(--d-fast) var(--e-calm),
              transform var(--d-fast) var(--e-calm),
              background var(--d-fast) var(--e-calm);
}
.pill:hover {
  color: var(--ink);
  border-color: color-mix(in oklab, var(--c-deep-lavender) 40%, transparent);
  transform: translateY(-1px);
}
.pill--icon { padding-inline: 0; }

/* The night/day control.
   The icon shown is the one that describes what pressing will *do*, not the
   state the page is in — a sun at night, a moon in daylight. Swapping by CSS
   rather than in the engine means the correct icon is already there on the
   first paint, in step with the theme the inline boot script stamped. */
.theme-toggle__sun, .theme-toggle__moon {
  transition: rotate var(--d-base) var(--e-calm), opacity var(--d-fast) linear;
}
:root:not([data-theme="light"]) .theme-toggle__moon { display: none; }
:root[data-theme="light"] .theme-toggle__sun { display: none; }
.theme-toggle:hover .theme-toggle__sun { rotate: 45deg; }
.theme-toggle:hover .theme-toggle__moon { rotate: -18deg; }

/* Equaliser bars inside the sound control */
.eq { display: flex; align-items: flex-end; gap: 2.5px; block-size: 14px; }
.eq span {
  inline-size: 2.5px;
  block-size: 6px;
  border-radius: 2px;
  background: currentColor;
  transition: block-size var(--d-fast) var(--e-calm);
}
[data-audio="on"] .eq span { animation: eq 1.1s var(--e-breath) infinite; }
[data-audio="on"] .eq span:nth-child(2) { animation-delay: 0.18s; }
[data-audio="on"] .eq span:nth-child(3) { animation-delay: 0.36s; }
[data-audio="on"] .eq span:nth-child(4) { animation-delay: 0.09s; }
@keyframes eq {
  0%, 100% { block-size: 5px; }
  50%      { block-size: 14px; }
}

/* Language switcher */
.lang { position: relative; }
.lang__menu {
  position: absolute;
  inset-block-start: calc(100% + var(--s-2));
  inset-inline-end: 0;
  min-inline-size: 190px;
  padding: var(--s-2);
  border-radius: var(--r-soft);
  border: 1px solid var(--line);
  background: color-mix(in oklab, var(--c-indigo) 92%, transparent);
  -webkit-backdrop-filter: blur(var(--blur-l));
  backdrop-filter: blur(var(--blur-l));
  box-shadow: var(--sh-soft);
  display: grid;
  gap: 2px;
  opacity: 0;
  transform: translateY(-6px);
  visibility: hidden;
  transition: opacity var(--d-fast) var(--e-calm),
              transform var(--d-fast) var(--e-calm),
              visibility var(--d-fast);
}
.lang[data-open="true"] .lang__menu {
  opacity: 1; transform: translateY(0); visibility: visible;
}
.lang__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-tiny);
  font-size: var(--t-caption);
  color: var(--ink-2);
  transition: background var(--d-fast) var(--e-calm), color var(--d-fast);
}
.lang__item:hover { background: var(--tint-1); color: var(--ink); }
.lang__item[aria-current="true"] { color: var(--ink); font-weight: 500; }
.lang__item span:last-child { font-size: var(--t-micro); color: var(--ink-3); letter-spacing: 0.06em; }

/* Mobile menu */
.burger { display: none; }
.drawer {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  background: color-mix(in oklab, var(--atmos-1) 96%, transparent);
  -webkit-backdrop-filter: blur(var(--blur-xl));
  backdrop-filter: blur(var(--blur-xl));
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--d-base) var(--e-calm), visibility var(--d-base);
}
.drawer[data-open="true"] { opacity: 1; visibility: visible; }
.drawer__list { display: grid; gap: var(--s-4); text-align: center; }
.drawer__list a {
  font-size: var(--t-headline-m);
  font-weight: 300;
  color: var(--ink);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--d-base) var(--e-enter),
              transform var(--d-base) var(--e-enter);
}
.drawer[data-open="true"] .drawer__list a { opacity: 1; transform: none; }
.drawer[data-open="true"] .drawer__list li:nth-child(1) a { transition-delay: 60ms; }
.drawer[data-open="true"] .drawer__list li:nth-child(2) a { transition-delay: 110ms; }
.drawer[data-open="true"] .drawer__list li:nth-child(3) a { transition-delay: 160ms; }
.drawer[data-open="true"] .drawer__list li:nth-child(4) a { transition-delay: 210ms; }
.drawer[data-open="true"] .drawer__list li:nth-child(5) a { transition-delay: 260ms; }
.drawer[data-open="true"] .drawer__list li:nth-child(6) a { transition-delay: 310ms; }
.drawer__close {
  position: absolute;
  inset-block-start: var(--s-5);
  inset-inline-end: var(--edge);
}

/* When the film enters one of its dark acts the header, rail and controls
   invert with it. Without this the chrome floats above the scene instead of
   belonging to it, and the illusion of a room breaks. */

/* --------------------------------------------------------------------------
   8. CHAPTER RAIL — the emotional curve, doubling as navigation
   -------------------------------------------------------------------------- */
.rail {
  position: fixed;
  inset-inline-end: clamp(14px, 2.2vw, 32px);
  inset-block-start: 50%;
  translate: 0 -50%;
  z-index: 50;
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-3);
  border-radius: var(--r-inf);
  opacity: 0;
  transition: opacity var(--d-slow) var(--e-calm);
}
.rail[data-visible="true"] { opacity: 1; }
.rail__item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-direction: row-reverse;
  min-block-size: 24px;
  padding-inline: var(--s-2);
  margin-inline: calc(var(--s-2) * -1);
}
[dir="ltr"] .rail__item { flex-direction: row; }
.rail__dot {
  position: relative;
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: var(--tint-3);
  transition: background var(--d-base) var(--e-calm),
              scale var(--d-base) var(--e-calm);
}
.rail__label {
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  color: var(--ink-3);
  opacity: 0;
  translate: var(--rail-shift, 8px) 0;
  transition: opacity var(--d-base) var(--e-calm), translate var(--d-base) var(--e-calm);
  white-space: nowrap;
}
[dir="rtl"] .rail { --rail-shift: -8px; }
.rail__item:hover .rail__label,
.rail__item[aria-current="true"] .rail__label { opacity: 1; translate: 0 0; }
.rail__item[aria-current="true"] .rail__dot {
  background: var(--c-deep-lavender);
  scale: 1.45;
  box-shadow: 0 0 0 6px color-mix(in oklab, var(--c-deep-lavender) 14%, transparent);
}

/* --------------------------------------------------------------------------
   9. CHAPTER SECTIONS
   -------------------------------------------------------------------------- */
.chapter {
  position: relative;
  /* Was clamp(80px, 11vh, 140px) — 280px of nothing between two chapters on a
     desktop screen, which read as the page having ended. Enough air to
     separate a chapter, not enough to lose the reader between two. */
  padding-block: clamp(56px, 7vh, 96px);
}
/* A chapter only claims a full screen when the viewport is tall enough
   to hold its content without stranding it in empty space. */
@media (min-height: 720px) {
  .chapter--tall { min-block-size: 92svh; display: grid; align-content: center; }
}

/* Scroll reveal — the engine adds [data-seen] */
/* > **Decision — the hidden state belongs to `.js .reveal`, not to `.reveal`.**
   > *Why:* `opacity: 0` written unconditionally means the heading of every
   > chapter on this site is invisible to anything that has not run the
   > observer — a reader with JavaScript off, a text browser, and any crawler
   > or audit tool that reads the DOM before the engine has stamped
   > `data-seen`. An SEO inspector reported it as «content seems to be
   > hidden», which is the polite reading; the impolite one is that a page
   > whose visible text depends on JavaScript is a page some readers get
   > blank. The boot script adds `js` to `<html>` before the first paint, so
   > for everybody who *can* see the animation nothing changes, and for
   > everybody who cannot the content is simply there. */
.js .reveal {
  opacity: 0;
  translate: 0 22px;
  filter: blur(6px);
}
.reveal {
  transition:
    opacity 1100ms var(--e-enter),
    translate 1100ms var(--e-enter),
    filter 1100ms var(--e-enter);
  transition-delay: var(--delay, 0ms);
}
.js .reveal[data-seen="true"] { opacity: 1; translate: 0 0; filter: blur(0); }

/* Per-letter arrival for display lines */
.lines { display: block; }
.line { display: block; }
/* Same reasoning as `.reveal`: the hero headline is set 105% down inside a
   clipping box until the engine says otherwise, so without JavaScript the
   biggest words on the site were an empty rectangle. Both the clip and the
   offset are `.js`-scoped, and the text simply sits where it belongs. */
.js .line { overflow: hidden; }
.js .line > span { translate: 0 105%; }
.line > span {
  display: block;
  transition: translate 1400ms var(--e-enter);
  transition-delay: var(--delay, 0ms);
}
.js [data-seen="true"] .line > span { translate: 0 0; }

/* --------------------------------------------------------------------------
   10. HERO — an opening frame, not a title card
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-block-size: 100svh;
  display: grid;
  align-content: center;
  /* Was `--s-14` (240px) at the foot, which was room for the enter cue alone.
     The facts moved into this column when the cell took the other one, and on
     a 900px screen an English headline of three lines pushed them off the
     fold. The cue needs its space; it does not need that much. */
  padding-block: calc(var(--header-h) + var(--s-5)) var(--s-9);
  overflow: clip;
}
.hero__canvas {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  z-index: 0;
  pointer-events: none;
  /* Two masks, intersected. The first fades the cell out where the headline
     begins, so the type never has to compete with it for contrast — and it
     runs along the writing direction, so it protects the correct side in
     Persian and in English alike. The second keeps swimmers from crossing the
     masthead, where they read as clutter over the menu rather than as life. */
  -webkit-mask-image:
    linear-gradient(to var(--flow-end, left),
      transparent 0%, transparent 30%, rgb(0 0 0 / 0.28) 42%, #000 62%),
    linear-gradient(to bottom,
      transparent 0, rgb(0 0 0 / 0.25) var(--header-h), #000 calc(var(--header-h) * 2));
  mask-image:
    linear-gradient(to var(--flow-end, left),
      transparent 0%, transparent 30%, rgb(0 0 0 / 0.28) 42%, #000 62%),
    linear-gradient(to bottom,
      transparent 0, rgb(0 0 0 / 0.25) var(--header-h), #000 calc(var(--header-h) * 2));
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
@media (max-width: 1079px) {
  /* One column: the cell sits behind the text, so it steps back instead. */
  .hero__canvas {
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgb(0 0 0 / 0.22) 46%);
    mask-image: linear-gradient(to bottom, #000 0%, rgb(0 0 0 / 0.22) 46%);
  }
}
.hero__grid {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(var(--s-8), 5vw, var(--s-11));
  align-items: center;
}
@media (min-width: 1080px) {
  /* The outer column is left empty on purpose: it belongs to the cell.
     The facts used to sit there as a narrow stack, which meant the one
     element the page is actually about had nowhere to be bigger than a
     background texture. They read better as a strip under the statement
     anyway — four short facts in a row scan in one pass. */
  .hero__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
  .hero__main { grid-column: 1; }
}

.hero__main .eyebrow { margin-block-end: var(--s-5); }
.hero__title { margin-block-end: var(--s-5); max-inline-size: 15ch; }
.hero__lede { max-inline-size: 42ch; color: var(--ink-2); font-weight: 300; }
.hero__actions {
  margin-block-start: var(--s-7);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
}

/* Four claims, each on its own hairline. Facts, not badges. */
.hero__facts { display: grid; gap: 0; }
.hero__facts li {
  display: grid;
  gap: var(--s-1);
  padding-block: var(--s-4);
  border-block-start: 1px solid var(--line-soft);
}
@media (min-width: 1080px) { .hero__facts li { padding-block: var(--s-3); } }
.hero__facts li:last-child { border-block-end: 1px solid var(--line-soft); }
@media (min-width: 1080px) {
  /* Under the statement, in the first column, as one strip. Four in a row
     rather than two by two: the second row was what pushed the scroll cue —
     and the fourth fact — under the fold of a 900px screen in English. */
  .hero__facts {
    grid-column: 1;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--s-5);
    margin-block-start: var(--s-4);
    max-inline-size: 52rem;
    border-block-start: 1px solid var(--line-soft);
  }
  .hero__facts li {
    padding-block: var(--s-3) 0;
    border-block-start: none;
  }
  .hero__facts li:last-child { border-block-end: none; }
  .hero__facts b { font-size: var(--t-caption); }
  .hero__facts span {
    font-size: var(--t-micro);
    line-height: 1.5;
    /* Two lines, never three: the strip has to keep one height whatever the
       language, or it moves the cue below it every time a word is longer. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}
.hero__facts b {
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--ink);
}
.hero__facts span {
  font-size: var(--t-caption);
  color: var(--ink-3);
  line-height: 1.6;
}

/* The invitation. It occupies the full width at the foot of the frame so it
   reads as a threshold rather than as a hint, and it says what is behind it
   — nine acts — because "scroll" on its own is not a reason to. */
.enter {
  position: absolute;
  z-index: 2;
  inset-block-end: 0;
  inset-inline: 0;
  display: flex;
  align-items: center;
  gap: var(--s-5);
  inline-size: min(100% - (var(--edge) * 2), var(--container));
  margin-inline: auto;
  padding-block: var(--s-6) var(--s-7);
  border-block-start: 1px solid var(--line-soft);
  transition: border-color var(--d-base) var(--e-calm);
}
.enter:hover { border-block-start-color: var(--line); }

.enter__line {
  position: relative;
  inline-size: clamp(48px, 12vw, 160px);
  block-size: 1px;
  background: var(--line);
  overflow: hidden;
  flex: 0 0 auto;
}
.enter__line::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inline-size: 40%;
  background: linear-gradient(to right, transparent, var(--accent), transparent);
  animation: sweep 3.4s var(--e-breath) infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}

.enter__body { display: grid; gap: 2px; }
.enter__body b {
  font-size: var(--t-body);
  font-weight: 400;
  color: var(--ink);
}
.enter__body span { font-size: var(--t-caption); color: var(--ink-3); }

.enter__cue {
  margin-inline-start: auto;
  inline-size: 1px;
  block-size: 34px;
  background: var(--line);
  position: relative;
  overflow: hidden;
  flex: 0 0 auto;
}
.enter__cue::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  block-size: 14px;
  background: linear-gradient(to bottom, transparent, var(--accent), transparent);
  animation: drop 2.6s var(--e-breath) infinite;
}
@keyframes drop {
  0%   { transform: translateY(-16px); opacity: 0; }
  30%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translateY(34px); opacity: 0; }
}

.enter__count {
  margin-inline-start: var(--s-5);
  font-size: clamp(2rem, 4vw, 3.2rem);
  font-weight: 200;
  line-height: 1;
  color: var(--ink);
  opacity: 0.22;
  transition: opacity var(--d-base) var(--e-calm);
}
.enter:hover .enter__count { opacity: 0.5; }

@media (max-width: 860px) {
  .hero { padding-block-end: var(--s-15); }
  .enter { gap: var(--s-4); padding-block: var(--s-5) var(--s-6); }
  .enter__body b { font-size: var(--t-small); }
  .enter__body span { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .enter__line::after, .enter__cue::after { animation: none; opacity: 0.6; }
}

/* --------------------------------------------------------------------------
   11. BUTTONS
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: #f6f2ff;
  --btn-fg: #14102a;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-7);
  border-radius: var(--r-inf);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--t-small);
  font-weight: 500;
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--d-fast) var(--e-calm),
              box-shadow var(--d-base) var(--e-calm);
  box-shadow: var(--sh-micro);
}
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg, var(--c-deep-lavender), var(--c-soft-orchid) 55%, var(--c-rose-quartz));
  opacity: 0;
  transition: opacity var(--d-base) var(--e-calm);
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--sh-soft); }
.btn:hover::before { opacity: 1; }
.btn__arrow { transition: translate var(--d-base) var(--e-calm); }
.btn:hover .btn__arrow { translate: var(--arrow-shift, -4px) 0; }
[dir="ltr"] .btn { --arrow-shift: 4px; }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  border: 1px solid var(--line);
  box-shadow: none;
}
.btn--ghost:hover { --btn-fg: #14102a; }

/* --------------------------------------------------------------------------
   12. GLASS CARD SYSTEM
   -------------------------------------------------------------------------- */
.card {
  position: relative;
  padding: var(--s-7);
  border-radius: var(--r-lg);
  border: 1px solid var(--line-soft);
  background: linear-gradient(
    150deg,
    var(--surface-2),
    var(--surface)
  );
  -webkit-backdrop-filter: blur(var(--blur-m));
  backdrop-filter: blur(var(--blur-m));
  box-shadow: var(--sh-micro);
  overflow: hidden;
  transition: transform var(--d-base) var(--e-calm),
              box-shadow var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm);
}
.card::after {
  /* cursor-following light — the surface responds to attention */
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    260px circle at var(--mx, 50%) var(--my, 50%),
    rgb(var(--atmos-glow) / 0.22),
    transparent 70%
  );
  opacity: 0;
  transition: opacity var(--d-base) var(--e-calm);
  pointer-events: none;
}
.card:hover, .card:focus-within {
  transform: translateY(-4px);
  box-shadow: var(--sh-living);
  border-color: color-mix(in oklab, var(--c-soft-orchid) 34%, transparent);
}
.card:hover::after, .card:focus-within::after { opacity: 1; }

/* --------------------------------------------------------------------------
   13. PORTAL GRID — every service is a doorway, not a list item
   -------------------------------------------------------------------------- */
.portals {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 288px), 1fr));
}
/* A uniform grid of eight reads as a spreadsheet. Giving the primary
   treatment the weight it actually has in the clinic turns the same eight
   items into a hierarchy. */
@media (min-width: 1080px) {
  .portals--bento { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .portals--bento > :first-child { grid-column: span 2; grid-row: span 2; }
  .portals--bento > :last-child { grid-column: span 2; }
  .portals--bento > :first-child .portal__title { font-size: clamp(1.6rem, 2.6vw, 2.4rem); }
  .portals--bento > :first-child .portal__text { font-size: var(--t-body); max-inline-size: 34ch; }
}
.portal {
  position: relative;
  display: grid;
  gap: var(--s-4);
  padding: var(--s-7);
  min-block-size: 268px;
  align-content: start;
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--surface);
  -webkit-backdrop-filter: blur(var(--blur-s));
  backdrop-filter: blur(var(--blur-s));
  overflow: hidden;
  isolation: isolate;
  box-shadow: var(--lift-rest);
  transition: transform var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm),
              box-shadow var(--d-base) var(--e-calm);
}
.portal::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block-start: -40%;
  inset-inline-end: -30%;
  inline-size: 74%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(var(--portal-hue, 168 139 250) / 0.42), transparent 66%);
  filter: blur(var(--blur-l));
  scale: 0.6;
  opacity: 0.55;
  transition: scale var(--d-slow) var(--e-calm), opacity var(--d-slow) var(--e-calm);
}
.portal:hover, .portal:focus-within {
  transform: translateY(-6px);
  border-color: color-mix(in oklab, rgb(var(--portal-hue, 168 139 250)) 46%, transparent);
  box-shadow: var(--sh-living);
}
.portal:hover::before, .portal:focus-within::before { scale: 1.25; opacity: 1; }
.portal__index {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  color: var(--ink-3);
}
.portal__title { font-size: var(--t-title-l); font-weight: 500; line-height: 1.25; }
.portal__latin {
  font-size: var(--t-caption);
  color: var(--ink-3);
  letter-spacing: 0.06em;
  direction: ltr;
  unicode-bidi: isolate;
}
.portal__text { font-size: var(--t-caption); color: var(--ink-2); line-height: 1.7; }
.portal__go {
  margin-block-start: auto;
  align-self: end;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-caption);
  color: var(--ink);
  font-weight: 500;
}
.portal__go svg { transition: translate var(--d-base) var(--e-calm); }
.portal:hover .portal__go svg { translate: var(--arrow-shift, -4px) 0; }
.portal a::after { content: ""; position: absolute; inset: 0; }

/* --------------------------------------------------------------------------
   13b. INSTRUMENT ROWS — the laboratory, as a specification sheet
   Three equal cards say "three of the same". Full-width rules with a ghost
   numeral say "an inventory", which is what a laboratory actually is.
   -------------------------------------------------------------------------- */
.instruments { display: grid; }
.instrument {
  display: grid;
  gap: var(--s-4) clamp(var(--s-5), 4vw, var(--s-10));
  grid-template-columns: auto 1fr;
  align-items: start;
  padding-block: clamp(var(--s-6), 4vw, var(--s-9));
  border-block-start: 1px solid var(--line-soft);
  transition: border-color var(--d-base) var(--e-calm);
}
.instruments .instrument:last-child { border-block-end: 1px solid var(--line-soft); }
.instrument:hover { border-block-start-color: var(--line); }
.instrument__no {
  font-size: clamp(2.4rem, 5vw, 4.2rem);
  font-weight: 200;
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--ink);
  opacity: 0.16;
  transition: opacity var(--d-base) var(--e-calm);
}
.instrument:hover .instrument__no { opacity: 0.42; }
.instrument__body { display: grid; gap: var(--s-3); }
.instrument__title { font-size: var(--t-title-xl); font-weight: 400; line-height: 1.2; }
.instrument__latin {
  font-size: var(--t-caption);
  letter-spacing: 0.1em;
  color: var(--accent);
  direction: ltr;
  unicode-bidi: isolate;
  text-transform: uppercase;
}
.instrument__text {
  font-size: var(--t-reading);
  line-height: 1.75;
  color: var(--ink-2);
  max-inline-size: 62ch;
}
@media (min-width: 1080px) {
  /* the description hangs in its own column, so the eye reads name-then-detail
     rather than one long measure */
  .instrument { grid-template-columns: auto minmax(0, 0.9fr) minmax(0, 1.1fr); align-items: baseline; }
  .instrument__body { grid-column: 2; }
  .instrument__text { grid-column: 3; grid-row: 1; margin: 0; }
}

/* --------------------------------------------------------------------------
   13c. INDEX LIST — the knowledge centre, as a table of contents
   -------------------------------------------------------------------------- */
.index { display: grid; }
.index__item {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--s-5);
  padding-block: var(--s-6);
  border-block-start: 1px solid var(--line-soft);
  transition: padding-inline var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm);
}
.index__item:last-child { border-block-end: 1px solid var(--line-soft); }
.index__item:hover { padding-inline-start: var(--s-4); border-block-start-color: var(--line); }
.index__no {
  font-size: var(--t-caption);
  color: var(--ink-3);
  letter-spacing: 0.12em;
}
.index__title {
  font-size: clamp(1.15rem, 2vw, 1.75rem);
  font-weight: 300;
  line-height: 1.3;
}
.index__title a { transition: color var(--d-fast) var(--e-calm); }
.index__item:hover .index__title a { color: var(--accent); }
.index__title a::after { content: ""; position: absolute; inset: 0; }
.index__meta { font-size: var(--t-caption); color: var(--ink-3); white-space: nowrap; }
.index__kicker {
  display: block;
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  color: var(--accent);
  text-transform: uppercase;
  margin-block-end: var(--s-2);
}
@media (max-width: 640px) {
  .index__item { grid-template-columns: auto 1fr; }
  .index__meta { grid-column: 2; }
}

/* --------------------------------------------------------------------------
   14. FIGURES / STATS — numbers presented as quiet certainty
   -------------------------------------------------------------------------- */
.figures {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
}
.figure {
  padding: var(--s-6) var(--s-5);
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--surface);
}
.figure__value {
  display: block;
  font-size: clamp(2.4rem, 4vw, 3.4rem);
  font-weight: 200;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.figure__value sup { font-size: 0.42em; vertical-align: super; color: var(--c-soft-orchid); }
.figure__label { display: block; margin-block-start: var(--s-3); font-size: var(--t-caption); color: var(--ink-2); }

/* --------------------------------------------------------------------------
   15. JOURNEY — the health-tourism path
   -------------------------------------------------------------------------- */
.journey { display: grid; gap: var(--s-5); counter-reset: step; }
@media (min-width: 1080px) {
  /* Read as a route travelled left-to-right (or right-to-left), which is what
     a patient journey is. The connecting rule runs behind the markers. */
  .journey--route {
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--s-5);
    position: relative;
  }
  .journey--route::before {
    content: "";
    position: absolute;
    inset-block-start: 20px;
    inset-inline: 8%;
    block-size: 1px;
    background: linear-gradient(to right, transparent, var(--line), var(--line), transparent);
  }
  .journey--route .journey__item {
    grid-template-columns: 1fr;
    gap: var(--s-4);
    padding-block: 0;
  }
  .journey--route .journey__item::after { display: none; }
  .journey--route .journey__marker { background: var(--bg); }
  .journey--route .journey__text { max-inline-size: 28ch; }
}
.journey__item {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-5);
  align-items: start;
  padding-block: var(--s-4);
}
.journey__marker {
  position: relative;
  inline-size: 40px;
  block-size: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: var(--t-caption);
  color: var(--ink-2);
  flex: 0 0 auto;
}
.journey__item::after {
  content: "";
  position: absolute;
  inset-block-start: 56px;
  inset-inline-start: 20px;
  inline-size: 1px;
  block-size: calc(100% - 40px);
  background: linear-gradient(to bottom, var(--line), transparent);
}
.journey__item:last-child::after { display: none; }
.journey__title { font-size: var(--t-title-m); font-weight: 500; margin-block-end: var(--s-2); }
.journey__text { font-size: var(--t-caption); color: var(--ink-2); line-height: 1.7; max-inline-size: 52ch; }

/* --------------------------------------------------------------------------
   16. EDITORIAL / KNOWLEDGE
   -------------------------------------------------------------------------- */
.editorial { display: grid; gap: var(--s-5); grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.article {
  display: grid;
  gap: var(--s-4);
  align-content: start;
  padding: var(--s-6);
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--surface);
  transition: border-color var(--d-base) var(--e-calm), transform var(--d-base) var(--e-calm);
  position: relative;
}
.article:hover { transform: translateY(-4px); border-color: color-mix(in oklab, var(--c-soft-orchid) 36%, transparent); }
.article a::after { content: ""; position: absolute; inset: 0; }
.article__kicker { font-size: var(--t-micro); letter-spacing: 0.14em; color: var(--c-soft-orchid); text-transform: uppercase; }
.article__title { font-size: var(--t-title-m); font-weight: 500; line-height: 1.35; }
.article__meta { font-size: var(--t-caption); color: var(--ink-3); }

/* --------------------------------------------------------------------------
   17. QUOTE / HUMAN LAYER
   -------------------------------------------------------------------------- */
.quote {
  max-inline-size: 22ch;
  font-size: var(--t-headline-l);
  font-weight: 200;
  line-height: 1.3;
  letter-spacing: -0.01em;
}
.quote__cite {
  display: block;
  margin-block-start: var(--s-6);
  font-size: var(--t-caption);
  color: var(--ink-3);
  font-style: normal;
}

/* Two-column editorial split */
.split {
  display: grid;
  gap: clamp(var(--s-7), 6vw, var(--s-12));
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split--wide { grid-template-columns: 1.1fr 0.9fr; }
}

/* --------------------------------------------------------------------------
   18. CONSULTATION PANEL
   -------------------------------------------------------------------------- */
.consult {
  position: relative;
  padding: clamp(var(--s-8), 6vw, var(--s-12));
  border-radius: var(--r-organic);
  border: 1px solid color-mix(in oklab, var(--c-glass-violet) 46%, transparent);
  background:
    radial-gradient(120% 130% at 78% 6%, rgb(150 120 255 / 0.26), transparent 58%),
    radial-gradient(90% 100% at 6% 96%, rgb(236 150 190 / 0.20), transparent 60%),
    var(--surface);
  -webkit-backdrop-filter: blur(var(--blur-m));
  backdrop-filter: blur(var(--blur-m));
  box-shadow: var(--sh-hero);
  overflow: hidden;
}
/* The same panel with no form beside it. Centred and narrower, so it reads as
   an invitation rather than as a column that lost its other half. */
.consult--cta {
  display: grid;
  justify-items: center;
  text-align: center;
}
.consult--cta .stack { max-inline-size: 54ch; }
.consult--cta .measure { margin-inline: auto; }
.consult--cta .footer__actions { justify-content: center; }

/* What happens after "send" — numbered, because the order is the reassurance. */
.consult__steps { display: grid; gap: var(--s-4); counter-reset: cstep; }
.consult__steps li {
  counter-increment: cstep;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: start;
  font-size: var(--t-caption);
  line-height: 1.7;
  color: var(--ink-2);
}
.consult__steps li::before {
  content: counter(cstep);
  inline-size: 26px;
  block-size: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--c-soft-orchid) 42%, transparent);
  background: var(--glass-2);
  font-size: var(--t-micro);
  color: var(--accent-soft);
}

.field { display: grid; gap: var(--s-2); }
.field label { font-size: var(--t-caption); color: var(--ink-2); }
.field input, .field select, .field textarea {
  inline-size: 100%;
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-soft);
  /* > **Decision — a form control's edge is its own token, and it is not
     >   `--line`.**
     > *Why:* `--line` is a hairline between *content* — a rule under a
     > heading, the edge of a card — and it is allowed to be quiet. The edge of
     > an input is not decoration: it is the entire affordance, the only thing
     > saying «you may type here», and WCAG 1.4.11 asks it for 3:1 against its
     > surround. At `rgb(27 18 54 / 0.14)` on ivory it measured 1.25:1, so the
     > consultation form in daylight was six labels above six invisible boxes.
     > Night passed only because the fill carried it. */
  border: 1px solid var(--field-line);
  background: var(--glass-2);
  /* A native control inherits neither colour nor font from its parent, and
     the approved build set neither: the select rendered as the browser's own
     white box with near-invisible text.
     `color-scheme` used to be pinned to `dark` right here — which meant that
     in daylight the drop-down opened as a dark popup in the middle of a light
     page, and the scrollbar inside it came with it. It is inherited by
     default and `:root` already declares the right one for each theme, so the
     correct value is no value at all. */
  color: var(--ink);
  font-family: inherit;
  font-size: var(--t-small);
  transition: border-color var(--d-fast) var(--e-calm), background var(--d-fast);
}
.field input::placeholder, .field textarea::placeholder { color: var(--ink-3); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--c-soft-orchid);
  outline-offset: 2px;
  background: var(--glass-3);
}
/* Firefox and Chromium both paint the list from the page, not the control. */
.field select option, .field select optgroup {
  background: var(--c-deep);
  color: var(--ink);
}
/* `appearance: none` drops the platform arrow along with the platform box, so
   the chevron is drawn back in. Inline, because a background-image on a CDN is
   a request this page does not need — and a strict CSP would refuse it. */
.field select {
  appearance: none;
  padding-inline-end: var(--s-9);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none' stroke='%23a79dcb' stroke-width='1.6'%3E%3Cpath d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-5) center;
  background-size: 12px 8px;
}
[dir="rtl"] .field select { background-position: left var(--s-5) center; }
.field select:hover { background-color: var(--glass-3); }
.field input:hover, .field select:hover, .field textarea:hover { border-color: color-mix(in oklab, var(--c-soft-orchid) 40%, transparent); }
.field textarea { min-block-size: 120px; resize: vertical; }
.form-grid { display: grid; gap: var(--s-5); grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.form-note { font-size: var(--t-caption); color: var(--ink-3); }

/* --------------------------------------------------------------------------
   19. FOOTER
   -------------------------------------------------------------------------- */
.footer {
  position: relative;
  z-index: 1;
  margin-block-start: var(--s-11);
  padding-block: var(--s-10) var(--s-7);
  border-block-start: 1px solid var(--line);
  background:
    radial-gradient(90% 120% at 50% 0%,
      color-mix(in oklab, var(--c-deep-lavender) 9%, transparent) 0%,
      transparent 62%),
    var(--surface);
}

/* The invitation. A footer whose first act is a link list has already given
   up; the last thing on every page should be a way forward. */
.footer__call {
  display: grid;
  gap: var(--s-7);
  align-items: end;
  padding-block-end: var(--s-9);
  margin-block-end: var(--s-9);
  border-block-end: 1px solid var(--line-soft);
}
@media (min-width: 900px) {
  .footer__call { grid-template-columns: 1.4fr auto; gap: var(--s-9); }
}
.footer__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); }

.footer__grid {
  display: grid;
  gap: var(--s-8);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}
@media (min-width: 1100px) {
  /* The brand column carries more than a logo, so it is given more than a
     quarter — the three link columns are narrower and read as a set. */
  .footer__grid { grid-template-columns: 1.5fr 1fr 1fr 1.3fr; }
}
.footer__brand { display: grid; gap: var(--s-4); align-content: start; }
.footer__note { color: var(--ink-2); max-inline-size: 34ch; }
.footer__tagline { color: var(--ink-3); max-inline-size: 34ch; }

.footer__title {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  color: var(--ink-3);
  text-transform: uppercase;
  margin-block-end: var(--s-4);
}
.footer__list { display: grid; gap: var(--s-2); }
/* WCAG 2.2 target size: link text stays small, the hit area does not. */
.footer__list a {
  display: inline-block;
  min-block-size: 24px;
  padding-block: 3px;
  font-size: var(--t-caption);
  color: var(--ink-2);
  transition: color var(--d-fast) var(--e-calm);
}
.footer__list a:hover { color: var(--ink); }
.footer__all { color: var(--accent) !important; }

/* Contact is a definition list in spirit: a label the eye skips and a value
   it lands on. Two lines on narrow columns, so nothing is ever truncated. */
.footer__contact li { display: grid; gap: 2px; }
.footer__key {
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  color: var(--ink-3);
  text-transform: uppercase;
}
.footer__contact span:not(.footer__key) {
  font-size: var(--t-caption);
  color: var(--ink-2);
  line-height: 1.6;
}

.footer__bottom {
  margin-block-start: var(--s-9);
  padding-block-start: var(--s-6);
  border-block-start: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-6);
  justify-content: space-between;
  align-items: center;
  font-size: var(--t-caption);
  color: var(--ink-3);
}
.footer__bottom a { display: inline-block; min-block-size: 24px; padding-block: 3px; }
.footer__legal { display: flex; flex-wrap: wrap; gap: var(--s-4); }

.footer__audio {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-block-size: 32px;
  padding: var(--s-2) var(--s-4);
  border-radius: 999px;
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  font-size: var(--t-micro);
  letter-spacing: 0.06em;
  color: var(--ink-3);
  transition: color var(--d-fast) var(--e-calm),
              border-color var(--d-fast) var(--e-calm);
}
.footer__audio:hover { color: var(--ink-2); border-color: var(--line); }
.footer__audio[aria-pressed="true"] {
  color: var(--ink);
  border-color: color-mix(in oklab, var(--c-soft-orchid) 40%, transparent);
}

/* --------------------------------------------------------------------------
   20. GENESIS ZERO — the arrival veil
   -------------------------------------------------------------------------- */
.veil {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
  /* `--veil-glow` rather than a literal: the base already came from
     `--c-deep`, so in daylight the arrival screen was a light ground with a
     near-black halo painted over the middle of it — the one screen every
     visitor sees first, and the only one that was still half at night. */
  background:
    radial-gradient(120% 90% at 50% 42%, var(--veil-glow) 0%, transparent 70%),
    var(--c-deep);
  transition: opacity 1000ms var(--e-calm), visibility 1000ms;
}
.veil[data-done="true"] { opacity: 0; visibility: hidden; }

/* The arrival, in the one image this centre is actually about: a single cell
   that becomes two, four, and then a ring. The old veil was a dot that grew
   and faded — true of any loading screen anywhere. This one only makes sense
   here.

   All of it is composited transform and opacity, so it costs nothing on the
   main thread while the page behind it parses. */
.veil__stage {
  position: relative;
  inline-size: 132px;
  block-size: 132px;
}
.veil__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--c-soft-orchid) 34%, transparent);
  opacity: 0;
  animation: veilring 2600ms var(--e-calm) forwards;
}
.veil__ring:nth-child(2) { animation-delay: 160ms; scale: 1.28; }
@keyframes veilring {
  0%       { opacity: 0; scale: 0.4; }
  40%      { opacity: 0.9; }
  100%     { opacity: 0; scale: 1.7; }
}
.veil__cell {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  inline-size: 34px;
  block-size: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%,
              var(--c-glass-lilac) 0%,
              var(--c-deep-lavender) 55%,
              color-mix(in oklab, var(--c-deep-lavender) 40%, transparent) 100%);
  box-shadow: 0 0 34px 6px rgb(139 110 255 / 0.34);
  animation: veilcell 2600ms var(--e-calm) both;
}
/* One cell, then two, then four — each pair travelling a little further out
   than the last, which is what cleavage actually looks like. */
.veil__cell:nth-child(3) { --x: 0;     --y: 0;     animation-delay: 0ms; }
.veil__cell:nth-child(4) { --x: -26px; --y: 0;     animation-delay: 620ms; }
.veil__cell:nth-child(5) { --x: 26px;  --y: 0;     animation-delay: 620ms; }
.veil__cell:nth-child(6) { --x: -26px; --y: -26px; animation-delay: 1200ms; }
.veil__cell:nth-child(7) { --x: 26px;  --y: 26px;  animation-delay: 1200ms; }
@keyframes veilcell {
  0%   { opacity: 0; translate: 0 0; scale: 0.3; }
  22%  { opacity: 1; scale: 1; }
  70%  { opacity: 1; translate: var(--x) var(--y); scale: 0.8; }
  100% { opacity: 0; translate: var(--x) var(--y); scale: 0.7; }
}
.veil__word {
  position: absolute;
  inset-block-end: calc(50% - 130px);
  font-size: var(--t-micro);
  letter-spacing: 0.42em;
  text-indent: 0.42em;              /* the last letter's tracking, given back */
  color: var(--ink-3);
  opacity: 0;
  animation: veilword 2600ms var(--e-calm) forwards;
}
@keyframes veilword {
  0%, 24% { opacity: 0; }
  52%     { opacity: 1; }
  100%    { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .veil__cell, .veil__ring { animation: none; opacity: 0.9; }
  .veil__cell:nth-child(n+4) { display: none; }
  .veil__word { animation: none; opacity: 1; }
}

/* --------------------------------------------------------------------------
   21. RESPONSIVE
   -------------------------------------------------------------------------- */
@media (max-width: 1080px) {
  .rail { display: none; }
}
/* The bar carries seven links now, and in English they are long ones. It
   hands over to the drawer while there is still room rather than at the point
   where it would have to fold — the drawer holds the same menu, so nothing is
   lost by reaching it earlier. */
@media (max-width: 1240px) {
  .nav { display: none; }
  .burger { display: inline-flex; }
}
@media (max-width: 860px) {
  :root { --header-h: 66px; }
  .hero__title { max-inline-size: 100%; }
  .card, .portal { padding: var(--s-6); }
  .portal { min-block-size: 0; }
  .consult { border-radius: var(--r-lg); }

  /* A phone screen is a third of the height of a laptop's, so the same air
     between chapters is three times as much of the view — long enough to
     read as the page having ended. Everything that sets vertical rhythm
     comes down together, so the proportions stay what they were. */
  .chapter { padding-block: clamp(40px, 5vh, 60px); }
  /* Two by two rather than four stacked: the same four facts in half the
     height, which is most of what made the hero a screen and a half. */
  .hero { padding-block: calc(var(--header-h) + var(--s-6)) var(--s-8); }
  .hero__facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s-5);
    margin-block-start: var(--s-5);
  }
  .hero__facts li { padding-block: var(--s-3); }
  .hero__facts li:nth-child(2) { border-block-start: 1px solid var(--line-soft); }
  .hero__facts li:last-child { border-block-end: none; }
  .hero__facts li:nth-last-child(-n+2) { border-block-end: 1px solid var(--line-soft); }
  .stack-lg { --flow: var(--s-6); }
  .stack-xl { --flow: var(--s-7); }
  .footer { margin-block-start: var(--s-9); padding-block: var(--s-8) var(--s-6); }
  .footer__call { padding-block-end: var(--s-7); margin-block-end: var(--s-7); }
  .footer__grid { gap: var(--s-7); }
  .footer__bottom { margin-block-start: var(--s-7); }
  .consult { padding: var(--s-7) var(--s-6); }
}
@media (max-width: 520px) {
  .header__tools .pill--label { display: none; }
  .figure { padding: var(--s-5) var(--s-4); }
  /* Two ragged pills read as an accident; two equal ones read as a choice. */
  .hero__actions { display: grid; grid-template-columns: 1fr; }
  .hero__actions .btn { justify-content: center; }
}

/* --------------------------------------------------------------------------
   22. REDUCED MOTION & PRINT — accessibility is above beauty
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; translate: none; filter: none; }
  .line > span { translate: none; }
  .veil { display: none; }
  .scroll-cue__track::after { display: none; }
}

@media print {
  .world, .atmosphere, .rail, .header, .veil, .scroll-cue { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ==========================================================================
   INTERIOR PAGES
   Components shared by every treatment chapter (IVF, ICSI, PGT, freezing…).
   A treatment page is the same system as the home page, at a slower pace.
   ========================================================================== */

/* --------------------------------------------------------------------------
   23. BREADCRUMB — position in the story, not decoration
   -------------------------------------------------------------------------- */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-caption);
  color: var(--ink-3);
}
.crumbs a {
  min-block-size: 24px;
  display: inline-flex;
  align-items: center;
  transition: color var(--d-fast) var(--e-calm);
}
.crumbs a:hover { color: var(--ink); }
.crumbs li { display: flex; align-items: center; gap: var(--s-2); }
.crumbs li + li::before {
  content: "";
  inline-size: 14px;
  block-size: 1px;
  background: var(--line);
}
.crumbs [aria-current="page"] { color: var(--ink-2); }

/* --------------------------------------------------------------------------
   24. PAGE HERO — quieter than the home hero; the story is already running
   -------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  padding-block: calc(var(--header-h) + var(--s-10)) var(--s-11);
  min-block-size: 72svh;
  display: grid;
  align-content: center;
}
.page-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  margin-block-start: var(--s-7);
  font-size: var(--t-caption);
  color: var(--ink-2);
}
.page-hero__meta div { display: grid; gap: 2px; }
.page-hero__meta dt { font-size: var(--t-micro); color: var(--ink-3); letter-spacing: 0.1em; }
.page-hero__meta dd { margin: 0; font-weight: 500; color: var(--ink); }

/* --------------------------------------------------------------------------
   25. STAGES — an embryo does not have "features", it has days
   -------------------------------------------------------------------------- */
.stages {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .stages { grid-template-columns: minmax(220px, 0.9fr) 1.6fr; align-items: start; }
}

.stages__list { display: grid; gap: var(--s-1); }
.stages__tab {
  position: relative;
  display: grid;
  gap: 2px;
  inline-size: 100%;
  text-align: start;
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-soft);
  border: 1px solid transparent;
  color: var(--ink-2);
  transition: background var(--d-fast) var(--e-calm),
              border-color var(--d-fast) var(--e-calm),
              color var(--d-fast) var(--e-calm);
}
.stages__tab:hover { background: color-mix(in oklab, var(--c-glass-violet) 18%, transparent); color: var(--ink); }
.stages__tab[aria-selected="true"] {
  background: color-mix(in oklab, var(--c-deep-lavender) 26%, transparent);
  border-color: color-mix(in oklab, var(--c-soft-orchid) 38%, transparent);
  color: var(--ink);
  box-shadow: var(--sh-micro);
}
.stages__day { font-size: var(--t-micro); letter-spacing: 0.12em; color: var(--ink-3); }
.stages__tab[aria-selected="true"] .stages__day { color: var(--c-glass-violet); }
.stages__name { font-size: var(--t-small); font-weight: 500; }

.stages__panel {
  position: relative;
  padding: clamp(var(--s-6), 4vw, var(--s-9));
  border-radius: var(--r-lg);
  border: 1px solid var(--line-soft);
  background: var(--glass-2);
  -webkit-backdrop-filter: blur(var(--blur-m));
  backdrop-filter: blur(var(--blur-m));
  min-block-size: 300px;
  display: grid;
  gap: var(--s-5);
  align-content: start;
}
.stages__panel[hidden] { display: none; }
.stages__panel > * { animation: stagein 700ms var(--e-enter) both; }
.stages__panel > *:nth-child(2) { animation-delay: 70ms; }
.stages__panel > *:nth-child(3) { animation-delay: 140ms; }
.stages__panel > *:nth-child(4) { animation-delay: 210ms; }
@keyframes stagein {
  from { opacity: 0; translate: 0 12px; filter: blur(4px); }
  to   { opacity: 1; translate: 0 0; filter: blur(0); }
}
.stages__figure {
  inline-size: 100%;
  max-inline-size: 220px;
  aspect-ratio: 1;
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
   26. PROCEDURE — a numbered sequence, because the order genuinely matters
   -------------------------------------------------------------------------- */
.steps { display: grid; gap: var(--s-5); counter-reset: step; }
.steps__item {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-6);
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--surface);
  counter-increment: step;
}
.steps__item::before {
  content: counter(step, decimal-leading-zero);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  color: #5b3ce8;
}
.steps__title { font-size: var(--t-title-m); font-weight: 500; }
.steps__text { font-size: var(--t-caption); color: var(--ink-2); line-height: 1.75; }
@media (min-width: 760px) {
  .steps { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
}

/* --------------------------------------------------------------------------
   27. FAQ — native disclosure, no JavaScript, keyboard-complete by default
   -------------------------------------------------------------------------- */
/* The questions beside their heading, not under it.
   `.faq` is capped at `--content` because an answer is prose and prose has a
   measure. On its own inside a 1440px shell that filled exactly half the page
   and left the other half empty — a measure doing the job of a layout. The
   spread gives the empty half to the heading and makes the column deliberate. */
.faq-spread {
  display: grid;
  gap: var(--s-7) var(--s-8);
  align-items: start;
}
@media (min-width: 62rem) {
  .faq-spread { grid-template-columns: minmax(16rem, 1fr) minmax(0, var(--content)); }
  /* Sticky, so the heading is still there at the fortieth question. The offset
     clears the fixed masthead. */
  .faq-spread__aside { position: sticky; top: calc(var(--header-h) + var(--s-6)); }
}
.faq-spread__aside { display: grid; gap: var(--s-4); justify-items: start; }
.faq-spread__note {
  max-inline-size: 26ch;
  color: var(--ink-2);
  font-size: var(--t-body);
  line-height: 1.7;
}
.faq-spread__count {
  font-family: var(--font-accent);
  font-size: clamp(2.5rem, 6vw, 4rem);
  line-height: 1;
  color: color-mix(in oklab, var(--c-soft-orchid) 60%, transparent);
}
.faq-spread .faq { max-inline-size: var(--content); }

.faq { display: grid; gap: var(--s-3); max-inline-size: var(--content); }
.faq__item {
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  overflow: hidden;
  transition: border-color var(--d-base) var(--e-calm);
}
.faq__item[open] { border-color: color-mix(in oklab, var(--c-soft-orchid) 34%, transparent); }
.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  font-size: var(--t-body);
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  min-block-size: 24px;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "";
  flex: 0 0 auto;
  inline-size: 12px;
  block-size: 12px;
  /* `border-right`, not `border-inline-end`.
     The two borders form an L that a 45° turn points downwards — but only if
     the L is always in the same physical corner. Logical borders put it in the
     bottom-*left* under RTL, so every question on the Persian and Arabic site
     had a chevron pointing sideways at the text instead of down at its own
     answer. Open/closed is not a direction the writing mode has an opinion
     about, so this one is deliberately physical. */
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  rotate: 45deg;
  translate: 0 -3px;
  transition: rotate var(--d-base) var(--e-calm);
}
.faq__item[open] .faq__q::after { rotate: 225deg; translate: 0 3px; }
.faq__a {
  padding: 0 var(--s-6) var(--s-6);
  font-size: var(--t-reading);
  line-height: 1.75;
  color: var(--ink-2);
  max-inline-size: var(--reading);
}

/* --------------------------------------------------------------------------
   28. NOTE — a quiet aside for clinical caveats, never a scare box
   -------------------------------------------------------------------------- */
.note {
  padding: var(--s-5) var(--s-6);
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  border-inline-start: 2px solid color-mix(in oklab, var(--c-soft-orchid) 60%, transparent);
  background: var(--glass-1);
  font-size: var(--t-caption);
  line-height: 1.75;
  color: var(--ink-2);
  max-inline-size: var(--content);
}

/* --------------------------------------------------------------------------
   29. PLATFORM SURFACES — pagination, facets, the search bar

   Not part of the approved static build, because that build had eight
   hand-written pages and nothing to paginate or search. The CMS routes them
   anyway, so they are drawn here in the same tokens rather than left
   unstyled — the alternative is a browser-default row of links at the foot of
   every archive.
   -------------------------------------------------------------------------- */
.pager {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  flex-wrap: wrap;
  padding-block-start: var(--s-6);
  border-block-start: 1px solid var(--line-soft);
}
.pager .caption { color: var(--ink-3); letter-spacing: 0.08em; }

.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border-radius: 999px;
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--ink-2);
  transition: border-color var(--d-base) var(--e-calm),
              color var(--d-base) var(--e-calm);
}
.chip:hover { border-color: color-mix(in oklab, var(--c-soft-orchid) 45%, transparent); color: var(--ink); }
.chip--on {
  border-color: color-mix(in oklab, var(--c-soft-orchid) 65%, transparent);
  background: color-mix(in oklab, var(--c-glass-violet) 22%, transparent);
  color: var(--ink);
}
.chip .num { color: var(--ink-3); }

.search-bar {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
  align-items: center;
  max-inline-size: var(--content);
}
.search-bar input { flex: 1 1 min(100%, 320px); }

/* --------------------------------------------------------------------------
   30. MOBILE RHYTHM, CONTINUED

   These three components are declared after §21, so their phone-sized
   spacing has to be declared after them or the cascade throws it away. Same
   reasoning as the block up there: on a screen a third as tall, the same air
   is three times as much of the view.
   -------------------------------------------------------------------------- */
@media (max-width: 860px) {
  .page-hero {
    padding-block: calc(var(--header-h) + var(--s-7)) var(--s-8);
    min-block-size: 0;
  }
  .stages__panel { padding: var(--s-6); min-block-size: 0; }
  .steps { gap: var(--s-4); }
}

@media (min-width: 1080px) {
  /* The cue moves under the cell. It used to run the full width at the foot
     of the hero, which is where the facts strip now is — the two were being
     printed on top of each other. Declared here because `.enter` sets
     `inset-inline` itself further up, and the cascade takes the later one. */
  .enter {
    inset-inline: 50% var(--edge);
    inline-size: auto;
    margin-inline: 0;
  }
}

/* --------------------------------------------------------------------------
   31. PICTURES

   The theme uses pictures in three registers and never mixes them:

     .portal--plate   a drawn treatment plate behind glass, under a violet
                      scrim  — /services/ and the front page's fifth chapter
     .rack            a photograph of this centre, at full width — the
                      laboratory and technology archives
     .figure          an explanatory diagram inside the body of one page

   The scrim is not decoration. A card is a link with a heading, a Latin
   name, a line of body text and an arrow, all in near-white; dropped
   straight onto an image, the least contrast on the card decides whether
   the card is readable, and no image can promise that. The scrim fixes the
   floor: whatever the plate does, the text sits on at least
   `rgb(20 8 42 / 0.72)` where the text actually is.
   -------------------------------------------------------------------------- */
.portal--plate { min-block-size: 320px; }
.portal__art {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  border-radius: inherit;
}
.portal__art img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: center 34%;
  opacity: 0.90;
  /* The plate leans toward the card as the card lifts. 1.06 is small enough
     that the drawing is never cropped past its own margins. */
  scale: 1;
  transition: scale var(--d-slow) var(--e-calm), opacity var(--d-slow) var(--e-calm);
}
.portal:hover .portal__art img,
.portal:focus-within .portal__art img { scale: 1.06; opacity: 1; }

/* > **Decision — the twelve plates ship once and daylight inverts them.**
   > *Why:* they are generated line drawings (`scripts/taban_plates.py`), and
   > every stroke in them is baked as `#EDE9FE` at a per-element `opacity`.
   > That is a drawing in light, and on a white card it is a blank card — in
   > daylight the whole fifth chapter of the front page and the whole services
   > archive were showing twelve empty panels. Nobody would report it as
   > broken; they would just find the page thin.
   >
   > Three ways out, and the reason this is the one: shipping a second set of
   > files doubles twelve assets and adds a way for the two to diverge on the
   > next regeneration; masking the shape and painting it with a colour token
   > needs the file's URL in CSS, which means an inline `style` on a `<img>`
   > and a hole in `style-src`; and re-tinting with `sepia`/`saturate` throws
   > away the tone structure.
   >
   > `invert(1)` alone is exact: it leaves alpha untouched, so all sixty-odd
   > per-element opacities — which are the *drawing* — survive to the pixel.
   > It only lands on the wrong hue, olive, because blue was the high channel.
   > A half turn of the wheel puts it back: `#EDE9FE → #151126`, which is
   > `--ink` to within a hair, and the one accent stroke `#C4B5FD → #483981`,
   > which keeps it distinguished from the body of the drawing exactly as it
   > was. Same plate, same weights, other polarity. */
:root[data-theme="light"] .portal__art img {
  filter: invert(1) hue-rotate(180deg);
}

/* The veil, in two layers: a vertical wash that carries the text, and a
   violet bloom in the top corner that ties the picture to the card's hue. */
.portal__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      var(--scrim-0) 0%,
      var(--scrim-1) 40%,
      var(--scrim-2) 76%,
      var(--scrim-3) 100%),
    radial-gradient(120% 88% at 78% 6%,
      rgb(var(--portal-hue, 168 139 250) / 0.34), transparent 62%);
  transition: opacity var(--d-slow) var(--e-calm);
}
.portal:hover .portal__art::after,
.portal:focus-within .portal__art::after { opacity: 0.88; }

/* With a picture behind it the corner bloom is doubled up — the veil already
   carries the hue, so the blurred blob underneath only muddies it. */
.portal--plate::before { display: none; }
.portal--plate .portal__title,
.portal--plate .portal__text { text-shadow: 0 1px 18px var(--scrim-glow); }

/* --------------------------------------------------------------------------
   31b. RACK — the laboratory and the instruments, photographed

   Deliberately not a card grid. An archive of four rooms rendered as four
   equal tiles reads as "four articles about rooms"; a full-width plate with
   the name set beside it reads as a place you could walk into.
   -------------------------------------------------------------------------- */
.rack { display: grid; gap: clamp(var(--s-7), 6vw, var(--s-10)); }
.rack__item {
  position: relative;
  display: grid;
  gap: var(--s-5);
  align-items: center;
}
@media (min-width: 900px) {
  .rack__item { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
  /* Alternating sides give the archive a rhythm a uniform column cannot. The
     picture keeps its reading order in the DOM; only the columns swap. */
  .rack__item:nth-child(even) .rack__shot { grid-column: 2; grid-row: 1; }
  .rack__item:nth-child(even) .rack__body { grid-column: 1; grid-row: 1; }
}
.rack__shot {
  position: relative;
  border-radius: var(--r-med);
  overflow: hidden;
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  aspect-ratio: 3 / 2;
}
.rack__shot img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Photographs of a laboratory are lit white and cold. Left alone they
     punch a hole in a page that is otherwise violet and dark, so they are
     brought into the palette rather than pasted onto it. */
  filter: var(--plate-img);
  scale: 1;
  transition: scale 900ms var(--e-calm), filter 900ms var(--e-calm);
}
.rack__item:hover .rack__shot img {
  scale: 1.04;
  filter: var(--plate-img-lift);
}
.rack__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(200deg, rgb(139 110 255 / 0.20), transparent 54%),
    linear-gradient(to top, var(--scrim-edge), transparent 58%);
  pointer-events: none;
}
.rack__no {
  position: absolute;
  z-index: 2;
  inset-block-start: var(--s-5);
  inset-inline-start: var(--s-5);
  font-size: var(--t-caption);
  letter-spacing: 0.18em;
  color: var(--ink);
  opacity: 0.7;
}
.rack__body { display: grid; gap: var(--s-4); align-content: center; }
.rack__title { font-size: var(--t-title-xl); font-weight: 400; line-height: 1.18; }
.rack__latin {
  font-size: var(--t-caption);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  direction: ltr;
  unicode-bidi: isolate;
}
.rack__text {
  font-size: var(--t-reading);
  line-height: 1.75;
  color: var(--ink-2);
  max-inline-size: 46ch;
}
.rack__item a::after { content: ""; position: absolute; inset: 0; }

/* --------------------------------------------------------------------------
   31c. JOURNAL — the knowledge centre's archive, with no pictures at all

   The clearest signal that these are articles and the treatments are not is
   the absence of a picture. The front page already reads the newest six as
   an index; the archive is the same instrument at full length, with the date
   and the reading time a reader uses to decide.
   -------------------------------------------------------------------------- */
.journal { display: grid; }
.journal__item {
  position: relative;
  display: grid;
  gap: var(--s-3) var(--s-6);
  grid-template-columns: 1fr;
  padding-block: clamp(var(--s-6), 3.4vw, var(--s-8));
  border-block-start: 1px solid var(--line-soft);
  transition: padding-inline var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm);
}
.journal__item:last-child { border-block-end: 1px solid var(--line-soft); }
.journal__item:hover {
  padding-inline-start: var(--s-4);
  border-block-start-color: var(--line);
}
@media (min-width: 860px) {
  .journal__item { grid-template-columns: 7.5rem minmax(0, 1fr) 9rem; align-items: baseline; }
}
.journal__kicker {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.journal__title {
  font-size: clamp(1.25rem, 2.4vw, 2rem);
  font-weight: 300;
  line-height: 1.28;
  letter-spacing: -0.01em;
}
.journal__title a { transition: color var(--d-fast) var(--e-calm); }
.journal__item:hover .journal__title a { color: var(--accent); }
.journal__lede {
  grid-column: 1 / -1;
  font-size: var(--t-caption);
  line-height: 1.75;
  color: var(--ink-3);
  max-inline-size: 68ch;
}
@media (min-width: 860px) {
  .journal__lede { grid-column: 2 / 3; }
}
.journal__meta {
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  color: var(--ink-3);
  white-space: nowrap;
}
.journal__item a::after { content: ""; position: absolute; inset: 0; }

/* --------------------------------------------------------------------------
   31d. FIGURE — a diagram inside the body of one page

   Framed, captioned, and never edge to edge: it is a document the reader may
   want to look at closely, not atmosphere. `.figure--tall` is for the
   portrait posters, which would otherwise be two screens high.
   -------------------------------------------------------------------------- */
.figure {
  margin-block: clamp(var(--s-7), 5vw, var(--s-9));
  display: grid;
  gap: var(--s-4);
}
.figure__frame {
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--glass-2);
  padding: clamp(var(--s-4), 2vw, var(--s-6));
  overflow: hidden;
}
.figure__frame img {
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--r-soft);
  /* These are the customer's own artwork on white. Inverting or tinting them
     would misrepresent a clinical diagram, so they keep their own ground and
     the frame does the work of belonging to the page. */
  background: #fff;
}
.figure--tall .figure__frame img { max-block-size: 78vh; object-fit: contain; }
.figure figcaption {
  font-size: var(--t-caption);
  color: var(--ink-3);
  line-height: 1.7;
}

/* --------------------------------------------------------------------------
   31e. PAGE PLATE — the photograph at the head of a page that has one
   -------------------------------------------------------------------------- */
.page-plate {
  position: relative;
  margin-block-start: clamp(var(--s-7), 5vw, var(--s-9));
  border-radius: var(--r-large, var(--r-med));
  overflow: hidden;
  border: 1px solid var(--line-soft);
  aspect-ratio: 21 / 9;
}
.page-plate img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* > **Decision — a photograph's treatment is a token, in all three places
     >   the theme uses one.**
     > *Why:* `--plate-img` and `--scrim-edge` were introduced for exactly this
     > and then three components kept their literals — this one, the
     > instrument shelf and the journey strip. Every one of them multiplied a
     > laboratory photograph down to 86% brightness and laid a near-black wash
     > over the bottom half, which is the correct way to sink a cold white
     > photograph into a violet night and is a *stain* on ivory. In daylight
     > the About page opened on a photograph that looked like it had been
     > taken with the lens cap half on. */
  filter: var(--plate-img);
}
.page-plate::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top, var(--scrim-edge), transparent 54%),
    radial-gradient(90% 70% at 20% 0%,
      rgb(var(--atmos-glow) / 0.22), transparent 60%);
}
@media (max-width: 720px) {
  .page-plate { aspect-ratio: 4 / 3; }
  .rack__shot { aspect-ratio: 16 / 10; }
  .portal--plate { min-block-size: 268px; }
}

/* --------------------------------------------------------------------------
   32. LOOP — the last chapter, which is the first one again

   An aperture rather than a headline on an empty band: four rings emanate
   outward on a nine-second cycle, a dashed ring turns behind them, and a
   core breathes at the centre. The type sits inside all of it.

   Every animated property here is `scale`, `rotate` or `opacity` — the three
   the compositor can run without touching layout or paint. The chapter is on
   screen while the browser is still settling the rest of the page, so it is
   not allowed to cost anything.
   -------------------------------------------------------------------------- */
.loop {
  --aperture: min(112vw, 92vh, 900px);
  /* > **Decision — the aperture is drawn at its own weight, per theme.**
     > *Why:* the rings, the dashed orbits and the core were all a single
     > alpha of `--atmos-glow`, chosen against near-black. A one-pixel violet
     > hairline at 55% is unmistakable on a dark page and a rumour on ivory —
     > in daylight the whole closing chapter was a headline floating in an
     > empty band, which is precisely the composition the aperture exists to
     > avoid. Nothing here is a *glow* in daylight; it is a drawn line, so it
     > is drawn: thicker, at full strength, and the core carries real colour
     > instead of a suggestion of one. */
  --loop-line: rgb(var(--atmos-glow) / 0.55);
  --loop-line-w: 1px;
  --loop-peak: 0.62;
  --loop-core-1: rgb(var(--atmos-glow) / 0.30);
  --loop-core-2: rgb(var(--atmos-glow) / 0.10);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
:root[data-theme="light"] .loop {
  --loop-line: rgb(var(--atmos-glow) / 0.68);
  --loop-line-w: 1.5px;
  --loop-peak: 0.92;
  --loop-core-1: rgb(var(--atmos-glow) / 0.30);
  --loop-core-2: rgb(var(--atmos-warm) / 0.16);
}
.loop__field {
  /* Centred by `inset: 0` + `margin: auto` on a box with both sizes fixed.
     Two other ways were wrong for this: `inset-inline-start: 50%` with a
     negative translate is direction-aware in the wrong direction — in RTL
     the inline start is the right edge, and the aperture slid off the left
     of the Persian and Arabic front pages — and centring it as a grid cell
     fixed that but let a 112vw-wide aperture set the column width, so on a
     phone the headline and buttons were laid out 500px wide inside a 390px
     screen and spilled off both edges. This centres in both directions and
     contributes nothing to layout. */
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: -1;
  inline-size: var(--aperture);
  block-size: var(--aperture);
  display: grid;
  place-items: center;
  pointer-events: none;
  /* Softened at the edge so the rings do not collide with the chapter above:
     the emanation should look like it is coming out of the page, not like a
     circle drawn on it. */
  -webkit-mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
}
.loop__ring,
.loop__dash,
.loop__core,
.loop__orbit { position: absolute; border-radius: 50%; }

.loop__ring {
  inline-size: 100%;
  aspect-ratio: 1;
  border: var(--loop-line-w) solid var(--loop-line);
  opacity: 0;
  scale: 0.28;
  animation: loop-emanate 9s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
  animation-delay: calc(var(--i) * -2.25s);
}
@keyframes loop-emanate {
  0%   { scale: 0.24; opacity: 0; }
  14%  { opacity: var(--loop-peak); }
  100% { scale: 1.06; opacity: 0; }
}

.loop__dash {
  inline-size: 62%;
  aspect-ratio: 1;
  border: 1px dashed var(--tint-2);
  animation: loop-turn 64s linear infinite;
}
.loop__dash::after {
  content: "";
  position: absolute;
  inset: 14%;
  border-radius: 50%;
  border: 1px dashed var(--tint-1);
}
@keyframes loop-turn { to { rotate: 360deg; } }

.loop__core {
  inline-size: 38%;
  aspect-ratio: 1;
  background:
    radial-gradient(circle at 42% 38%,
      var(--loop-core-1) 0%,
      var(--loop-core-2) 42%,
      transparent 70%);
  filter: blur(var(--blur-l));
  animation: loop-breathe 7.5s var(--e-calm, ease-in-out) infinite;
}
@keyframes loop-breathe {
  0%, 100% { scale: 0.94; opacity: 0.75; }
  50%      { scale: 1.08; opacity: 1; }
}

/* Three points on a slow orbit — the only thing in the composition that is
   not concentric, which is what keeps it from reading as a target. */
.loop__orbit {
  inline-size: 62%;
  aspect-ratio: 1;
  animation: loop-turn 44s linear infinite reverse;
}
.loop__orbit i {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: 5px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--accent, #b79dff);
  opacity: 0.5;
  translate: -50% -50%;
  rotate: var(--a);
  transform: translateY(calc(var(--aperture) * -0.31));
}

.loop__inner {
  position: relative;
  text-align: center;
  display: grid;
  justify-items: center;
  gap: var(--s-5);
  padding-block: clamp(var(--s-8), 9vh, var(--s-11));
}
.loop__title {
  max-inline-size: 21ch;
  margin-inline: auto;
  margin-block-start: var(--s-3);
  text-wrap: balance;
}
.loop__lead {
  max-inline-size: 46ch;
  color: var(--ink-2);
  margin-block-start: var(--s-3);
}
.loop__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: center;
  margin-block-start: var(--s-5);
}
.loop__sign {
  font-size: var(--t-caption);
  color: var(--ink-3);
  max-inline-size: 40ch;
  margin-block-start: var(--s-5);
}
.loop__return {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  margin-block-start: var(--s-6);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  color: var(--ink-3);
  transition: color var(--d-base) var(--e-calm), gap var(--d-base) var(--e-calm);
}
.loop__return:hover { color: var(--ink); gap: var(--s-4); }
.loop__return svg { transition: rotate var(--d-slow) var(--e-calm); }
.loop__return:hover svg { rotate: -180deg; }

@media (max-width: 860px) {
  .loop { --aperture: min(128vw, 74vh, 620px); }
  .loop__inner { padding-block: clamp(var(--s-7), 6vh, var(--s-9)); gap: var(--s-4); }
  .loop__actions { inline-size: 100%; }
  .loop__actions .btn { flex: 1 1 auto; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  /* The composition stays; only the motion goes. Rings that animate from
     `opacity: 0` would otherwise leave an empty chapter behind. */
  .loop__ring,
  .loop__dash,
  .loop__core,
  .loop__orbit { animation: none; }
  .loop__ring { opacity: 0.30; }
  .loop__ring:nth-of-type(1) { scale: 0.42; }
  .loop__ring:nth-of-type(2) { scale: 0.64; }
  .loop__ring:nth-of-type(3) { scale: 0.86; }
  .loop__ring:nth-of-type(4) { scale: 1.04; }
  .loop__core { opacity: 0.85; }
}

/* --------------------------------------------------------------------------
   33. INSTRUMENT MARK — the numeral, and the instrument under it

   Declared after §13b so it wins the cascade, and kept inside the row's
   existing first column so that a row without a photograph aligns exactly
   with one that has it.
   -------------------------------------------------------------------------- */
.instrument__mark { display: grid; gap: var(--s-4); justify-items: start; }
.instrument__shot {
  display: block;
  inline-size: clamp(64px, 8vw, 96px);
  aspect-ratio: 1;
  border-radius: var(--r-soft);
  overflow: hidden;
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
}
.instrument__shot img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  filter: var(--plate-img);
  transition: filter var(--d-slow) var(--e-calm), scale var(--d-slow) var(--e-calm);
}
.instrument:hover .instrument__shot img {
  filter: var(--plate-img-lift);
  scale: 1.06;
}

/* --------------------------------------------------------------------------
   34. JOURNEY STRIP — the international patient's route, photographed

   Four steps in the order they happen. On a phone it scrolls sideways with
   scroll-snap rather than stacking into four full-width photographs, because
   the sequence is the point and a sequence you have to scroll a screen
   between steps of stops being one.
   -------------------------------------------------------------------------- */
.journey-strip {
  display: grid;
  gap: var(--s-5);
  grid-auto-flow: column;
  grid-auto-columns: min(78%, 300px);
  overflow-x: auto;
  scroll-snap-type: inline mandatory;
  /* The cards lift on hover; without the padding the lift is clipped by the
     scroll container. */
  padding-block-end: var(--s-4);
  margin-inline: calc(var(--edge) * -1);
  padding-inline: var(--edge);
  scrollbar-width: thin;
}
@media (min-width: 900px) {
  .journey-strip {
    grid-auto-flow: row;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
  }
}
.journey-strip__step {
  position: relative;
  display: grid;
  gap: var(--s-3);
  scroll-snap-align: start;
}
.journey-strip__shot {
  position: relative;
  border-radius: var(--r-med);
  overflow: hidden;
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  aspect-ratio: 4 / 3;
}
.journey-strip__shot img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  filter: var(--plate-img);
  transition: filter 700ms var(--e-calm), scale 700ms var(--e-calm);
}
.journey-strip__step:hover .journey-strip__shot img {
  filter: var(--plate-img-lift);
  scale: 1.05;
}
.journey-strip__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    color-mix(in oklab, var(--scrim-edge) 68%, transparent), transparent 52%);
}
.journey-strip__no {
  font-size: var(--t-micro);
  letter-spacing: 0.18em;
  color: var(--accent);
}
.journey-strip__label {
  font-size: var(--t-caption);
  line-height: 1.6;
  color: var(--ink-2);
}
/* The thread that makes four pictures one route. Drawn between the frames,
   not under them, and only where there is a next step to reach. */
@media (min-width: 900px) {
  .journey-strip__step:not(:last-child) .journey-strip__shot::before {
    content: "";
    position: absolute;
    z-index: 2;
    inset-block-start: 50%;
    inset-inline-end: calc(var(--s-5) * -1);
    inline-size: var(--s-5);
    block-size: 1px;
    background: linear-gradient(to left,
      rgb(var(--atmos-glow, 139 110 255) / 0.7), transparent);
  }
}

/* --------------------------------------------------------------------------
   35. TOAST — the result of a submission

   Built in JavaScript from `bp:form:result`, appended to `<body>`, and
   positioned against the viewport rather than the form. The message it
   replaces was a line of small type at the foot of a long form, on a page
   the visitor had just scrolled to the bottom of: the report of the one
   thing they came to do, in the least visible place on the page.

   Not a modal. A confirmation that steals focus and has to be dismissed
   before anything else can happen is a worse answer than one that cannot be
   missed and does not interrupt.
   -------------------------------------------------------------------------- */
.toast {
  position: fixed;
  z-index: 90;
  inset-block-end: clamp(var(--s-5), 4vh, var(--s-7));
  /* `right`, not `inset-inline-start`. The live-chat launcher is a fixed
     bottom-left bubble in every language — it does not flip with the
     writing direction, so a toast that does flip lands on top of it in
     English and beside it in Persian. */
  right: clamp(var(--s-5), 4vw, var(--s-8));
  inline-size: min(420px, calc(100vw - var(--s-6) * 2));

  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-5) var(--s-5) var(--s-4);

  border-radius: var(--r-med);
  border: 1px solid var(--line);
  background: color-mix(in oklab, var(--c-raised) 92%, transparent);
  -webkit-backdrop-filter: blur(var(--blur-m)) saturate(1.4);
  backdrop-filter: blur(var(--blur-m)) saturate(1.4);
  box-shadow: var(--sh-living);

  /* The entrance: up from below and into focus. `translate` and `opacity`
     only — the toast arrives while the browser is still settling a form
     submission and must not cost a layout pass. */
  opacity: 0;
  translate: 0 20px;
  scale: 0.98;
  transition: opacity 420ms var(--e-calm),
              translate 420ms var(--e-out, cubic-bezier(0.16, 1, 0.3, 1)),
              scale 420ms var(--e-calm);
}
.toast--in { opacity: 1; translate: 0 0; scale: 1; }

/* The left rule carries the outcome. Colour is never the only signal — the
   mark inside the disc differs too, for anyone who cannot tell the two
   hues apart. */
.toast::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 3px;
  border-start-start-radius: var(--r-med);
  border-end-start-radius: var(--r-med);
  background: var(--toast-hue);
}
.toast--ok  { --toast-hue: #7ee0b0; }
.toast--bad { --toast-hue: #f4a3a3; }

.toast__mark {
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--toast-hue) 18%, transparent);
  color: var(--toast-hue);
  flex: 0 0 auto;
}
.toast__mark svg { inline-size: 16px; block-size: 16px; }

.toast__text {
  font-size: var(--t-caption);
  line-height: 1.7;
  color: var(--ink);
  /* The success message is an operator's own sentence and can run long. */
  overflow-wrap: anywhere;
}

.toast__close {
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: 50%;
  border: 1px solid transparent;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: color var(--d-fast) var(--e-calm),
              border-color var(--d-fast) var(--e-calm);
}
.toast__close:hover { color: var(--ink); border-color: var(--line-soft); }
.toast__close svg { inline-size: 14px; block-size: 14px; }

@media (max-width: 620px) {
  /* Full width at the foot of the screen, above the thumb rather than under
     it — a floating card inset from both edges wastes the width a phone has
     least of. */
  .toast {
    inset-inline: var(--s-4);
    inline-size: auto;
    /* Above the chat launcher rather than over it: at full width there is no
       sideways room to avoid it. */
    inset-block-end: calc(var(--s-4) + 68px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast { transition: opacity 200ms linear; translate: none; scale: 1; }
}

/* --------------------------------------------------------------------------
   40. THE SCORE — a world that answers the music

   Sixty seconds at 120 BPM, thirty bars, looping on the bar. `score.js` writes
   six numbers to the root each frame and this section is everything the page
   does with them.

   > **Decision — every reaction is multiplied by `--score-on`.**
   > *Why:* the alternative is a `[data-score]` attribute and a parallel set of
   > rules, which is two designs to keep in step and a visible jump the moment
   > the sound stops. A multiplier means silence is not a different stylesheet,
   > it is the same one with the amplitude at zero — and the transition between
   > them is free, because it is arithmetic rather than a rule swap.

   > **Decision — only `scale`, `opacity` and `filter` are animated.**
   > *Why:* this runs sixty times a second for as long as somebody listens. All
   > three are composited; not one of them reads layout. A beat that moved
   > `width` would be the most expensive thing on the page.
   -------------------------------------------------------------------------- */
:root {
  --score-on: 0;
  --beat: 0;
  --beat-ease: 0;
  --bar: 0;
  --sub: 0;
  --bass: 0;
  --low: 0;
  --mid: 0;
}

/* The cell breathes on the beat. 3.5% at the hit — perceptible next to the
   existing slow breath, and nowhere near enough to read as a bounce. */
.hero__canvas {
  scale: calc(1 + var(--beat-ease) * 0.035 * var(--score-on));
  transition: scale 90ms linear;
}

/* The daylight cell takes the low end as *density*, not brightness. It is ink
   on paper now rather than light in a window, and `brightness()` on ink lifts
   it toward the paper — the cell would have faded on every kick, which is the
   opposite of a pulse. `contrast()` presses it into the page instead. */
:root[data-theme="light"] .hero__canvas {
  filter: contrast(calc(1 + var(--bass) * 0.14 * var(--score-on)));
}

/* The brand mark keeps the beat quietly — it is in the corner of every page,
   so it is the one element that can carry the tempo without being noticed. */
.brand__mark {
  scale: calc(1 + var(--beat-ease) * 0.05 * var(--score-on));
  transition: scale 110ms var(--e-calm);
}

/* The accent word takes the mids, which is where the melody actually is: the
   band climbs from 0.01 in bar one to 0.73 by bar twenty-four, so the one
   coloured word on the page brightens across the whole piece. */
.accent-word {
  filter: saturate(calc(1 + var(--mid) * 0.5 * var(--score-on)))
          brightness(calc(1 + var(--mid) * 0.14 * var(--score-on)));
}

/* The loop chapter's rings ride the low-mid, which is the band that carries
   this track's movement. */
.loop__ring { opacity: calc(0.55 + var(--low) * 0.45 * var(--score-on)); }
.loop__core { scale: calc(1 + var(--beat-ease) * 0.06 * var(--score-on)); }

/* -- bar nine ------------------------------------------------------------
   The sub sits at about 0.02 for the whole piece and 0.48 here. It is not a
   bass line, it is the one event the track has, and the page is built to have
   somewhere to put it: a single bloom of light from the middle of the world,
   two bars long, that costs one composited element. */
.drop-bloom {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle at 50% 50%,
    rgb(var(--atmos-glow) / 0.30) 0%,
    rgb(var(--atmos-glow) / 0.10) 34%,
    rgb(var(--atmos-glow) / 0) 62%);
}
:root[data-bar="9"] .drop-bloom,
:root[data-bar="10"] .drop-bloom {
  opacity: calc(var(--sub) * var(--score-on));
  transition: opacity 240ms var(--e-calm);
}

/* -- the control ---------------------------------------------------------- */
/* The equaliser on the footer button already animates on its own 1.1s loop,
   which is right for the synthesised pad — that has no tempo to keep. With a
   score playing there *is* one, so the bars take the beat instead and the
   control becomes an honest meter of what is actually sounding. */
:root[data-bar] .eq span {
  animation: none;
  block-size: calc(5px + var(--beat-ease) * 9px * var(--score-on));
}
:root[data-bar] .eq span:nth-child(2) { block-size: calc(5px + var(--bass) * 9px * var(--score-on)); }
:root[data-bar] .eq span:nth-child(3) { block-size: calc(5px + var(--low) * 9px * var(--score-on)); }
:root[data-bar] .eq span:nth-child(4) { block-size: calc(5px + var(--mid) * 9px * var(--score-on)); }

/* --------------------------------------------------------------------------
   Nothing above moves for a visitor who asked for stillness.

   The *sound* is left alone on purpose: `prefers-reduced-motion` is a
   statement about motion, and silencing music because someone gets motion
   sick would be answering a question they did not ask. The footer control is
   how sound is refused.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .hero__canvas, .brand__mark, .loop__core { scale: 1; }
  .accent-word { filter: none; }
  .loop__ring { opacity: 0.55; }
  .drop-bloom { display: none; }
}

/* --------------------------------------------------------------------------
   41. THE ROSTER — the clinical team, as a fifth register

   The site already has four: glass plates for treatments, full-width
   photographs for the rooms, an unillustrated journal for the articles and a
   numbered sequence for a pathway. A page of people is a fifth kind of thing,
   and the one where the photograph *is* the content — so it is a short column
   of large portraits with the name beside them, closer to a masthead than to
   a directory.

   Deliberately not a dense grid of round avatars: this roster is read slowly,
   by somebody deciding whom to trust with a hard year, and twelve identical
   thumbnails is the shape of a staff list rather than that decision.
   -------------------------------------------------------------------------- */
/* `auto-fill` with a *capped* track, not `auto-fit` with `1fr`.
   A clinic starting out has one doctor on this page, and `auto-fit` collapses
   the empty tracks and stretches that one card across 1400px — a single
   portrait with a metre of glass beside it, which reads as a layout error
   rather than as a short roster. A capped track keeps the card the size it
   would be in a full row and lets the rest of the line stay empty, which is
   what "one doctor so far" honestly looks like. */
.roster {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  justify-content: start;
}
/* Two or three per row on a wide screen, never four: four across turns a
   portrait into a thumbnail, and the whole point of this register is that it
   does not. */
@media (min-width: 1080px) {
  .roster { grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 480px)); }
}
.roster__item {
  position: relative;
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--surface);
  -webkit-backdrop-filter: blur(var(--blur-s));
  backdrop-filter: blur(var(--blur-s));
  overflow: hidden;
  box-shadow: var(--lift-rest);
  transition: transform var(--d-base) var(--e-calm),
              border-color var(--d-base) var(--e-calm),
              box-shadow var(--d-base) var(--e-calm);
}
.roster__item:hover, .roster__item:focus-within {
  transform: translateY(-6px);
  border-color: color-mix(in oklab, var(--accent) 40%, transparent);
  box-shadow: var(--sh-living);
}
.roster__link {
  display: grid;
  grid-template-columns: minmax(120px, 38%) 1fr;
  align-items: stretch;
  color: inherit;
  text-decoration: none;
}
.roster__shot {
  position: relative;
  display: grid;
  place-items: center;
  background: var(--glass-1);
  overflow: hidden;
}
.roster__shot img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* The supplied portraits are studio shots with the head high in the frame;
     centring them crops the forehead. */
  object-position: 50% 22%;
  transition: scale var(--d-slow) var(--e-calm);
}
.roster__item:hover .roster__shot img { scale: 1.04; }
/* No photograph yet is not the same as no doctor. A grey silhouette says the
   second; an initial in the brand's own type says the first. */
.roster__initial {
  display: grid;
  place-items: center;
  inline-size: 100%;
  aspect-ratio: 3 / 4;
  font-size: clamp(2.4rem, 6vw, 4rem);
  font-weight: 200;
  color: color-mix(in oklab, var(--accent) 55%, var(--ink-3));
  background:
    radial-gradient(circle at 50% 30%,
                    color-mix(in oklab, var(--accent) 18%, transparent),
                    transparent 68%);
}
.roster__body {
  display: grid;
  gap: var(--s-2);
  align-content: start;
  padding: var(--s-6);
}
.roster__name { font-size: var(--t-title-l); font-weight: 500; line-height: 1.3; }
.roster__role { font-size: var(--t-caption); color: var(--ink-2); line-height: 1.6; }
.roster__unit { font-size: var(--t-caption); color: var(--ink-3); }
/* The one line on this card a reader can independently check, so it is set
   like a fact and not like a caption. */
.roster__council {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-2);
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  color: var(--ink-3);
}
.roster__council .num { color: var(--ink); font-weight: 500; letter-spacing: 0.14em; }
.roster__body .portal__go { margin-block-start: var(--s-3); }
.roster__link::after { content: ""; position: absolute; inset: 0; }

/* Inside a profile's «هم‌تیمی‌ها», where the cards are a supporting note
   rather than the page. */
.roster__item--compact .roster__link { grid-template-columns: 96px 1fr; }
.roster__item--compact .roster__body { padding: var(--s-4); }
.roster__item--compact .roster__name { font-size: var(--t-title-m); }

@media (max-width: 30rem) {
  .roster__link { grid-template-columns: 1fr; }
  .roster__shot { aspect-ratio: 4 / 3; }
  .roster__shot img { object-position: 50% 26%; }
}

/* --------------------------------------------------------------------------
   42. THE PROFILE — one person, above the fold

   Everything in the identity block is either checkable or absent. Years of
   experience, patient counts and success figures are none of them here, and
   there is no slot for them: a layout with a hole shaped like a statistic
   invites somebody to fill it.
   -------------------------------------------------------------------------- */
.profile-hero__grid { display: grid; gap: var(--s-7); align-items: start; }
@media (min-width: 62rem) {
  .profile-hero__grid { grid-template-columns: minmax(0, 32%) minmax(0, 1fr); }
}
.profile-hero__shot {
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  overflow: hidden;
}
.profile-hero__shot img {
  inline-size: 100%;
  block-size: auto;
  display: block;
  object-fit: cover;
}
.profile-hero__id { gap: var(--s-4); }

/* The credential row: a label, the number, and the link that checks it. The
   link is the point — a number nobody can verify is decoration. */
.credential {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-soft);
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  inline-size: fit-content;
}
.credential__label { font-size: var(--t-micro); letter-spacing: 0.1em; color: var(--ink-3); }
.credential__value {
  font-size: var(--t-body-l);
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--ink);
}
.credential__check {
  font-size: var(--t-caption);
  color: var(--ink-2);
  border-block-end: 1px solid currentColor;
}
.credential__check:hover { color: var(--ink); }

.profile-section { display: grid; gap: var(--s-5); }
.profile-social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  font-size: var(--t-caption);
  color: var(--ink-2);
}
.profile-social a { display: inline-flex; align-items: center; gap: var(--s-2); }

/* A career is one column in one order. The `.steps` grid lays four jobs out
   side by side, which loses the only thing a reader wants from them. */
.steps--rail { grid-template-columns: 1fr; }
@media (min-width: 760px) {
  .steps--rail { grid-template-columns: 1fr; max-inline-size: var(--content); }
}
.steps__where { font-size: var(--t-caption); color: var(--ink-2); }
.steps__when {
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  color: var(--ink-3);
}

.facts-list {
  display: grid;
  gap: var(--s-2) var(--s-5);
  align-items: baseline;
}
@media (min-width: 40rem) {
  .facts-list { grid-template-columns: minmax(8rem, max-content) 1fr; }
}

/* --------------------------------------------------------------------------
   43. GETTING HERE — the airports, the terminals and the metro

   A reader planning a journey is scanning for one line, theirs. Rows, not
   prose, and the distance set as a number so the eye can find it without
   reading the sentence around it.
   -------------------------------------------------------------------------- */
.routes { display: grid; gap: var(--s-3); }
@media (min-width: 62rem) {
  .routes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3) var(--s-5); }
}
.route {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: start;
  padding: var(--s-5);
  border-radius: var(--r-soft);
  border: 1px solid var(--line-soft);
  background: var(--surface);
}
.route__icon {
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: color-mix(in oklab, var(--accent) 14%, transparent);
  color: color-mix(in oklab, var(--accent) 78%, var(--ink));
}
/* Drawn, not a font. The site ships no icon font, and the first version of
   these rows used `<i class="fa …">` — which rendered as five empty circles. */
.route__icon svg { display: block; }
.route__body { display: grid; gap: var(--s-1); }
.route__name { font-size: var(--t-body-l); font-weight: 500; line-height: 1.4; }
.route__detail {
  font-size: var(--t-caption);
  color: var(--ink);
  letter-spacing: 0.02em;
}
.route__note { font-size: var(--t-caption); color: var(--ink-3); line-height: 1.6; }

/* The journey, drawn.
   A map screenshot arrives as somebody else's rectangle — a bright web map on
   a dark page, hard edges against soft ones. Three rules settle it into the
   surface: the theme's own radius and hairline, and a night-mode treatment
   that takes the glare off without inverting the image into something
   unreadable. `aspect-ratio` with `cover` keeps the row heights equal whatever
   the centre uploads, which is what stops a two-column grid from going
   ragged. */
.route__map {
  margin: var(--s-2) 0 0;
  border-radius: var(--r-soft);
  border: 1px solid var(--line-soft);
  overflow: hidden;
  background: color-mix(in oklab, var(--ink) 4%, transparent);
}
.route__map img {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  /* The dark page is the default here, so the dimming is too — and the day
     theme takes it back off. */
  filter: saturate(0.75) brightness(0.82) contrast(1.05);
}
:root[data-theme="light"] .route__map img { filter: none; }

/* --------------------------------------------------------------------------
   44. THE MAP — `apps.maps`, in this theme's surfaces

   Nothing is fetched from the map service before the button is pressed, so
   the placeholder is what most visitors see and it has to look finished
   rather than broken. It is a plate with an invitation on it, not an empty
   frame with a spinner.
   -------------------------------------------------------------------------- */
.bp-map { display: grid; gap: var(--s-4); }
.bp-map__frame {
  position: relative;
  block-size: var(--bp-map-height, 440px);
  border-radius: var(--r-med);
  border: 1px solid var(--line-soft);
  background: var(--glass-1);
  overflow: hidden;
}
.bp-map__frame iframe {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;
}
.bp-map__frame--gated::before {
  /* The suggestion of a street grid under the invitation, so the block reads
     as a map that is not open yet rather than as a panel that failed. */
  content: "";
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(58deg, transparent 0 38px,
      color-mix(in oklab, var(--ink-3) 14%, transparent) 38px 39px),
    repeating-linear-gradient(-32deg, transparent 0 54px,
      color-mix(in oklab, var(--ink-3) 10%, transparent) 54px 55px);
  opacity: 0.5;
}
.bp-map__consent {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--s-2);
  inline-size: 100%;
  padding: var(--s-6);
  border: 0;
  background: transparent;
  color: inherit;
  text-align: center;
  cursor: inherit;
}
.bp-map__consent-title {
  font-size: var(--t-title-m);
  font-weight: 500;
  padding: var(--s-3) var(--s-6);
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--glass-3);
  -webkit-backdrop-filter: blur(var(--blur-s));
  backdrop-filter: blur(var(--blur-s));
  transition: border-color var(--d-base) var(--e-calm);
}
.bp-map__consent:hover .bp-map__consent-title,
.bp-map__consent:focus-visible .bp-map__consent-title {
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
}
.bp-map__consent-note {
  font-size: var(--t-caption);
  color: var(--ink-2);
  max-inline-size: 34ch;
}
.bp-map__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-5);
}
.bp-map__address { font-size: var(--t-caption); color: var(--ink-2); }
.bp-map__directions {
  font-size: var(--t-caption);
  color: var(--ink);
  border-block-end: 1px solid color-mix(in oklab, var(--ink) 40%, transparent);
}
