/* ==========================================================================
   1Q Academy — Design System
   Part of 1Q Health. Colours, type and spacing per brand handover document.
   ========================================================================== */

/* ---------- 0. Webfonts ----------
   Aptos — headings, sublines, labels and other emphasis.
   Noto Sans — body copy, self-hosted as WOFF2, subset to the Latin, Greek,
   punctuation and maths ranges the site uses.

   Aptos is licensed through Adobe Fonts and may NOT be downloaded and
   self-hosted from this folder — Adobe serves it from a web project. Two steps
   are needed once, in the Adobe Fonts account:

     1. Create a web project containing Aptos, weights 400, 600 and 700,
        normal and italic. Note the project (kit) ID.
     2. Add the kit stylesheet to the <head> of every page, above style.css:
          <link rel="stylesheet" href="https://use.typekit.net/KITID.css">
        `build.py` has the line ready and commented out — uncomment it there,
        insert the ID, and re-run the build.

   Until then --font-display falls back to a locally installed Aptos, which
   Windows and Microsoft 365 machines have. Everywhere else the fallback chain
   below applies, so the site stays fully usable and nothing shifts.

   Worth knowing before it goes live: the kit is a request to use.typekit.net on
   every page view, which passes the visitor's IP to Adobe. That is the one
   reason Noto Sans is self-hosted here. Adobe sets no cookies for font serving
   and offers a data processing agreement, but for a German B2B site the point
   is worth raising with whoever signs off on the privacy policy. The
   alternative is a desktop-and-web licence for Aptos from a distributor that
   permits self-hosting; then the fonts move into ../fonts/ like Noto Sans. */

/* Athiti returns for one line only: the 1Q Health promise in the band under the
   hero. It is not the site's display face any more — Aptos is — but the quote is
   set apart deliberately, and Athiti is licensed under the SIL OFL, so unlike
   Aptos it can simply be self-hosted from this folder. Two weights are here;
   only 600 is used. */
@font-face {
  font-family: "Athiti";
  src: url("../fonts/athiti-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Athiti";
  src: url("../fonts/athiti-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}

/* Variable weight axis 100–900 in a single file. */
@font-face {
  font-family: "Noto Sans";
  src: url("../fonts/notosans-var.woff2") format("woff2-variations"),
       url("../fonts/notosans-var.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Noto Sans";
  src: url("../fonts/notosans-italic-var.woff2") format("woff2-variations"),
       url("../fonts/notosans-italic-var.woff2") format("woff2");
  font-weight: 100 900; font-style: italic; font-display: swap;
}

/* ---------- 1. Design tokens ---------- */
:root {
  /* Brand colours */
  /* Version 3: the whole blue family is derived from 1Q Health blue #005B82,
     hue locked to 198° like the Academy palette it replaces. #005B82 is eight
     lightness points lighter than #003E59, so every tint and every gold-on-blue
     pairing had to be re-measured, not just recoloured. */
  --blue:        #005B82;  /* Primary — 1Q Health blue, 7.45:1 on white */
  --blue-light:  #006F9E;  /* Links / secondary text — 5.57:1 on white */
  --blue-mid:    #34444B;
  --blue-pale:   #C7D9E1;  /* Body copy on primary — 5.12:1 */
  --blue-faint:  #BDD2DB;  /* Eyebrow labels on primary — 4.76:1 */
  /* The gold family (--gold #D9A404, --gold-ink, --gold-deep #8A6800,
     --gold-light #F8D982) was removed in Version 4. Every accent that used it
     now uses the blue family above. See section 'Gold removal' in README.md. */
  --paper:       #EFF4F6;  /* Page background */
  --paper-tint:  #EAF1F5;  /* Resting surface of a publication record; the
                              focused record fades this away to white. Light blue,
                              from the --paper family.

                              Not lighter than this, and not darker. Lighter (the
                              #F1F6F8 first tried here) is only 1.02:1 against the
                              page background, so the card dissolved into the page,
                              and it was so close to white that a half-faded record
                              already looked focused. Darker (#E5EEF3 and below)
                              drops the blue citation separator to 4.40:1, under AA.

                              At this value every text colour inside a record
                              clears AA: ink 11.0:1, ink-soft 5.8:1, blue 6.5:1,
                              blue-light 4.9:1. */
  --ink:         #333333;  /* Body copy — never pure black */
  --ink-soft:    #4B5F66;
  --footer-bg:   #002E42;  /* Darkened #005B82 — keeps the footer as the page's anchor */
  --white:       #FFFFFF;

  /* Two lockups, two aspect ratios. The homepage carries the claim version
     (946 x 502 = 1.884); the subpages carry the wordmark on its own
     (1137 x 574 = 1.981), because the claim only needs saying once, on the page
     where the brand is introduced. Every panel measurement is derived from this,
     so it has to be a variable rather than a number repeated six times. */
  /* 1.981 — the lockup's real aspect, measured from the file: 1137 x 574, with no
     transparent margin on any side. The root value said 1.884, left over from the
     previous artwork, and every panel width on the homepage is derived from it. The
     logo was therefore rendering about 5% wider than the box had been sized for, which
     is why it sat hard against the right edge there and not on the subpages, which
     already carried the corrected value. */
  --logo-aspect: 1.981;

  --line:        rgba(0, 91, 130, 0.14);
  --line-strong: rgba(0, 91, 130, 0.28);
  --line-dark:   rgba(255, 255, 255, 0.18);
  /* The logo panel's ground: 12% of the primary over paper. Light enough that the
     blue lockup keeps its full contrast on it, distinct enough from the paper to
     read as a panel rather than a rendering artefact. */
  --brand-panel: #D8E3E9;

  /* The header's own ground. Paper by default; each section sets its own tint
     below. Kept as a variable because three things depend on it: the header bar
     and the top ramp of both panel layers, whose fade has to end in whatever
     colour the header is, or the seam at the top edge comes back. */
  --header-bg: var(--paper);

  /* Type — Aptos for display and emphasis, Noto Sans for reading.
     `aptos` is the family name Adobe Fonts serves; "Aptos" is the locally
     installed one on Windows and Microsoft 365. Segoe UI Variable and Segoe UI
     follow because they are the closest match already present on Windows, then
     the platform UI font. */
  --font-display: "aptos", "Aptos", "Segoe UI Variable Display", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-body: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-label: "aptos", "Aptos", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-quote: "Athiti", "aptos", "Aptos", "Segoe UI", sans-serif;

  /* Layout */
  /* Was 1400px. At that width a publication citation ran to well over 120
     characters before wrapping, and the page read as a wide admin table rather
     than as an editorial page. 1080px keeps the three-column card grids on the
     landing page comfortable while bringing the text column back to a normal
     measure. Anything sized from the content width must read this variable, not a
     copy of its value — see the promise band, which had 1400px baked into its
     font-size arithmetic and overflowed the page when this changed. */
  --container: 1080px;
  /* Header only: the nav needs about 1126px and does not fit the content column.
     See .site-header__inner. */
  --container-wide: 1240px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --section-y: clamp(3.5rem, 8vw, 6.25rem);
  --radius: 3px;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(0, 91, 130, 0.08);
  --shadow-md: 0 8px 24px -8px rgba(0, 91, 130, 0.22);
}

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

/* `display` declarations on components would otherwise beat the UA [hidden] rule.
   Filtering and search rely on the hidden attribute, so it must always win. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

/* One cut lighter throughout. Aptos ships Light, Regular, SemiBold, Bold and
   ExtraBold — there is no Medium — so h1 and h2 go from Bold to SemiBold and
   h3/h4 from SemiBold to Regular. At display sizes the lighter cut reads as more
   composed; a heavy headline is the fastest way to look like an advertisement. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--blue);
  line-height: 1.15;
  margin: 0 0 0.6em;
  text-wrap: balance;
}
h1 { font-size: clamp(2rem, 1.3rem + 3vw, 3.35rem); letter-spacing: -0.015em; }
h2 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.15rem); }
/* h1 and h2 carry the brand blue. h3 and h4 drop to ink at 600: colour and
   weight were both signalling hierarchy at every level, which read as loud. */
h3 { font-size: clamp(1.15rem, 1rem + 0.6vw, 1.4rem); color: var(--ink); font-weight: 400; }
h4 { font-size: 1.05rem; color: var(--ink); font-weight: 400; }

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

a { color: var(--blue-light); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--blue); }

strong { font-weight: 600; }
small { font-size: 0.8125rem; }

/* Two rings, not one, and the reason is measured. A single-colour focus ring cannot
   work on this site: against the paper background the old gold managed only 2.04:1,
   under the 3:1 a focus indicator needs, and plain blue is 6.72:1 on paper but
   1.00:1 on the brand blue — invisible on the hero, the CTA band and the footer.

   So: a blue outline sitting 2px out, and a white ring filling exactly that 2px gap.
   On light surfaces the blue is what you see (6.72:1). On blue and dark surfaces the
   blue outline disappears and the white ring is what you see (7.45:1 on the brand
   blue). Both states clear 3:1, which no single colour did. */
:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #fff;
  border-radius: 2px;
}

::selection { background: rgba(0, 91, 130, 0.16); color: var(--ink); }

/* The parent promise is set in capitals wherever it is quoted. Done in CSS, not
   in the markup, so screen readers still read it as a phrase and not as an
   initialism. */
/* Capitals barely tracked, as on 1qhealth.com. 0.06em read as spaced-out; at
   0.015em the words stay words. */
.caps { text-transform: uppercase; letter-spacing: 0.015em; }

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  background: var(--blue);
  color: #fff;
  padding: 0.75rem 1.25rem;
  font-weight: 600;
  transform: translateY(-120%);
  transition: transform 0.15s ease;
}
.skip-link:focus { transform: translateY(0); color: #fff; }

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

/* ---------- 3. Layout ---------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }
.section--paper { background: var(--paper); }
.section--white { background: var(--white); }
.section--blue { background: var(--blue); color: #fff; }
.section--blue h2, .section--blue h3, .section--blue h1 { color: #fff; }
.section--blue p { color: var(--blue-pale); }

.grid { display: grid; gap: 1.25rem; }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr)); }

/* Four across. Explicit breakpoints rather than auto-fit, because auto-fit
   would fall back to three columns on a 1024px screen and strand the fourth
   card on a row of its own — the exact thing this layout exists to avoid. */
.grid--4 { grid-template-columns: 1fr; }
@media (min-width: 40rem) { .grid--4 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .grid--4 { grid-template-columns: repeat(4, 1fr); gap: 1rem; } }

.split {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  grid-template-columns: 1fr;
}
@media (min-width: 62rem) {
  .split { grid-template-columns: 0.72fr 1fr; align-items: start; }
  .split--even { grid-template-columns: 1fr 1fr; align-items: center; }
  .split--wide-left { grid-template-columns: 1.15fr 0.85fr; align-items: center; }
}

.stack > * + * { margin-top: 1rem; }
.lede { font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem); line-height: 1.6; }

/* ---------- 4. The brand dot — bullet ---------- */
/* The 1QAcademy_Point asset replaces every generic bullet on the site.
   Version 4: the gold original (point.png/webp, #D9A404) is recoloured to the
   brand blue (point-blue.png/webp, #005B82) — at 0.29rem the gold dot reached
   only 2.04:1 against the paper background, the blue reaches 6.72:1. The gold
   files stay in assets/img/ untouched. */
.dot-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1.1em;
}
.dot-list > li {
  position: relative;
  padding-left: 1.6rem;
  margin-bottom: 0.6rem;
  max-width: 68ch;
}
.dot-list > li::before {
  content: "";
  position: absolute;
  left: 0;
  /* Down from 0.5rem in three steps: at high frequency the brand dot was
     reading as decoration. It stays the brand asset, just quieter. `top` moves
     with it so the dot still sits on the first line's x-height. */
  top: 0.66em;
  width: 0.29rem;
  height: 0.29rem;
  background-image: url("../img/point-blue.png");
  background-image: image-set(url("../img/point-blue.webp") type("image/webp") 1x,
                              url("../img/point-blue.png")  type("image/png")  1x);
  background-size: contain;
  background-repeat: no-repeat;
}
.dot-list--roomy > li { margin-bottom: 1rem; }
.dot-list--dark > li { color: var(--blue-pale); }

/* ---------- 4b. Eyebrow — three-dot family marker + Aptos label ----------
   Dots ascend small → large, matching the Academy mark: the blue 1QAcademy_Point
   is the last and largest. Dot sizes are in em so they track the label size. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-label);
  font-size: 0.8125rem;
  font-weight: 600;
  /* Capitals, tracked enough to stay legible at this size but well short of the
     0.15em it started at. The dots take the text colour rather than the brand
     blue, so the marker reads as part of the label. */
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 1.1rem;
  --eb-dot: var(--ink);
}
.eyebrow--dark { color: var(--blue-faint); --eb-dot: var(--blue-faint); }

/* The gold summit point is no longer used in the eyebrow anywhere, on light or
   dark ground: the marker is one colour throughout and takes --eb-dot. Only the
   ascending sizes carry the family reference now. */

/* Larger in the hero and on every page hero, per brand direction. */
.hero .eyebrow,
.page-hero .eyebrow {
  font-size: clamp(0.8125rem, 0.78rem + 0.2vw, 0.9375rem);
  letter-spacing: 0.11em;
  margin-bottom: 1.35rem;
}

/* Centred on the text beside them, not sitting on its baseline. The container
   used to be a fixed 0.95em tall with the dots bottom-aligned inside it, which
   hung the small ones low; now it is as tall as the largest dot and the three
   share one horizontal axis, which the flex line centres against the label.
   Measured against a label without descenders ("WHAT THIS IS"), the dots' centre
   and the ink band of the capitals coincide exactly, so no correction is needed.
   Note for anyone tempted to nudge this: under `align-items: center` a negative
   margin only moves the item by half its value, because the centring recomputes
   the free space. A -0.09em nudge shifted the dots by 0.5px. */
.eyebrow__dots {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.26em;
  height: auto;
}
.eyebrow__dots i {
  display: block;
  border-radius: 50%;
  background: var(--eb-dot);
}
/* Three steps smaller than the original 0.34 / 0.52 / 0.78em: at this frequency
   the marker only has to be recognisable, not prominent. */
.eyebrow__dots i:nth-child(1) { width: 0.21em; height: 0.21em; }
.eyebrow__dots i:nth-child(2) { width: 0.32em; height: 0.32em; }
/* All three dots carry --eb-dot: one colour throughout, on light and dark
   ground alike. The gold summit point is no longer used in the eyebrow — the
   ascending sizes carry the family reference on their own. */
.eyebrow__dots i:nth-child(3) {
  width: 0.48em;
  height: 0.48em;
  /* One colour, like the other two. */
  background: var(--eb-dot);
  background-image: none;
}

/* Build-up animation, same easing and rhythm as the hero Academy mark.
   Runs when the eyebrow scrolls into view; JS adds .is-in. Without JS, or with
   reduced motion, the dots are simply present. */
.eyebrow__dots.is-in i {
  animation: dotIn 0.55s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
.eyebrow__dots.is-in i:nth-child(1) { animation-delay: 0.05s; }
.eyebrow__dots.is-in i:nth-child(2) { animation-delay: 0.19s; }
.eyebrow__dots.is-in i:nth-child(3) {
  animation: dotIn 0.55s cubic-bezier(0.2, 0.8, 0.3, 1) 0.33s both;
}
@media (prefers-reduced-motion: reduce) {
  .eyebrow__dots.is-in i,
  .eyebrow__dots.is-in i:nth-child(3) { animation: none !important; opacity: 1; }
}

/* ---------- 4c. Emphasis ----------
   A gold textmarker stroke used to run through one sentence per section. It was
   the second-loudest gold surface on the page and read as a study aid rather
   than as a publication, so the <mark> elements were unwrapped in the markup.
   The rule below stays only as a guard: should a <mark> ever come back through
   a CMS or a paste, it renders as quiet emphasis instead of a yellow stripe. */
mark, .hl {
  background: transparent;
  color: inherit;
  font-weight: 600;
}

/* ---------- 5. Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  padding: 0.8rem 1.35rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.btn:hover { transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) { .btn:hover { transform: none; } }

/* The primary action used to be a filled gold block, repeated on every page in
   the header and again in the hero. Gold is the accent in the brand system, not
   a second primary, and as a large fill it was the loudest thing on the screen.
   It is now solid blue on light ground and solid white on the blue sections,
   where blue on blue would not read. The class name is kept so no markup had to
   change. */
.btn--primary { background: var(--blue); color: #fff; }
.btn--primary:hover { background: #002b3e; color: #fff; }
.section--blue .btn--primary, .hero .btn--primary,
.page-hero .btn--primary, .cta-band .btn--primary {
  background: #fff; color: var(--blue);
}
.section--blue .btn--primary:hover, .hero .btn--primary:hover,
.page-hero .btn--primary:hover, .cta-band .btn--primary:hover {
  background: var(--blue-pale); color: #003347;
}

.btn--blue { background: var(--blue); color: #fff; }
.btn--blue:hover { background: #002b3e; color: #fff; }

.btn--ghost-dark { border-color: rgba(255,255,255,0.4); color: #fff; background: transparent; }
.btn--ghost-dark:hover { background: rgba(255,255,255,0.1); color: #fff; }

.btn--ghost { border-color: var(--line-strong); color: var(--blue); background: transparent; }
.btn--ghost:hover { background: rgba(0, 91, 130,0.06); color: var(--blue); }

.btn-row { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2rem; }

/* ---------- 6. Header ---------- */
/* The bar carries a hint of the section's own colour — 5% of the accent already
   used on the active nav item, mixed into paper.

   The first attempt tinted it in steps of the primary blue, which was the wrong
   answer: the sections already have a colour scheme, on the current nav item, and
   blue steps both ignored it and broke it.

   **Why only 5%.** Those five accents are calibrated to land within a hundredth of
   5.03:1 on paper — somebody chose them carefully. Every step of tint eats into
   that, because the accent is measured against the header it now sits on:

     tint    Ingredients  Health Areas  Publications  Webinars  Concept Lab
      4%        4.76         4.71          4.80        4.79        4.74
      5%        4.70         4.64          4.75        4.71        4.68   <- set
      6%        4.65         4.57          4.71        4.64        4.63
      8%        4.51         4.41          4.57        4.52        4.51   <- Health Areas fails
     10%        4.40         4.28          4.44        4.38        4.37

   Health Areas is the binding one. 5% keeps every section above 4.6:1 with the grey
   nav items at 10.2:1 and the magnifier at 6:1. A stronger tint is possible, but it
   means strengthening the five accents first — and those are a brand decision, not
   a CSS one.

   The bar is opaque. It used to be 93% over the page artwork, but the panel's top
   ramp has to end in the header's colour, and a translucent ramp inside a
   translucent bar composites twice — the seam came back at 5 to 7 steps of 255.
   Measured, the artwork behind contributed a difference of 1 step across the five
   sections, so nothing is lost by making the bar solid. */
.page--bg-ingredients  { --header-bg: #E3EEED; }   /* 5% of #007842 */
.page--bg-health-indications { --header-bg: #EDE9EC; }   /* 5% of #CD1928 */
.page--bg-publications { --header-bg: #E9EDEA; }   /* 5% of #726900 */
.page--bg-webinars     { --header-bg: #ECEBEA; }   /* 5% of #B54200 */
.page--bg-concept-lab  { --header-bg: #E9ECF4; }   /* 5% of #764ED1 */

/* The bar is opaque. It used to be 93% over the page artwork, but the panel's top
   ramp has to end in the header's colour, and a translucent ramp inside a
   translucent bar composites twice — the seam came back at 5 to 7 steps of 255.
   Measured, the artwork behind contributed a difference of 1 step across the five
   sections, so nothing is lost by making the bar solid, and now the ramp can match
   it exactly. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--header-bg);
  border-bottom: 1px solid var(--line);
}
.site-header__inner {
  /* The header keeps a wider measure than the article column. The logo, five nav
     items with dropdowns, the search icon and the CTA need about 1126px; inside
     the 1080px content container they did not fit, and the only alternatives were
     forcing the mobile drawer onto 1280px-wide desktops or shrinking the nav type.
     A full-width bar over a narrower text column is a normal arrangement, and it
     keeps the drawer breakpoint where it was. */
  max-width: var(--container-wide);
  /* Positioning context for the logo panel layers on .brand. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 5.25rem;
}
/* The subpage override is now the same as the root value and is kept only so that a
   future change to one does not silently change the other. */
.page--inner { --logo-aspect: 1.981; }

/* ---------- Logo panel ----------
   At the top of the page the brand sits in a white panel that hangs below the
   header bar; on the first scroll it retracts and the logo lands where it sits
   today. Four decisions behind it:

   1. The default — no class on <html> — is the RETRACTED state, so without
      JavaScript, or before this stylesheet's script runs, the page shows the
      header it has always had. The panel is an enhancement, never a dependency.
   2. **The logo is positioned absolutely inside the brand link.** It has to be:
      left in the flex row, a 120 px logo simply pushed the header from 85 px to
      121 px, and everything below it moved. Out of flow, the header keeps its
      row and nothing can shift. Measured: layout shift 0.000 both ways.
   3. `.brand` stretches to the full row height, so the panel's `top: 0` is the
      header's own top edge without a magic number derived from the logo clamp.
   4. The logo grows by `height`, not by `transform: scale()`. The artwork is
      1137 × 574, so every size used here is a downscale and stays crisp —
      scaling a raster up would go soft mid-animation.

   Desktop only: on a phone a panel this size would take a third of the first
   screen. */
.brand {
  position: relative;
  flex: none;
  display: block;
  align-self: stretch;
  width: calc(clamp(2.9rem, 2.1rem + 1.9vw, 4.25rem) * var(--logo-aspect));
}
.brand img {
  position: absolute;
  left: 0;
  top: 50%;
  height: clamp(2.9rem, 2.1rem + 1.9vw, 4.25rem);
  /* The global reset sets `max-width: 100%`, which here means 100% of the brand
     link — and the link only reserves the retracted footprint. Left in place it
     squashed the expanded logo from its 1.98 aspect to 1.01. Out of flow it
     cannot break the layout, so the cap is not needed. */
  max-width: none;
  transform: translateY(-50%);
  /* Slower than a normal UI transition on purpose: this is the one deliberate
     movement on the page and it reads better unhurried. */
  transition: height 0.9s cubic-bezier(0.22, 0.7, 0.25, 1),
              transform 0.9s cubic-bezier(0.22, 0.7, 0.25, 1);
}

/* From 76rem, the same breakpoint as the side-by-side hero. Measured at 1100 px
   and below, reserving the panel's 301px in the header pushed the navigation,
   search and CTA past the edge — 61px of overflow at 1100, 161px at 992. */
@media (min-width: 76rem) {
  /* Reserves the *panel's* footprint rather than the small logo's, so the
     navigation begins to the right of the panel instead of sitting on it. Held in
     both states: a nav that slides sideways on scroll is a layout shift. */
  /* 7.5rem is the logo's panel height; 1.884 its aspect (the claim lockup —
     946 x 502; the previous artwork without the claim was 1.981). These used to say 9rem,
     left over from the first draft, which is why the panel had 16px of ground on
     the left of the logo and 63px on the right. */
  .brand { width: calc(7.5rem * var(--logo-aspect) + 1rem); }

  .brand::before {
    content: "";
    position: absolute;
    z-index: -1;
    /* Geometry is derived from the expanded logo, not from the brand link, whose
       width follows the small logo's clamp and would make the insets drift with
       the viewport. 7.5rem × 1.884 is the artwork at its panel size; 1rem of
       ground on each side. */
    left: -1rem;
    width: calc(7.5rem * var(--logo-aspect) + 2rem);
    /* A different answer to the pale line at the top, after overshooting alone did
       not settle it: rather than make the edge land exactly, make there be no edge
       to land.

       The panel starts half a rem above the viewport, and its background is a
       gradient that is *paper-coloured* at that point, deepening to the panel blue
       over the next 44px. So at the visible top edge the panel is the same colour
       as the header behind it — a stray hairline of paper cannot show against a
       colour it already matches — and the two blues run into one another instead
       of meeting at a line.

       Note the two must agree: the gradient's first stop is at 0.5rem because that
       is exactly how far the panel reaches above the viewport. With the earlier
       -6rem the whole ramp sat above the visible area and the edge stayed hard.
       The ramp is over by 52px, well clear of the logo at 100px. */
    top: -0.5rem;
    /* Deep enough for the lockup, which now carries "provided by 1Q Health" inside
       the artwork: the logo runs 74–194, and 26px of ground below it puts the panel
       edge at 220. Re-measure this if the 7.5rem logo height changes. */
    bottom: -8.5rem;
    background: linear-gradient(to bottom,
                var(--header-bg) 0.5rem,
                var(--brand-panel) 3.25rem);
    /* It dissolves rather than collapsing: opacity only, no scaling. The movement
       comes from the logo sliding up out of it. */
    opacity: 0;
    transition: opacity 1.1s ease;
    /* Without this the panel would extend the link's hit area by a 317 × 252
       rectangle over the hero, swallowing clicks even at zero opacity. */
    pointer-events: none;
  }
  .at-top .brand::before { opacity: 1; }

  /* ---------- The panel artwork (homepage only) ----------
     Luisa's "Logo-Hintergrund-Startseite" conic gradient, 780 x 890 in the
     source, delivered at 560px wide (WebP 4 KB, JPEG 19 KB) for a panel that is
     about 270 x 292 CSS px.

     Third artwork ("Logo-Hintergrund-Startseite-3"), delivered at 560px wide
     (WebP 2 KB, JPEG 8 KB). Re-measured on the bare image, with no white over it:

       claim "provided by 1Q Health"   4.73:1   (needs 4.5)
       wordmark + dots                 5.29:1   (needs 3)

       veil   claim    wordmark
         0%   4.73:1    5.29:1
        15%   4.95:1    5.60:1   <- kept
        25%   5.12:1    5.78:1
        35%   5.25:1    5.99:1

     It clears both lines bare, but the claim does so by a quarter of a point, and
     it is the smallest, lightest ink on the panel. The veil stays at the 15% the
     previous artwork used, which buys half a point on the claim and leaves the
     image essentially untouched.

     For the record, the second artwork ("…-2") measured as follows:

       "PROVIDED BY 1Q HEALTH" in --blue   1.73:1   (needs 4.5:1)
       the logo lockup itself                1.97:1   (3:1 is the bar for a graphic)

       claim "provided by 1Q Health"   4.74:1   (needs 4.5)
       wordmark + dots                 3.34:1   (needs 3)

     The first artwork put a dark turquoise corner directly under the claim and
     needed a 62% veil to clear the same line. This one is pale where the lockup
     sits, so the veil drops to 15% — enough to buy a real margin, not enough to
     wash the image out:

       veil   claim    wordmark   colour left (mean CIELAB chroma in the panel)
         0%   4.74:1    3.34:1        7.36
        15%   5.04:1    3.77:1        6.31   <- set
        35%   5.47:1    4.43:1        4.87
        62%   6.10:1    5.40:1        2.93   (what the old artwork needed)

     15% keeps 86% of the image's colour and puts half a point of headroom on the
     claim and three quarters of a point on the wordmark. The claim is the binding
     element either way: it is the smallest, lightest ink on the panel (#005F84).

     Layers, painting order from the top:
       1. paper -> transparent over the top 3.25rem, so the ramp on ::before still
          owns the top edge and no hairline can show against the header,
       2. the 35% white veil,
       3. the artwork, `cover`,
     with --brand-panel painted underneath on ::before, so a failed image request
     leaves the panel exactly as it was. */
  body:not(.page--inner) .brand::after {
    content: "";
    position: absolute;
    z-index: -1;
    left: -1rem;
    width: calc(7.5rem * var(--logo-aspect) + 2rem);
    top: -0.5rem;
    bottom: -8.5rem;
    background-image:
      linear-gradient(to bottom,
        var(--header-bg) 0.5rem,
        rgb(from var(--header-bg) r g b / 0) 3.25rem),
      linear-gradient(rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.15)),
      image-set(url("../img/panel-home.webp") type("image/webp") 1x,
                url("../img/panel-home.jpg")  type("image/jpeg") 1x);
    background-size: 100% 3.25rem, cover, cover;
    background-position: left top, center, center top;
    background-repeat: no-repeat, no-repeat;
    opacity: 0;
    transition: opacity 1.1s ease;
    pointer-events: none;
  }
  /* `at-top` is on <html>, the page class on <body>, so they cannot be chained. */
  .at-top body:not(.page--inner) .brand::after { opacity: 1; }

  /* ---------- The panel artwork on Ingredients ----------
     The artwork panel was a homepage-only thing: `body:not(.page--inner)`. Ingredients
     now has its own, from Logo-Hintergrund-Ingredients.jpg, so the rule is repeated for
     that page with the compact geometry the inner pages use — the panel there is a third
     smaller, so the image is told to cover a shorter box and the ramp along the top keeps
     the same 3.25rem it has everywhere.

     The veil is 40% here, not the homepage's 15%, and that is measured rather than
     copied. This artwork is a teal gradient and its darkest point under the wordmark is
     #8DB3A1; the brand blue on that reads 3.23:1 bare and 3.72:1 at the homepage's 15%.
     3:1 is the bar for a graphic, so it was not failing — but it was a long way under the
     4.5:1 this site holds its type to, on the one mark every page carries.

       veil   wordmark on the darkest point
         0%      3.23:1
        15%      3.72:1   (what the homepage uses)
        30%      4.25:1
        40%      4.25:1   (predicted 4.64 — the prediction was taken from the source
                          file, the measurement from the rendered panel, and the
                          rendered one is the one that counts)
        52%      4.71:1   <- set

     52% is where it clears 4.5 with something to spare against the *rendered* pixels.
     The image is paler than the file but still clearly this image.

     No animation here, like the other inner pages: the panel is simply there. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
      .page--bg-publications, .page--bg-webinars) .brand::after {
    content: "";
    position: absolute;
    z-index: -1;
    left: -1rem;
    width: calc(5.5rem * var(--logo-aspect) + 2rem);
    top: -0.5rem;
    bottom: -4.5rem;
    background-image:
      linear-gradient(to bottom,
        var(--header-bg) 0.5rem,
        rgb(from var(--header-bg) r g b / 0) 3.25rem),
      linear-gradient(rgba(255, 255, 255, var(--panel-veil, 0.52)), rgba(255, 255, 255, var(--panel-veil, 0.52))),
      var(--panel-art);
    background-size: 100% 3.25rem, cover, cover;
    background-position: left top, center, center top;
    background-repeat: no-repeat, no-repeat;
    opacity: 0;
    transition: none;
    pointer-events: none;
  }
  .page--bg-ingredients .brand::after {
    --panel-art: image-set(url("../img/panel-ingredients.webp") type("image/webp") 1x,
                           url("../img/panel-ingredients.jpg")  type("image/jpeg") 1x);
  }
  .page--bg-health-indications .brand::after {
    /* 60%, not the 52% the other two use. This artwork is the pinkest of the three and
       its darkest point under the wordmark left exactly 4.50:1 at 52% — the bar with
       nothing in hand. 60% puts a quarter of a point on it. */
    --panel-veil: 0.60;
    --panel-art: image-set(url("../img/panel-health-indications.webp") type("image/webp") 1x,
                           url("../img/panel-health-indications.jpg")  type("image/jpeg") 1x);
  }
  .page--bg-concept-lab .brand::after {
    --panel-art: image-set(url("../img/panel-concept-lab.webp") type("image/webp") 1x,
                           url("../img/panel-concept-lab.jpg")  type("image/jpeg") 1x);
  }
  .page--bg-webinars .brand::after {
    --panel-art: image-set(url("../img/panel-webinars.webp") type("image/webp") 1x,
                           url("../img/panel-webinars.jpg")  type("image/jpeg") 1x);
  }
  .page--bg-publications .brand::after {
    --panel-art: image-set(url("../img/panel-publications.webp") type("image/webp") 1x,
                           url("../img/panel-publications.jpg")  type("image/jpeg") 1x);
  }
  .at-top :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
      .page--bg-publications, .page--bg-webinars) .brand::after { opacity: 1; }

  /* The image is 7rem while the panel is still sized from 7.5rem, so the lockup keeps
     1.5rem of ground on each side instead of the 1rem the formula alone would give. The
     translate follows it down by the quarter it lost, so it stays where it sat in the
     panel. */
  .at-top .brand img {
    height: 7rem;
    /* Half the width the logo gave up, so it stays centred in a panel that is still
       sized from 7.5rem. Without it the image keeps its left edge and all the new
       space lands on the right: measured 16px left against 32px right. */
    transform: translateY(-50%) translateY(6rem) translateX(calc((7.5rem - 7rem) * var(--logo-aspect) / 2));
  }

  /* ---------- Compact panel on the inner pages ----------
     The homepage hero is built around the panel; a subpage's job is to get to its
     content, so the panel is about a third smaller there and the hero keeps more
     of its height. Every value is the homepage's scaled by 0.733 (5.5rem instead
     of 7.5rem), including the label, which stays exactly as wide as the logo. */
  .page--inner .brand { width: calc(5.5rem * var(--logo-aspect) + 1rem); }
  .page--inner .brand::before {
    width: calc(5.5rem * var(--logo-aspect) + 2rem);
    bottom: -4.5rem;
  }
  /* `at-top` is on <html> and `page--inner` on <body>, so they cannot be chained
     on one element — the descendant order is the whole selector. */
  .at-top .page--inner .brand img { height: 5.5rem; transform: translateY(-50%) translateY(3.2rem); }

  /* No animation on the subpages. Arriving on one of eight pages and watching the
     panel build itself each time is restless; the movement earns its place once,
     on the homepage. The panel is simply there, and it snaps away on scroll. */
  .page--inner .brand::before,
  .page--inner .brand img { transition: none; }

}
@media (prefers-reduced-motion: reduce) {
  .brand::before, .brand::after, .brand img { transition: none; }
}


/* The sentinel is 5rem tall and absolutely positioned at the very top, so the
   panel retracts once the page has scrolled past that. */
.top-sentinel {
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: 5rem;
  pointer-events: none;
}
/* The final lockup is 1137 × 574 — aspect 1.98 : 1, where the previous one was
   4.37 : 1. The words "1Q Academy" are 154 px tall in both files, so they now
   occupy 27% of the artwork's height instead of 57%. Rendered at the old header
   height the wordmark would be less than half its former size, so the logo is
   set taller here. Even so it does not fully recover: matching the old wordmark
   exactly would need 68–94 px of height and a header of roughly 140 px. */
.brand img { height: clamp(2.9rem, 2.1rem + 1.9vw, 4.25rem); width: auto; }

/* The row's free space is split between the two gaps that should have it: after the
   brand, and after the navigation. Two `margin-left: auto` in one flex row share the
   spare space equally, so "Ingredients" sits away from the logo panel instead of hard
   against it, and the search icon still ends up pinned to the button (see .header-search).

   Before this the search's single auto took all of it: the gap after the brand was stuck
   at the row's 16px while the gap before the search ran to 163px. */
.site-nav { margin-left: auto; }

.site-nav ul {
  display: flex;
  align-items: center;
  /* Minus 0.06rem against the old clamp. Reserving the bold width on every link (see
     the ::before rule further down) made the row a couple of pixels wider, and at 1024px
     the header has no slack at all — all ten inner pages grew a 2px horizontal scrollbar.
     Six gaps give it back with room over. */
  gap: clamp(0.9rem, 1.8vw - 0.06rem, 1.75rem);
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav > ul > li > .nav-row > a,
.site-nav > ul > li > a {
  font-size: 0.875rem;
  /* Neutral at rest. The row has been grey, then blue, then fully coloured; the
     coloured row was too much, so colour is back to marking one thing at a time — the
     item you are pointing at, or the page you are on. Both of those are colour, weight
     and an underline together, and the width for the bold state is reserved below so
     that nothing moves. */
  color: var(--ink);
  text-decoration: none;
  padding: 0.4rem 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  transition: background-color 0.14s ease, color 0.14s ease;
}
@media (prefers-reduced-motion: reduce) {
  .site-nav > ul > li > .nav-row > a,
  .site-nav > ul > li > a { transition: none; }
}
/* No underline on hover: the box is the hover mark now. The current page keeps its
   underline — that is state, not hover. */
.site-nav a[aria-current="page"] { color: var(--blue); font-weight: 600; border-bottom-color: var(--blue); }

/* ---------- 6a. The active category carries its own colour ----------
   Only the page you are on is coloured. At rest and on hover every item stays in
   ink and then the brand blue, exactly as before — the colour is a "you are here"
   mark, not decoration on all five labels at once.

   Each colour is the hue of that band's artwork, sampled as the circular mean of
   the most saturated tenth of its pixels. The artwork colours themselves cannot be
   used as text: they are pale by design, and the Publications yellow measures
   1.29:1 on white.

     category        hue    colour     on the header
     Ingredients     153°   #007842      5.03:1
     Health Areas    355°   #CD1928      5.03:1
     Publications     55°   #726900      5.07:1
     Webinars         22°   #B54200      5.06:1
     Concept Lab     258°   #764ED1      5.01:1

   These are brighter than the first attempt on purpose. That version held all five
   to one CIELAB chroma (46) so the row would read as a matched set — necessary when
   every label was coloured at once. With one colour on screen at a time there is no
   row to match, so each hue now runs as saturated as it can while still clearing
   5:1 against the header, capped at chroma 78 so the strongest hues stop short of
   neon:

     category        chroma before -> now    hue's ceiling at 5:1
     Health Areas         46  ->  77                87
     Concept Lab          46  ->  78               112
     Webinars             46  ->  70                70   (at its ceiling)
     Publications         46  ->  51                51   (at its ceiling)
     Ingredients          46  ->  47                47   (at its ceiling)

   So green, olive and orange are as bright as they can be — they are already at the
   hue's own limit, and the only way further is to break the 5:1 line. Publications
   stays olive for the same reason: a yellow dark enough to read as text is olive.

   The colour also takes the underline, so the rule under the label and the label
   agree. Attribute selectors on the five unique top-level hrefs, so neither
   build.py nor index.html had to change. */
/* Two variables per category, not one, and the reason is Publications.

   --cat is the colour as a *surface*: the hover box, the dropdown panel's tint.
   --cat-ink is the colour as *type*: the current page's label and underline, the
   chevron, the dropdown's own entries.

   Both selectors are doubled — `> .nav-row > a` and `> a` — because Webinars and
   Concept Lab lost their dropdowns and with them their .nav-row wrapper. The first
   version of this rule matched only the wrapped form, so those two fell back to the
   blue default and their boxes came out the same colour as Authors', which has no
   category at all. Nothing in the CSS looked wrong; the rendered pixels did.

   For four of the five they are the same value. Publications is the exception. Its
   artwork is a bright yellow-green — sampled from page-bg-publications.webp, the most
   common saturated colour in it is #D8D878 — and that yellow is a fine surface and
   an impossible typeface: measured, #DCDC80 on the dropdown panel reads 1.39:1 and
   on the header 1.33:1, against 4.5:1 for small text. There is no veil or weight
   that fixes that; a yellow light enough to be this yellow cannot carry text.

   #6E6E10 is that same hue (60°) taken down until it reads 5.2:1. It is, within a
   couple of points, the olive that was here before — which is worth saying plainly:
   the olive was never an arbitrary substitute for the yellow, it *is* the yellow,
   darkened until it is legible. The yellow is back where it can be seen, as the box
   and the panel. */
.site-nav > ul > li:has(> .nav-row > a[href^="ingredients.html"]),
.site-nav > ul > li:has(> a[href^="ingredients.html"]) { --cat: #007842; --cat-ink: #007842; }
.site-nav > ul > li:has(> .nav-row > a[href^="health-indications.html"]),
.site-nav > ul > li:has(> a[href^="health-indications.html"]) { --cat: #CD1928; --cat-ink: #CD1928; }
.site-nav > ul > li:has(> .nav-row > a[href^="publications.html"]),
.site-nav > ul > li:has(> a[href^="publications.html"]) { --cat: #DCDC80; --cat-ink: #676710; }
.site-nav > ul > li:has(> .nav-row > a[href^="webinars.html"]),
.site-nav > ul > li:has(> a[href^="webinars.html"]) { --cat: #F0926C; --cat-ink: #B54200; }
.site-nav > ul > li:has(> .nav-row > a[href^="concept-lab.html"]),
.site-nav > ul > li:has(> a[href^="concept-lab.html"]) { --cat: #764ED1; --cat-ink: #764ED1; }

/* Both forms again — Webinars and Concept Lab have no .nav-row. Without the second
   selector the generic aria-current rule won and the page you were on was the one
   item in the row still painted blue. */
.site-nav > ul > li > .nav-row > a[aria-current="page"],
.site-nav > ul > li > a[aria-current="page"] { color: var(--cat-ink, var(--blue)); border-bottom-color: var(--cat-ink, var(--blue)); }

/* The hover box. Solid, not a tint: brand blue on any of these five measures 1.34:1,
   so a blue label could not survive on top of one. The label goes white instead, which
   measures 5.55:1 to 5.62:1 across the five — the same five colours were calibrated to
   about 5:1 as text on the header, and they land in the same place as grounds for white.

   The current page keeps its coloured label and underline underneath; hovering it turns
   it into the box like any other, which is right — the underline is still there when the
   pointer leaves. */
/* Hover and current page are one treatment: the category's colour, semibold, and an
   underline in the same colour. No boxes — those are gone on request.

   The underline is the element's existing 2px bottom border rather than
   text-decoration, so it runs the full width of the label and sits exactly where the
   current page's rule has always sat; the two states line up instead of one being a
   text underline and the other a border.

   THE WIDTH IS RESERVED. Going from 400 to 600 makes a label wider, and in a flex row
   that shifts every item after it — the whole right-hand half of the header twitches as
   the pointer crosses it. Each link carries a hidden copy of its own text at the bold
   weight (::before, from data-label), which fixes its width to the widest state it will
   ever take. */
.site-nav > ul > li > .nav-row > a::before,
.site-nav > ul > li > a::before {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 600;
  pointer-events: none;
}
.site-nav > ul > li > .nav-row > a:hover,
.site-nav > ul > li > a:hover,
.site-nav > ul > li > .nav-row > a[aria-current="page"],
.site-nav > ul > li > a[aria-current="page"] {
  color: var(--cat-ink, var(--blue));
  font-weight: 600;
  border-bottom-color: var(--cat-ink, var(--blue));
}
/* The chevron thickens with the label it belongs to. */
.has-menu:hover > .nav-row > .submenu-toggle svg,
.has-menu:focus-within > .nav-row > .submenu-toggle svg,
.has-menu.is-open > .nav-row > .submenu-toggle svg,
.nav-row:has(a[aria-current="page"]) .submenu-toggle svg { stroke-width: 2.6; }

/* ---------- 6b. Dropdown menus ---------- */
.nav-row { display: flex; align-items: center; gap: 0.25rem; }
.submenu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.submenu-toggle svg { width: 0.75rem; height: 0.75rem; transition: transform 0.18s ease; }
/* The chevron sits beside the label rather than inside the hover box — it is a control
   of its own and moving it into the box would have meant restructuring .nav-row. It takes
   the category colour instead, so it belongs to the box without being in it. The five
   colours were calibrated to about 5:1 as ink on this header, so the mark is as legible
   as the label was. */
.has-menu:hover > .nav-row > .submenu-toggle,
.has-menu:focus-within > .nav-row > .submenu-toggle,
.has-menu.is-open > .nav-row > .submenu-toggle { color: var(--cat, var(--blue)); }

.has-menu:hover .submenu-toggle svg,
.has-menu.is-open .submenu-toggle svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .submenu-toggle svg { transition: none; } }

@media (min-width: 62rem) {
  .site-nav > ul > li.has-menu { position: relative; }
  /* Bridges the gap between the nav item and the panel so the pointer can
     travel down without the menu closing underneath it. */
  .has-menu::after {
    content: "";
    position: absolute;
    inset: 100% 0 auto 0;
    height: 1.15rem;
    display: none;
  }
  .has-menu:hover::after, .has-menu:focus-within::after { display: block; }

  .submenu {
    position: absolute;
    top: calc(100% + 1.1rem);
    left: -1rem;
    z-index: 50;
    min-width: 15rem;
    /* The panel carries its category's colour at 10%, and the rule along its top
       carries it solid. 10% measured: the menu's own ink reads 10.7:1 to 11.0:1 on it
       and the blue "all …" link 6.3:1 to 6.5:1, so the tint is well clear of costing
       anything. The flat fallback is there for anything without color-mix(). */
    background: var(--white);
    background: color-mix(in srgb, var(--cat, var(--blue)) 10%, #fff);
    border: 1px solid var(--line);
    border-top: 3px solid var(--cat, var(--blue));
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 0.6rem;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s;
  }
  .has-menu:hover > .submenu,
  .has-menu:focus-within > .submenu,
  .has-menu.is-open > .submenu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }
  @media (prefers-reduced-motion: reduce) {
    .submenu { transition: none; transform: none; }
  }
  .submenu ul { display: block; }
  /* Nine long health-area labels would make a single column absurdly tall. */
  .submenu--wide { min-width: 34rem; }
  .submenu--wide ul { display: grid; grid-template-columns: 1fr 1fr; gap: 0 0.5rem; }
  /* Keep the last item ("All …") on its own full-width row. */
  .submenu--wide ul li:last-child { grid-column: 1 / -1; }
}

/* display:block must be unconditional — the nav's base rule sets the list to
   flex-row, which the drawer would otherwise inherit and lay the submenu out
   sideways, pushing the page into horizontal scroll. */
.submenu ul { display: block; list-style: none; margin: 0; padding: 0; gap: 0; }

/* …and because that rule sits after the desktop block and carries equal
   specificity, the two-column health-area panel has to be re-asserted here or
   `display: block` silently wins and the panel collapses to one column.
   Note: getComputedStyle still reports the track list when display is block,
   so verify this by measuring rendered positions, not the computed value. */
@media (min-width: 62rem) {
  .submenu.submenu--wide ul {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 0.5rem;
  }
  .submenu.submenu--wide ul li:last-child { grid-column: 1 / -1; }
}
.submenu li { border: 0; }
.submenu a {
  display: block;
  font-size: 0.875rem;
  line-height: 1.35;
  /* The entries take their category's colour, at full strength — --cat-ink, not
     --cat, so the Publications panel reads in its dark yellow rather than in a
     bright yellow nobody can read on it. Measured on each panel's own 10% tint:
     4.72:1 to 5.17:1, all clear of AA. */
  color: var(--cat-ink, var(--ink));
  text-decoration: none;
  padding: 0.5rem 0.65rem;
  border-radius: 2px;
  border-bottom: 0;
}
/* On a panel that is already tinted, a 6% blue wash no longer registered. The hover
   is the same category colour again, at 22% over the panel's 10%. */
.submenu a:hover, .submenu a:focus-visible {
  background: rgba(0, 91, 130, 0.06);
  background: color-mix(in srgb, var(--cat, var(--blue)) 22%, transparent);
  color: var(--cat-ink, var(--blue));
  border-radius: 0;
}
/* The final entry is the catch-all link back to the whole category. */
.submenu li:last-child a {
  margin-top: 0.35rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--line);
  border-radius: 0;
  font-weight: 600;
  color: var(--cat-ink, var(--blue));
}

/* Search sits to the left of the primary action: a quiet icon, same optical
   size as the button's cap height, linking to the publications library search. */
.header-search {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  /* Pinned to the button. The header row is `justify-content: space-between`, which was
     making this gap float: measured from the icon's ink to the button's edge it ran from
     15px at 992 to 62px at 1280, because whatever space the row has spare gets
     distributed across all three gaps, and the amount spare depends on the page (the
     subpages' logo panel is narrower than the homepage's, so their header has more).

     A negative margin could not fix that — it only shifts the average. `margin-left:
     auto` can: it absorbs all the free space to the left of the icon, so the row has
     none left to distribute and the icon-to-button distance becomes a constant. It is
     then set by the row's 1rem gap plus this margin: 20px box to box, 32px ink to the
     button's edge, at every width. */
  margin-left: auto;
  margin-right: 0.25rem;
  border-radius: var(--radius);
  color: var(--blue);
  text-decoration: none;
}
.header-search svg { width: 1.15rem; height: 1.15rem; }
.header-search:hover { background: rgba(0, 91, 130, 0.08); color: var(--blue); }

.header-cta { flex: none; }

.nav-toggle {
  display: none;
  flex: none;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--blue);
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.55rem 0.8rem;
  cursor: pointer;
  min-height: 2.75rem;
}
.nav-toggle svg { width: 1rem; height: 1rem; }
.nav-toggle[hidden] { display: none !important; }

@media (max-width: 61.99rem) {
  .nav-toggle { display: inline-flex; }
  .header-cta, .header-search { display: none; }
  .site-nav {
    margin-left: 0;
    position: absolute;
    inset: 100% 0 auto 0;
    background: var(--white);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-md);
    padding: 1rem var(--gutter) 1.5rem;
  }
  .site-nav[hidden] { display: none; }
  .site-nav > ul { flex-direction: column; align-items: stretch; gap: 0; }
  .site-nav > ul > li { border-bottom: 1px solid var(--line); }
  .site-nav a { display: block; padding: 0.85rem 0; font-size: 1rem; border-bottom: none; }
  .site-nav .btn { margin-top: 1.1rem; width: 100%; }

  /* In the drawer the panel becomes an indented, collapsible list rather than
     an overlay — an absolutely positioned dropdown is unusable at this width. */
  .nav-row { justify-content: space-between; }
  .nav-row > a { flex: 1; }
  .submenu-toggle {
    width: 2.75rem;
    height: 2.75rem;
    margin-right: -0.5rem;
    color: var(--blue);
  }
  .submenu-toggle svg { width: 0.9rem; height: 0.9rem; }
  .submenu { display: none; padding: 0 0 0.6rem 0.9rem; }
  .has-menu.is-open > .submenu { display: block; }
  .submenu a { padding: 0.6rem 0; font-size: 0.9375rem; color: var(--blue-light); }
  .submenu li:last-child a { margin-top: 0.3rem; padding-top: 0.7rem; }
}
@media (min-width: 62rem) {
  .site-nav [data-mobile-only] { display: none; }
}

/* ---------- 7. Hero ---------- */
.hero {
  position: relative;
  background: var(--blue);
  color: #fff;
  overflow: hidden;
  isolation: isolate;
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
/* A little more of the photograph than before (0.28): the parent brand leads
   with a full photographic hero, and a barely-there image read as texture
   rather than as a place. Still low enough that white type clears AA — the
   gradient below does the real work. */
/* `height: 100%` on the image resolves against its parent, and that parent is
   the <picture>, whose own height is auto — so the percentage fell back to auto
   and the image stopped short of the hero's full height once it no longer
   spanned the whole width. Giving <picture> a definite height fixes it. */
.hero__media picture { display: block; width: 100%; height: 100%; }
/* 40% rather than centre: the scientist's head sat low in the crop and the top of the
   frame was ceiling. Raising the crop puts her eyes roughly on the heading's line. */
.hero__media img { width: 100%; height: 100%; object-fit: cover; object-position: 60% 40%; opacity: 0.55; }
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg, var(--blue) 20%, rgba(0, 91, 130, 0.9) 58%, rgba(0, 91, 130, 0.8) 100%);
}
.hero__inner {
  display: grid;
  gap: clamp(2.5rem, 5vw, 3.5rem);
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
  align-items: center;
}
@media (min-width: 62rem) {
  .hero__inner { grid-template-columns: 1.15fr 0.85fr; }
}
/* ---------- Split hero ----------
   Text and photograph no longer overlap. However pale the image is, type on a
   photograph is harder to read than type on paper — that is about the busy
   detail behind the letters, not only about contrast, and the measured values
   were already above AA when it still read as difficult. So the picture takes
   the right half of the viewport at full strength, with no overlay, and the
   text sits on paper beside it. Below 62rem the image becomes a plain band
   above the text instead. */
@media (min-width: 62rem) {
  .hero--light .hero__inner { grid-template-columns: minmax(0, 34rem); }
}
/* The side-by-side layout only from 75rem. Measured at 1024 px it does not fit:
   the 34rem text column plus the gutter ends at 584 px while the picture would
   start at 492 px, so the two overlapped by 92 px and the text was back on the
   photograph. At 75rem/52% they still touched by 8 px, hence 76rem and 50%: at
   1216 px there are 24 px of paper between them. Below that the picture is a
   band above the text. */
@media (min-width: 76rem) {
  /* The picture is wider than the half it shows and its left edge is masked to
     transparent, so it dissolves into the paper instead of ending on a hard
     vertical seam. The mask is fully opaque from 34% of the media's width, which
     at 1440 px is 350 px to the right of where the text column ends — the fade
     never reaches the type. */
  .hero--light .hero__media {
    inset: 0 0 0 auto;
    width: 58%;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.35) 16%, #000 34%);
            mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.35) 16%, #000 34%);
  }
}
@media (max-width: 75.99rem) {
  .hero--light .hero__media { position: static; inset: auto; width: auto; }
  .hero--light .hero__media img { aspect-ratio: 16 / 9; height: auto; }
}
.hero h1 { color: #fff; max-width: 15ch; }
.hero p { color: var(--blue-pale); }
.hero__mark { display: none; justify-self: center; }


/* The bridge to the parent promise. Set apart by a hairline rather than by
   colour, so it reads as a statement of position and not as a slogan. */
/* One line on flat paper, between the hero and the metrics. */
/* ---------- The "Work with us" one-liner ---------- */
/* The line under the five teasers ("Need something built around your own product
   line? Custom workshops and masterclasses →") is a single sentence with the link
   at its end — broken over two lines the question and the answer drift apart, so
   on desktop it sits on one line.

   It is not forced with `white-space: nowrap`: at 19px the sentence measures
   1000px, the content column is 1080px, so it fits on one line by itself from a
   ~62rem viewport upward. Below that the 62ch cap comes back and it wraps
   normally — nowrap there would push a horizontal scrollbar onto the page
   (at 768px the sentence needs 811px against 768px available). */
.one-line-cta { max-width: 62ch; margin: 0; }
@media (min-width: 62rem) {
  .one-line-cta { max-width: none; }
}

.promise-band {
  background: var(--white);
  border-block: 1px solid var(--line);
}
.promise-band p {
  margin: 0;
  padding-block: clamp(1.1rem, 2.4vw, 1.6rem);
  max-width: none;
  /* Athiti SemiBold in the 1Q Health blue, and on one line. The size is tied to
     the viewport rather than fixed, because the sentence has to fit the width
     without breaking — at 1400 px it needs about 19 px, and below the split
     breakpoint it is allowed to wrap rather than shrink into illegibility. */
  font-family: var(--font-quote);
  font-weight: 600;
  font-size: clamp(1rem, 0.35rem + 1.32vw, 1.22rem);
  line-height: 1.45;
  color: var(--blue);
}
/* One line, filling the content width exactly.

   The size is derived, not guessed. Measured with a Range over the text: at 100px
   the sentence is 6141px wide, so it is 61.41em. Dividing the available content
   width by that gives the size that fills it — 21.5px at the capped container,
   against the 19.5px it sat at before, which left it 112px short.

   `min(var(--container), 100vw …)` mirrors the container's own max-width — and it
   reads the variable rather than repeating the number. It used to be the literal
   `87.5rem`, which silently meant "1400px"; when --container was narrowed to
   1080px the sentence kept sizing itself for the old width and pushed 62px of
   horizontal overflow onto the landing page. Anything derived from the content
   width belongs on the variable, not on a copy of its value.

   The 1.1rem deduction covers a classic scrollbar: below the capped width `100vw`
   is wider than the content box, and without it the line would overshoot and push
   the page sideways. Above the cap the `min()` picks --container and the deduction
   is inert.

   The divisor is 62.05, re-measured at the new width: the sentence actually sets to
   61.84em in the rendered face, not the 61.41em originally recorded, so the old
   figure overshot the box by 7px. Erring high by a hair is the safe direction —
   `text-align-last: justify` cannot stretch a `nowrap` line, so a line that is a
   little short leaves a small gap while a line that is a little long pushes the
   page sideways. If the display face is ever changed, this number has to be
   re-measured: it is a property of the type, not of the layout.

   Below 80rem the sentence wraps instead: forcing one line all the way down would
   mean shrinking it into illegibility. */
@media (min-width: 80rem) {
  /* :not() — the band gained a second paragraph above this one, and `white-space: nowrap`
     on a sentence that is not the promise line sends it straight off the right edge of
     the page. This apparatus belongs to the promise line alone: it is the thing that is
     one line, stretched to the content width, at a size derived from its own length. */
  .promise-band p:not(.promise-band__lead) {
    white-space: nowrap;
    font-size: calc((min(var(--container), 100vw - 1.1rem) - 2 * var(--gutter)) / 62.05);
    text-align: justify;
    text-align-last: justify;
  }
}
.promise-band strong { color: var(--blue); font-weight: 600; }

.promise {
  margin: 1.75rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid rgba(255,255,255,0.22);
  max-width: 52ch;
  font-size: 1.0625rem;
}
.hero .promise { color: #fff; }
.hero .promise strong { font-weight: 600; }

/* ---------- Light hero ----------
   The supplied hero image already carries the 1Q wash, so it runs at full
   strength with no gradient over it. That inverts the type: on a near-white
   photograph the white headline of the dark hero would be invisible, so every
   piece of text here takes the ink and blue it would have on paper. */
.hero--light { background: var(--paper); color: var(--ink); }
.hero--light .hero__media img { opacity: 1; object-position: 62% center; }
.hero--light::after { content: none; }
.hero--light h1 { color: var(--blue); }
/* `.hero--light p` used to catch the eyebrow and the promise line as well —
   both are paragraphs — and quietly dropped them to the muted tone. On the pale
   photograph that put the eyebrow at 4.11:1, under AA. The exclusions keep the
   broad rule useful without it reaching into components that set their own
   colour. The `.lede` exclusion is the same trap one step later: the `:not()`
   version outranked `.hero--light .lede`, so the lede fell back to the muted
   tone and measured 4.54:1 instead of the intended ~9:1. */
.hero--light p:not(.eyebrow):not(.promise):not(.lede) { color: var(--ink-soft); }
.hero--light .eyebrow { color: var(--ink); }
.hero--light .dot-list > li { color: var(--ink); }
.hero--light .promise {
  color: var(--ink);
  border-top-color: var(--line-strong);
}
/* Readability of the left block, in order of effect:
   1. The lede takes the full ink rather than the muted tone. On the pale
      photograph that lifts it from 4.60:1 — the tightest value on the page — to
      roughly 8:1, and it is the single biggest change here.
   2. Slightly larger and a little more leading than the standard lede, because
      it sits on a picture rather than on flat paper.
   3. The bullets get more air between them, so each one reads as its own claim.
   Deliberately NOT done: a white scrim behind the text. It would raise contrast
   further, but it is an overlay, and the brief was to keep the image clear. */
.hero--light .lede {
  color: var(--ink);
  /* The same clamp as the opening band's first sentence. The two are one paragraph
     that was split across the fold — the hero could not hold all three sentences —
     so they have to set at one size or the split becomes visible as a step down.
     Any change to one of these two values belongs on both. */
  font-size: clamp(1.125rem, 0.9rem + 0.55vw, 1.375rem);
  line-height: 1.55;
  max-width: 34ch;
}
/* 20ch, so the headline breaks after "science," as in the approved mockup —
   two lines instead of three. */
.hero--light h1 { max-width: 20ch; }
/* Only four things left in the hero, so it does not need the full height. */
.hero--light .hero__inner { padding-block: clamp(3rem, 6.5vw, 5.5rem); }

.hero--light .promise strong { color: var(--blue); }
.hero--light .btn--primary { background: var(--blue); color: #fff; }
.hero--light .btn--primary:hover { background: #002b3e; color: #fff; }

@media (min-width: 62rem) { .hero__mark { display: block; } }

/* Animated academy mark */
.academy-mark circle { opacity: 0; animation: dotIn 0.6s cubic-bezier(0.2, 0.8, 0.3, 1) forwards; }
.academy-mark circle:nth-child(1) { animation-delay: 0.55s; }
.academy-mark circle:nth-child(2) { animation-delay: 0.42s; }
.academy-mark circle:nth-child(3) { animation-delay: 0.30s; }
.academy-mark circle:nth-child(4) { animation-delay: 0.18s; }
.academy-mark circle:nth-child(5) { animation-delay: 0.06s; }
/* The build-up stays — it is the one piece of motion on the page that carries
   meaning. The endless glow that used to sit on the gold dot is gone: over a
   photograph it read as a lens flare. */
.academy-mark .mark-gold { animation: dotIn 0.6s cubic-bezier(0.2,0.8,0.3,1) 0.55s forwards; }
@keyframes dotIn {
  from { opacity: 0; transform: translateY(6px) scale(0.6); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .academy-mark circle, .academy-mark .mark-gold { animation: none !important; opacity: 1; }
}

/* Page hero (inner pages) */
.page-hero { background: var(--blue); color: #fff; padding-block: clamp(2.75rem, 6vw, 4.5rem); }
.page-hero h1 { color: #fff; margin-bottom: 0.5rem; }
/* --blue-pale is 5.12:1 on the flat brand blue, but these heroes are not flat: the
   dot mesh lightens the blue under the text to #1B6C8F at its brightest, and
   --blue-pale on that is 4.02:1 — under AA. Measured on all four page heroes, so it
   predates the Publications photograph rather than being caused by it. One step
   lighter fixes it: #DBE6EC reads 4.61:1 on the mesh's brightest point and 5.87:1
   on the plain blue. */
.page-hero p { color: #DBE6EC; max-width: 62ch; }

/* ---------- A figure in the left column of a split section ----------
   The "What this is" section is a two-column split: a short heading on the left, all
   the prose on the right. The left column was two lines of type and then a column of
   air the height of the paragraph beside it. The figure fills it.

   The second artwork is a circle drawn on white. Measured, the disc spans 2960 of the
   file's 3024px with a 32px white margin on three sides and 72px at the bottom, so the
   asset is cropped to the disc's own bounding box (2960 x 2960) and shown square with
   `border-radius: 50%`. That clips the last of the white away instead of leaving a pale
   square on the paper background — the graphic *is* a circle, so the frame should be
   one too. No `object-fit` crop is involved: a square source at 1/1 is untouched.

   The first artwork was a 3:4 portrait and was shown at 5/6 with `object-fit: cover`,
   because at the column's width the untouched portrait ran about 640px tall and
   out-measured the text beside it. That rule stays for any future non-square figure.

   Only from 60rem: below that the split collapses to one column and the figure would
   push the prose a full screen down, so it is not rendered at all. `display: none` on
   an <img> that has already been chosen by srcset still costs the download, which is
   why `sizes` says 100vw below the breakpoint and the element is hidden rather than
   the source removed — the browser then picks the 900w file, which it may reuse for
   nothing. That is the one wasted request in this arrangement and it is 63 KB. */
.section-figure { margin: 1.75rem 0 0; }
.section-figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 6;
  object-fit: cover;
  object-position: center;
  border-radius: var(--radius);
}
.section-figure--round img {
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  /* 18rem = 288px rendered at 1440. Four sizes were put side by side: at 26rem (395px)
     the disc filled the column edge to edge and took the section over; at 15rem (240px)
     it read as an afterthought. 18rem sits as a figure next to the heading without
     competing with the prose in the other column — column heights 434px against the
     text's 661px, where 26rem gave 541px.

     Left-aligned, not centred: at this size the disc is narrower than the heading above
     it, so a shared left edge with the eyebrow and the h2 reads tidier than a disc
     floating in the middle of the column. */
  max-width: 18rem;
}
.section-figure--round { display: flex; justify-content: flex-start; }
@media (max-width: 59.99rem) {
  .section-figure { display: none; }
}

/* ---------- 8. Proof strip ---------- */
.proof {
  background: var(--white);
  border-bottom: 1px solid var(--line);
}
.proof__grid {
  display: grid;
  /* Three metrics since "Ingredients sold by us: 0" came out. auto-fit still
     handles the reflow: 3 across on desktop, 2 + 1 in the middle, 1 per row on
     a phone. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: 1.5rem 2rem;
  padding-block: 2.25rem;
  /* Figure centred over its label rather than both flush left. Because the
     number is a block filling the whole cell, the digits grow outward from the
     centre while counting up — the label underneath never moves. */
  text-align: center;
}
.proof__num {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 1.3rem + 1.4vw, 2.25rem);
  /* One cut lighter with the headings, so the metrics stay in the same voice. */
  font-weight: 600;
  /* Lining, equal-width figures: numbers in a row should align on the digit,
     not drift. */
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--blue);
  line-height: 1;
  display: block;
}
.proof__label {
  font-family: var(--font-label);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: 0.6rem;
  display: block;
}

/* ---------- 9. Cards ---------- */
.card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.35rem, 2.5vw, 1.75rem);
  text-decoration: none;
  color: inherit;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
  height: 100%;
}
a.card:hover, .card--link:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--line-strong);
}
@media (prefers-reduced-motion: reduce) { a.card:hover { transform: none; } }

.card h3 { margin: 0.5rem 0 0.55rem; }
.card p { font-size: 0.9375rem; color: var(--ink-soft); margin-bottom: 0; }
.card__tag {
  font-family: var(--font-label);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ink-soft);
}
.card__more {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--blue-light);
  margin-top: auto;
  padding-top: 1.1rem;
}
.card--dark {
  background: rgba(255,255,255,0.05);
  border-color: var(--line-dark);
}
.card--dark h3 { color: #fff; }
.card--dark p { color: var(--blue-pale); }
.card--dark .card__tag { color: var(--blue-pale); }
.card--dark .card__more { color: var(--blue-pale); }
.card--dark .card__more a { color: #fff; }

/* ---------- 10. Evidence / article entries ---------- */
.entry {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.4rem, 2.5vw, 1.85rem);
  height: 100%;
  display: flex;
  flex-direction: column;
}
/* Tag and grade always stack, so cards line up whatever the label length. */
.entry__head { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; }
.entry h3 { margin: 0.45rem 0 0.15rem; }

/* ---------- Authors ----------
   The three entries used to be .card — bordered boxes with a hover lift. The lift
   was borrowed from the linked category tiles and an author entry is not a link, so
   it promised something it could not deliver; the border made each author look like
   a product tile on a page that is supposed to read as a masthead. Both are gone.
   What is left is a portrait, the text beside it, and the two things the page now
   makes its case with: what the author works on, and how much of our own library
   they are on. */
.author { padding: 2rem 0; border-bottom: 1px solid var(--line); }
.author:first-child { padding-top: 0; }
.author:last-child { border-bottom: 0; }
@media (min-width: 62rem) { .author .split { align-items: center; } }

.author-card__label {
  font-family: var(--font-label);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 1.1rem 0 0.5rem;
}
.author-card__chips { display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none; margin: 0; padding: 0; }
.author-card__chips li {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  padding: 0.2rem 0.55rem;
}
.author-card__links { display: flex; flex-wrap: wrap; align-items: baseline; gap: 1.1rem; margin-top: 1.1rem; }
/* The count is the loudest thing in the entry on purpose: it is the evidence. */
.author-card__stat { font-size: 1rem; font-weight: 600; }
.author-card__stat strong { font-size: 1.35rem; }
.author-card__stat--pending {
  color: var(--ink-soft);
  font-weight: 600;
  font-size: 0.9375rem;
  font-style: italic;
}
.author-card__profile { font-size: 0.875rem; }

/* ---------- Category icon and name on an ingredient card ----------
   The six illustrations the client supplied, one per ingredient class, so the class is
   legible before the label is read. Icon and name share a line; the claim-readiness badge
   keeps the line below to itself.

   64px, up from 44. At 44 the six were identifiable but read as decoration; at 64 the
   class is what the eye lands on first, which is the point of having them. The name went
   up with the icon — 1.0625rem against the 0.8125rem a card tag carries elsewhere — so
   that the pair reads as one heading rather than a picture with a caption.

   1.15rem between that line and the badge, against the 0.5rem the head used before. Two
   different things are being said, and at 0.5rem with the icon this size they ran together.

   The illustrations are translucent watercolour with no flat edge, so they need no frame,
   no tint and no background — and they must not get one. A circle or a tinted plate behind
   them would put a hard shape around artwork whose whole character is that it has none.

   An earlier version put icon, name and badge in one row with the labels stacked beside
   the icon. That took 66px off the badge's width and "CLAIM READINESS: MODERATE" — tracked
   capitals — wrapped to two lines on every card. The badge is a fixed phrase and reads as
   one. */
.entry__head { gap: 1.15rem; }
.entry__cat { display: flex; align-items: center; gap: 0.85rem; }
.entry__icon { flex: none; width: 64px; height: 64px; object-fit: contain; }
.entry__cat .card__tag { font-size: 1.0625rem; font-weight: 600; letter-spacing: 0; }

/* ---------- One colour per class ----------
   Each class has a single-hue icon, so the colour is the icon's own: the peak hue of
   its saturated pixels, which in this set runs between 54% and 87% of each icon's
   colour mass.

   Botanicals is the exception, twice over. It was not in the September redraw, so it
   is still the multi-coloured sprig from the first set — and its pink sat 20 degrees
   from the new Proteins pink, which is what made the two indistinguishable. The green
   below is therefore NOT taken from its icon: it is a placeholder, chosen because it
   is the largest gap in the set (45 deg to 205 deg) and because green is the obvious
   hue for a plant. Until Botanicals is redrawn, its card is green and its icon is
   pink. That mismatch is the honest state of things and it is meant to be visible.

   Two values per class:
     --icon-tint  the colour at full strength, laid over the whole card at 18%,
     the text colour, that hue taken down until the class name reads 5.4:1. */
.entry { --icon-tint: var(--blue); }
.entry.cat--botanicals      { --icon-tint: #2E9B57; }   /* placeholder, see above */
.entry.cat--vitamins        { --icon-tint: #F2AA44; }
.entry.cat--minerals        { --icon-tint: #44AAF2; }
.entry.cat--proteins        { --icon-tint: #F244AA; }
.entry.cat--nootropics      { --icon-tint: #AA44F2; }
.entry.cat--other-nutrients { --icon-tint: #F2C744; }

/* The whole card carries the colour, not a box around the icon. 18%: measured on the
   card's own body copy, which is the lightest ink on it, every class lands between
   5.27:1 and 6.16:1 — comfortably clear of AA and light enough that six of these in a
   grid read as a set rather than as six different products. */
/* Only a card that names a class carries the colour. The rule used to be on `.entry`
   itself, and `.entry { --icon-tint: var(--blue) }` above is the fallback for a card
   with no class on it — so the nine health-indication cards, which have no class,
   silently took the fallback and came out pale blue. They are white now; the tint
   belongs to the ingredient classes, where it means something. */
.entry[class*="cat--"] { background: color-mix(in srgb, var(--icon-tint) 18%, #fff); }

/* Measured against the *tinted card*, not against white. The first set of these was
   derived on a white ground and then the card went 18% coloured underneath them —
   Proteins fell to 4.34:1, under AA, and Minerals and Vitamins were inside a fifth of
   a point of it. Each is re-derived here on the ground it actually sits on. */
.entry.cat--botanicals      .card__tag { color: #186C42; }  /* 150 deg  5.26:1 on its card */
.entry.cat--vitamins        .card__tag { color: #875B1E; }  /*  35 deg  5.26:1 */
.entry.cat--minerals        .card__tag { color: #216697; }  /* 205 deg  5.25:1 */
.entry.cat--proteins        .card__tag { color: #AA2573; }  /* 325 deg  5.21:1 */
.entry.cat--nootropics      .card__tag { color: #852BC5; }  /* 275 deg  5.24:1 */
.entry.cat--other-nutrients .card__tag { color: #7C641B; }  /*  45 deg  5.21:1 */

/* No box behind the icon any more — the card is the box. */
.entry__cat picture { flex: none; display: block; line-height: 0; }
.entry__icon { flex: none; width: 64px; height: 64px; object-fit: contain; }
.entry__cat .card__tag { font-size: 1.0625rem; font-weight: 600; letter-spacing: 0; }

/* ---------- One colour per class ----------
   Each class now has a single-hue icon, so the colour is simply the icon's own:
   the peak hue of its saturated pixels, which in this set runs between 54% and 87%
   of each icon's colour mass — these are not the mixed palette the first set was.

   Two values per class, the same split the navigation uses:
     --icon-tint  the icon's colour at full strength, used at 30% behind the icon,
     the text colour, that hue taken down until the class name reads 5.4:1 on white.

   Botanicals is the exception and it shows: it was not in the September redraw, so
   it is still the multi-coloured sprig from the first set, and its pink now sits
   20 degrees from the new Proteins pink. Both of those are worth fixing with a
   redraw rather than with a colour picked here. */
.entry__cat { --icon-tint: var(--blue); }
.entry__cat.cat--botanicals      { --icon-tint: #C42F54; }
.entry__cat.cat--vitamins        { --icon-tint: #F2AA44; }
.entry__cat.cat--minerals        { --icon-tint: #44AAF2; }
.entry__cat.cat--proteins        { --icon-tint: #F244AA; }
.entry__cat.cat--nootropics      { --icon-tint: #AA44F2; }
.entry__cat.cat--other-nutrients { --icon-tint: #F2C744; }

.entry__cat.cat--botanicals      .card__tag { color: #C42F54; }  /* 345 deg  5.41:1 */
.entry__cat.cat--vitamins        .card__tag { color: #8F601F; }  /*  35 deg  5.44:1 */
.entry__cat.cat--minerals        .card__tag { color: #246FA4; }  /* 205 deg  5.41:1 */
.entry__cat.cat--proteins        .card__tag { color: #BF2A81; }  /* 325 deg  5.44:1 */
.entry__cat.cat--nootropics      .card__tag { color: #7A28B5; }  /* 275 deg  6.60:1 */
.entry__cat.cat--other-nutrients .card__tag { color: #6B5717; }  /*  45 deg  7.10:1 */

/* The box the icon sits in: its own colour, flat, at 30%. Square, like the
   navigation's hover box. The padding is what stops the artwork touching the
   edges — these are drawings that end in a soft edge, and a soft edge cropped by
   a hard one looks like a mistake. */

.entry__latin { font-style: italic; color: var(--ink-soft); font-size: 0.875rem; margin: 0 0 0.75rem; }
.entry p { font-size: 0.9375rem; color: var(--ink-soft); }
.entry__refs {
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: 0.78rem;
  color: var(--ink-soft);
  line-height: 1.5;
}
.entry__refs ol { margin: 0; padding-left: 1.1rem; }
.entry__refs li + li { margin-top: 0.35rem; }
.entry__refs a { color: var(--blue-light); }

/* Evidence grade chip */
.grade {
  display: inline-flex;
  align-items: center;
  /* `wrap` matters on small phones: the longest label, "Lead evidence: Saffron
     and Rhodiola", is wider than a 360 px viewport and pushed the page sideways
     while the pill was still one unbroken line. */
  flex-wrap: wrap;
  max-width: 100%;
  gap: 0.4rem;
  font-family: var(--font-label);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.3rem 0.6rem;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  color: var(--blue);
  background: transparent;
}
/* Unfilled: the three tinted badges read as a traffic light. The level is now
   carried by the border and the text alone, which is enough — the label spells
   the level out in words anyway. */
.grade--strong { border-color: var(--blue); color: var(--blue); }
/* The scale has three steps and they must stay apart. Strong keeps the full blue,
   moderate steps down to the lighter blue, emerging stays grey. The strong/moderate
   difference is now a lightness step within one hue rather than blue against gold,
   which is subtler than it was — a filled chip for strong would separate them more
   firmly if that is wanted. */
.grade--moderate { border-color: rgba(0, 111, 158, 0.45); color: var(--blue-light); }
.grade--emerging { border-color: var(--line-strong); color: var(--ink-soft); }

/* ---------- 11. Filter pills ---------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 2rem;
  padding: 0;
  border: 0;
}
.filters legend { float: left; width: 100%; }
.pill {
  font-family: var(--font-label);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.5rem 0.95rem;
  min-height: 2.25rem;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--blue-light);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.pill:hover { background: rgba(0, 91, 130, 0.07); }
.pill[aria-pressed="true"] { background: var(--blue); border-color: var(--blue); color: #fff; }

.filter-status { font-size: 0.8125rem; color: var(--ink-soft); margin-bottom: 1.25rem; }

/* ---------- 12. Steps ---------- */
.steps { counter-reset: step; list-style: none; padding: 0; margin: 0; display: grid; gap: 1.25rem; }
@media (min-width: 48rem) { .steps { grid-template-columns: repeat(4, 1fr); } }
.steps li {
  counter-increment: step;
  border-top: 2px solid var(--blue);
  padding-top: 1rem;
}
.steps li::before {
  content: "0" counter(step);
  display: block;
  font-family: var(--font-label);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--blue);
  margin-bottom: 0.5rem;
}
.steps h3 { font-size: 1.05rem; margin-bottom: 0.4rem; }
.steps p { font-size: 0.9rem; color: var(--ink-soft); }
.section--blue .steps h3 { color: #fff; }
.section--blue .steps li::before { color: var(--blue-pale); }
.section--blue .steps p { color: var(--blue-pale); }

/* ---------- 13. Service table (Concept Lab) ---------- */
.service {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 2vw, 1.6rem);
  height: 100%;
}
.service h3 { margin-bottom: 0.85rem; }

/* Grid stretch already equalises the card heights; this makes the *content*
   line up too — the timeframe is pinned to the bottom of every card, so the
   four boxes read as one row instead of four ragged columns. */
.service dl {
  display: flex;
  flex-direction: column;
  flex: 1;
  margin: 0;
  font-size: 0.875rem;
}
.service dl > div + div { margin-top: 0.85rem; }
.service__foot { margin-top: auto; padding-top: 0.95rem; }
.service dt {
  font-family: var(--font-label);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.service dd { margin: 0.2rem 0 0; color: var(--ink); }
.service .card__more { padding-top: 0.9rem; margin-top: 0; }

/* ---------- 14. Publications table ---------- */
.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); }
table.pubs { width: 100%; border-collapse: collapse; font-size: 0.875rem; min-width: 44rem; }
table.pubs caption { text-align: left; padding: 1rem 1.1rem; color: var(--ink-soft); font-size: 0.8125rem; }
table.pubs th, table.pubs td { text-align: left; padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--line); vertical-align: top; }
table.pubs thead th {
  font-family: var(--font-label);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--paper);
  border-bottom: 1px solid var(--line-strong);
  position: sticky;
  top: 0;
}
table.pubs tbody tr:last-child td { border-bottom: 0; }
table.pubs tbody tr:hover { background: rgba(0, 91, 130, 0.03); }
.pub-type {
  /* Set like a journal would set it: body face, roman, normal weight, sentence
     case. Bold tracked capitals inside a citation shouted over the author and
     the journal name, which are the parts a specialist reads first. */
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-soft);
  /* No `white-space: nowrap` here. It was meant to hold short labels like
     "RCT, n=382" on one line, but the longest study descriptors run to 376 px
     ("Open-label human phenotyping study, n=14") and pushed the page sideways
     on phones and narrow tablets. Short labels do not wrap anyway when there
     is room, so the rule only ever cost something. */
  overflow-wrap: break-word;
}

/* ---------- Section teasers on the landing page ----------
   Five full-bleed bands, one per area, stacked rather than side by side. Each
   carries its own horizontal gradient supplied by the client, so scrolling
   through the block changes the background band by band.

   THE LIGHTENING IS 90% WHITE, AND THAT NUMBER IS MEASURED, NOT CHOSEN.
   The brief was dark text on the banner, lightened where needed. Every one of the
   five banners runs from near-white to a mid teal, so text sitting directly on one
   has no fixed contrast ratio: measured across the banners, #005B82 ranges from
   1.47:1 to 7.36:1 and white from 1.01:1 to 5.08:1. Neither works alone.

   Sweeping the required white opacity for the darkest pixel under the text column
   of each banner, against 4.5:1 for every text colour used in a teaser:

     Concept Lab   90%      Health Areas  88%
     Ingredients   89%      Publications  88%
     Webinars      90%

   So 0.90 it is, and "lightened where needed" turns out to mean very nearly
   opaque. At that value the worst ratio anywhere under the wash is 4.54:1
   (blue on Concept Lab); body copy sits at 11:1 and headings at 6.5:1.

   What keeps it from reading as a card is the shape of the wash rather than its
   strength: it holds full opacity across the text column and then fades out, with
   no border and no corner radius, so it reads as the banner being lightened on the
   left. The right-hand third of every band shows the gradient at full strength,
   and the 10% that survives under the wash tints the text area in the section's
   own colour.
   ------------------------------------------------------------------------- */
.teasers { display: block; }

/* The heading section above ("Five ways into the evidence") sits on the paper
   colour and used to butt straight into the first band's artwork — a hard
   horizontal edge right under the h2. The artwork of the first band now fades in
   from nothing over its top 8rem, so the colour arrives instead of starting.

   Only the first band: between two bands an edge is correct, that is where one
   category ends and the next begins. The wash carries the same mask so its own
   top edge cannot show through while a band is hovered.

   `mask-image` is a progressive enhancement — without it the edge is simply as
   hard as it was before, nothing breaks. */
@supports ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))) {
  .teasers > .teaser:first-child::before,
  .teasers > .teaser:first-child::after {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 8rem);
            mask-image: linear-gradient(to bottom, transparent 0, #000 8rem);
  }
}

.teaser {
  position: relative;
  /* Neutral underneath. The artwork lives on ::before and is faded in and out by
     scroll position, so a band that is not the one being read shows this. */
  background: var(--paper);
  view-timeline: --teaser-band;
}

/* Layer 1 — the artwork. Faded in as the band approaches the middle of the screen
   and out again as it leaves, so only one band is in colour at a time. */
.teaser::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Layer 2 — the wash that makes dark text safe on the artwork. Above the artwork,
   below the content. Kept at full strength even while the artwork is faded out: it
   is a white wash over the paper colour there, which is invisible, so there is
   nothing to gain from animating it and one less thing to keep in sync. */
.teaser::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    to right,
    #fff 0,
    #fff 58%,
    rgba(255, 255, 255, 0) 88%
  );
  /* Off at rest. With the artwork at 35% the gradients are light enough on their
     own — every text element clears AA on the bare artwork: eyebrow 7.7–8.5:1,
     heading 4.7–5.4:1, body 8.3–9.9:1, and the link (now --blue) 6.0:1 or better.
     So nothing white sits behind the text while the band is resting.

     It fades in with the colour, because at 100% the same measurements collapse:
     without any wash the worst band drops to 2.1:1. The strength is 0.5, against
     a measured worst-case requirement of 0.41 (Concept Lab, the link) — margin
     enough for the JPEG artwork to differ slightly from the sampled pixels.
     Cross-fade timing matches the artwork's 450ms so the two move as one. */
  opacity: 0;
  transition: opacity 450ms ease;
}

.teaser > .container { position: relative; z-index: 2; }

.teaser__body {
  max-width: 34rem;   /* Stays inside the 58% where the wash is at full strength. */
  padding-block: clamp(2.75rem, 6vw, 4.5rem);
}
.teaser__body h3 {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.1rem + 1.1vw, 2rem);
  font-weight: 600;
  color: var(--blue);
  margin: 0 0 0.6rem;
  line-height: 1.15;
}
.teaser__body p {
  margin: 0 0 1.1rem;
  color: var(--ink);
  font-size: 1.0625rem;
  max-width: 46ch;
}
/* The eyebrow inside a teaser is a <p>, so the rule above was winning over
   .eyebrow on specificity (one class + one type beats one class): the five
   category labels were rendering at body size, 17px with 1.7px tracking, and
   capped at 46ch. On four of them the label is short enough that nothing showed.
   On Ingredients — "Botanicals · Vitamins · Minerals · Nootropics · Proteins" —
   it needed 702px inside a 544px column and broke over two lines.

   At the eyebrow's own size the same label measures 537px, so it fits the 34rem
   column on one line with 7px to spare, and the five labels now match every
   other eyebrow on the site. No `white-space: nowrap`: if a fallback font ever
   measures wider it wraps rather than running out over the artwork. */
.teaser__body .eyebrow {
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  max-width: none;
}
.teaser__more {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 0.9375rem;
  /* --blue, not --blue-light. Measured against every one of the five artworks at
     full colour, this link was the single reason the white wash had to be as
     heavy as it was: at --blue-light the worst band (Concept Lab) needed 74% white
     behind the text, at --blue it needs 41%. One lightness step on one link buys
     back a third of the artwork. */
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.teaser a.teaser__link { text-decoration: none; }

/* ---------- Faint at rest, full colour on hover ----------
   Every band shows its own colour immediately, at 15%. Pointing at a band brings
   that band to 100%. This replaces the scroll-driven handover that was here before.

   IT IS OPACITY, NOT `filter: saturate()`. The brief said "15% saturation", and
   `saturate(0.15)` is the literal reading — but it drains the colour while keeping
   the image's brightness, so these pastel gradients turn a muddy grey-blue rather
   than a pale tint of their own colour. Opacity blends the artwork into the paper
   background instead, which is what "faint version of this colour" looks like. Say
   the word and it is one line to switch.

   WHY THE SCROLL ANIMATION IS STILL HERE, FOR TOUCH ONLY. A touch device has no
   hover, so with hover alone every band on a phone would sit at 15% forever and the
   full colour would be unreachable. Under `(hover: none)` the scroll-driven handover
   therefore stays — it is already built and measured, and it gives the same
   one-at-a-time reading without needing a pointer. Under `(hover: hover)` the scroll
   animation is off and hover is in charge; the two never run at once.

   Keyboard users get it too, via `:focus-visible` on the band's link — the whole
   teaser is wrapped in one anchor, so tabbing to it lights the band exactly as
   pointing at it does. Without `:has()` support only that part is lost, not the hover.
   ------------------------------------------------------------------------- */
.teaser::before {
  /* 35% at rest, and the figure is measured rather than picked. Sampling the
     coloured half of all five bands and converting to CIELAB:

       rest   chroma of rest   dE rest -> hover
        15%        3.7              32.6
        22%        5.6              30.0
        30%        7.8              27.0
        35%        9.0              25.3
        42%       11.2              22.5
        50%       13.2              19.6

     Chroma under about 6 reads as grey rather than as a pale version of a colour,
     which is why the first attempt at 15% looked like nothing — 3.7 is not a faint
     colour, it is no colour.

     Now at 30%, one step back from the 35% that was here before. Re-measured on
     the rendered bands: 35% -> chroma 7.0, 30% -> 6.1, 28% -> 5.8, 25% -> 5.3,
     22% -> 4.8. 30% is the floor: it is the last value that still sits above the
     ~6 threshold, so the bands stay recognisably their own colour. Anything below
     drifts back towards grey, which was the complaint about 15%.

     The hover jump is not the constraint: dE stays above 19 everywhere in that
     table, and about 10 is already "obviously different". At 35% it is 25, so the
     hovered band reads as a step change rather than a nudge. 42% is the next stop
     if more presence is wanted; past that, five saturated bands stacked start
     competing with each other and with the blue hero.

     Contrast never binds here — the artwork at 100% already clears AA, so any rest
     value does too. */
  opacity: 0.30;
  transition: opacity 450ms ease;
}

@media (hover: hover) {
  .teaser:hover::before,
  .teaser:has(.teaser__link:focus-visible)::before {
    opacity: 1;
  }
  .teaser:hover::after,
  .teaser:has(.teaser__link:focus-visible)::after {
    opacity: 0.5;
  }
}

/* Touch: no pointer, so the scroll handover stays in charge. The window arithmetic
   and the measurements behind these keyframes are documented in the git history of
   this file — 26pp wide, shifted 3pp late to compensate `scroll-padding-top`. */
@keyframes teaser-art {
  0%,  40%  { opacity: 0; }
  48%, 58%  { opacity: 1; }
  66%, 100% { opacity: 0; }
}

/* The wash on the same 26pp window as teaser-art, capped at 0.5 rather than 1. */
@keyframes teaser-wash {
  0%,  40%  { opacity: 0; }
  48%, 58%  { opacity: 0.5; }
  66%, 100% { opacity: 0; }
}

@media (hover: none) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @media (prefers-reduced-motion: no-preference) {
      .teaser::before {
        animation: teaser-art auto linear both;
        /* After the shorthand — the shorthand resets animation-timeline. */
        animation-timeline: --teaser-band;
      }
      /* Same curve for the wash, so it arrives and leaves with the colour rather
         than sitting there while the band is pale. */
      .teaser::after {
        animation: teaser-wash auto linear both;
        animation-timeline: --teaser-band;
      }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No cross-fade; the colour change is instant. The two states stay. */
  .teaser::before { transition: none; }
}

/* Below this width the band is nearly all text column, so a wash that fades out
   at 88% would leave the last words of a line on the raw gradient. It goes
   uniform instead — and one step stronger, because at this size the text sits
   over the full height of the banner rather than only its lighter middle. */
@media (max-width: 62rem) {
  /* The body loses its cap here and the text runs the full width of the band, so
     the wash has to be flat rather than a gradient. Strength is still carried by
     `opacity`, so it is off at rest exactly as on a wide screen. Raised to 0.62
     because the text now reaches into the darker right-hand end of the artwork. */
  .teaser::after { background: #fff; }
  .teaser:hover::after,
  .teaser:has(.teaser__link:focus-visible)::after { opacity: 0.62; }
  .teaser__body { max-width: none; }
}

@media print {
  .teaser::before { background-image: none !important; }
  .teaser::after { display: none; }
}

/* Per-band artwork. WebP with a JPEG fallback, and a 960px variant for phones —
   the same pairing as the page backgrounds below. */
.teaser--ingredients::before  { background-image: url("../img/band-ingredients.webp?v=3"); }
.teaser--health-indications::before { background-image: url("../img/band-health-indications.webp?v=3"); }
.teaser--publications::before { background-image: url("../img/band-publications.webp?v=3"); }
.teaser--webinars::before     { background-image: url("../img/band-webinars.webp?v=3"); }
.teaser--concept-lab::before  { background-image: url("../img/band-concept-lab.webp?v=3"); }
@media (max-width: 48rem) {
  .teaser--ingredients::before  { background-image: url("../img/band-ingredients-960.webp?v=3"); }
  .teaser--health-indications::before { background-image: url("../img/band-health-indications-960.webp?v=3"); }
  .teaser--publications::before { background-image: url("../img/band-publications-960.webp?v=3"); }
  .teaser--webinars::before     { background-image: url("../img/band-webinars-960.webp?v=3"); }
  .teaser--concept-lab::before  { background-image: url("../img/band-concept-lab-960.webp?v=3"); }
}
@supports not (background-image: url("data:image/webp;base64,UklGRhIAAABXRUJQVlA4TAYAAAAvAAAAAAfQ//73v/+BiOh/AAA=")) {
  .teaser--ingredients::before  { background-image: url("../img/band-ingredients.jpg?v=3"); }
  .teaser--health-indications::before { background-image: url("../img/band-health-indications.jpg?v=3"); }
  .teaser--publications::before { background-image: url("../img/band-publications.jpg?v=3"); }
  .teaser--webinars::before     { background-image: url("../img/band-webinars.jpg?v=3"); }
  .teaser--concept-lab::before  { background-image: url("../img/band-concept-lab.jpg?v=3"); }
}

/* ---------- Cache busting for the artwork ----------
   Every background URL carries `?v=2`. The colour assignment was changed on 7 Aug by
   swapping the *contents* of the files and leaving the names alone, which is exactly
   the case a browser cache gets wrong: same URL, new bytes, so the old image keeps
   being served. It cost a round of "the colours are still wrong" when the files on
   disk were already correct.

   BUMP THIS NUMBER whenever an artwork file is replaced without renaming it. It is
   the only thing standing between a re-export and a visitor who keeps seeing last
   week's colours. Search for `?v=` — it appears on all thirty background URLs. */

/* ---------- Per-page background artwork ----------
   One gradient per area, supplied by the client, set on the <body> of that area's
   page. `page--bg` carries the mechanics, `page--bg-<area>` carries the image.

   Set as the body background, not as a layer behind the content. An earlier build
   used `::before { position: fixed; z-index: -1 }`, which renders in Chromium but
   only because `html` has no background of its own, so `body`'s paper colour
   propagates to the canvas and paints below the negative-z-index layer. Give `html`
   a background at any point and the artwork silently disappears. As the body
   background it propagates to the canvas itself and nothing can occlude it.

   HOW THE ARTWORK IS ALLOWED TO SHOW, AND WHY IT IS NOT BEHIND THE TEXT.
   All five artworks contain a dark teal corner around #5D97AD. A uniform paper
   veil over the image, strong enough to hold 4.5:1 for every text colour used on
   these pages, was measured at 97% — which makes the artwork invisible, so that
   route is closed. Text therefore never sits on the artwork. Instead:

   - Publications: the list section goes fully transparent, because its content is
     already opaque cards and a controls panel. The artwork shows around them.
   - The other four: their content sections become centred sheets on the paper
     colour, so the artwork frames them down both sides and shows between sections.
     Every ratio on those pages is the same fixed number it was before.

   `background-attachment: fixed` keeps the content gliding over the artwork; it
   drops to `scroll` below 48rem because iOS Safari has never handled a fixed
   attachment properly. `background-color` stays underneath for a failed load. */
.page--bg {
  background-color: var(--paper);
  background-size: cover;
  background-position: right top;
  background-repeat: no-repeat;
  background-attachment: fixed;
}
@media (max-width: 48rem) {
  .page--bg { background-attachment: scroll; }
}

.page--bg-ingredients  { background-image: url("../img/page-bg-ingredients.webp?v=2"); }
.page--bg-health-indications { background-image: url("../img/page-bg-health-indications.webp?v=2"); }
.page--bg-publications { background-image: url("../img/page-bg-publications.webp?v=2"); }
.page--bg-webinars     { background-image: url("../img/page-bg-webinars.webp?v=2"); }
.page--bg-concept-lab  { background-image: url("../img/page-bg-concept-lab.webp?v=2"); }
@media (max-width: 48rem) {
  .page--bg-ingredients  { background-image: url("../img/page-bg-ingredients-960.webp?v=2"); }
  .page--bg-health-indications { background-image: url("../img/page-bg-health-indications-960.webp?v=2"); }
  .page--bg-publications { background-image: url("../img/page-bg-publications-960.webp?v=2"); }
  .page--bg-webinars     { background-image: url("../img/page-bg-webinars-960.webp?v=2"); }
  .page--bg-concept-lab  { background-image: url("../img/page-bg-concept-lab-960.webp?v=2"); }
}
@supports not (background-image: url("data:image/webp;base64,UklGRhIAAABXRUJQVlA4TAYAAAAvAAAAAAfQ//73v/+BiOh/AAA=")) {
  .page--bg-ingredients  { background-image: url("../img/page-bg-ingredients.jpg?v=2"); }
  .page--bg-health-indications { background-image: url("../img/page-bg-health-indications.jpg?v=2"); }
  .page--bg-publications { background-image: url("../img/page-bg-publications.jpg?v=2"); }
  .page--bg-webinars     { background-image: url("../img/page-bg-webinars.jpg?v=2"); }
  .page--bg-concept-lab  { background-image: url("../img/page-bg-concept-lab.jpg?v=2"); }
}


/* ==========================================================================
   Photographic page heroes  (Concept Lab, Ingredients, Health indications,
                              Publications, Webinars)
   ==========================================================================
   Five pages carry a photograph instead of the blue block, and they carry it the same
   way, so the apparatus lives here once and each page supplies only its own picture
   through --hero-photo. Publications also has a photograph, but a different
   arrangement — there the picture is a panel on the right of type that keeps its own
   ground — so it stays where it is rather than being forced into this rule.

   THE ARRANGEMENT. The hero has no coloured ground of its own: the photograph is the
   ground, full-bleed, and the type sits on it in brand blue. Dark type on a light
   picture is the readable direction for both of these images, which are bright studio
   shots — mean luminance 200 to 240 of 255, measured column by column.

   THE WASH. "Bright almost everywhere" is not "bright everywhere", and both pictures go
   dark exactly where the type is. Health Areas has out-of-focus foliage in the left-hand
   corners; set straight onto it the lede measured 1.07:1. Webinars has a potted plant and
   a stack of books from 34% of the frame, down to a pixel value of 34. What makes dark
   type possible without bringing a coloured block back is a white wash over the left half:
   86% out to 46% of the width, eased to nothing by 56%. It costs little, because the part
   of each frame it covers is the empty room the photographer left for exactly this, and
   it is gone before the subject.

   The wash is a layer of the same element as the photograph rather than a separate
   overlay, so the two dissolve together at the bottom instead of the wash outliving the
   picture it is washing.

   THE LEDE IS 25rem, down from 62ch. That is what keeps the type clear of the subject:
   it now ends between 43% and 45% of the hero at every width from 1024 to 1920, and the
   wash is still at full strength there.

   THE BOTTOM DISSOLVES. The picture is at full strength to 70% of the hero's height and
   gone by 100%, so the hero melts into the page's own artwork instead of ending on a
   ruled line the way the other heroes do. That is right for a picture and wrong for a
   flat colour block, which is why the other heroes keep their edge. Each hero takes 12rem
   of bottom padding to give the dissolve room and the section below gives the same amount
   back, so nothing moved.

   NO HORIZONTAL MASK, and that is a correction. An earlier version faded the picture in
   over the first 14% so it would meet the page's artwork softly at the left edge — but a
   mask applies to the white wash too, and at 1024px the breadcrumb starts at 3.9% of the
   hero, on a wash nine tenths transparent. It measured 4.22:1, under AA. The picture is
   full-bleed, so there is no left edge to soften and nothing to mask horizontally.

   mask-image needs its own element, and .mesh-host::before is taken by the dot mesh, so
   this is ::after. .mesh-host > * already lifts the content to z-index 1, and .mesh-host
   clips to its own box, so the picture cannot bleed past the hero.

   ONLY FROM 64rem. Below it the container is fluid, the type spreads across the part of
   the frame that is dark, and no arrangement keeps it readable — so a narrow screen keeps
   the plain blue block with white type. The picture is a CSS background inside this media
   query, so narrow screens never request the file. */
.page--bg-health-indications .page-hero {
  /* 1800 x 780, not the supplied 1280 x 720, and the room was added in two rounds.

     `cover` scales the picture to whichever of the hero's two dimensions binds, and that
     scale cannot be lowered while the frame is smaller than the hero — a shorter hero does
     not help, it only swaps which dimension binds. So the frame grew instead.

     First round: 320px on the left, which took the drawing scale at 1440 from 1.125x to
     0.9x. Second round, after the picture still read as too close beside the other three
     heroes: 520px on the left in total, and 60px below. The bottom strip is what makes the
     difference — at 1440 the height term was 596/720 = 0.83 and would have become the
     binding one, so widening alone could not have gone past it. 60px of extra frame moves
     that floor to 0.76 and lets the width term win at 0.80.

     The left strip is built by averaging the leftmost 24px of each row and continuing it
     outwards; the bottom strip the same way from the last 14 rows, and it lands inside the
     hero's dissolve, where the mask has already faded it out. Neither carries detail anyone
     could check against the real photograph, but a wider original would still be better. */
  --hero-photo: image-set(url("../img/hero-health-indications.webp") type("image/webp") 1x,
                          url("../img/hero-health-indications.jpg")  type("image/jpeg") 1x);
  --hero-photo-pos: center 42%;
}
.page--bg-publications .page-hero {
  /* The Publications hero used to be the one page with its own photograph apparatus:
     a blue panel under the type, the picture masked in beside it, and the type in
     --footer-bg rather than white. It is folded into the shared rule here, so all five
     photographic heroes are now one set of rules. The old block is gone; its two useful
     findings live on in the shared comment above. */
  --hero-photo: image-set(url("../img/hero-publications.webp") type("image/webp") 1x,
                          url("../img/hero-publications.jpg")  type("image/jpeg") 1x);
  /* 24%, and it is derived rather than judged. The only person in this frame walks the
     upper gallery; located in the file, she occupies y 127 to 213 of the 1800 x 880
     frame. `cover` crops vertically, and how much it crops depends on the viewport:

       viewport   scale   visible source band at 24%
       1024px     0.569   y   30 – 786
       1440px     0.800   y   67 – 667
       1920px     1.067   y  103 – 553

     1920 is the binding case — it crops the most — and 24% is the largest value that
     still clears her head there, with about 24px to spare. At 45%, where this sat, the
     band at 1920 started at y 193 and cut her off at the knees. */
  --hero-photo-pos: center 24%;
}
.page--bg-concept-lab .page-hero {
  /* 1900 x 1000, and this one needed room in both directions. The other three heroes are
     shallow, so `cover` sizes their pictures by width. This hero is not: its h1 is a
     five-line sentence, which makes it 842px tall against roughly 600 elsewhere, and
     842/720 is 1.17 — height was binding, so the photograph was being *enlarged* by a
     sixth rather than reduced. Widening alone could not fix that; the source had to grow
     taller as well. 620px added on the left, 100 on top and 180 below, each built by
     continuing the edge rows and columns outwards. At 1440 the picture is now drawn at
     0.84x instead of 1.17x — the scene is a little under a third smaller.

     If the headline is ever shortened this file can go back to being merely wide. */
  --hero-photo: image-set(url("../img/hero-concept-lab.webp") type("image/webp") 1x,
                          url("../img/hero-concept-lab.jpg")  type("image/jpeg") 1x);
  --hero-photo-pos: center 50%;
}
.page--bg-ingredients .page-hero {
  /* 1800 x 780, in two rounds like Health Areas — see "Zooming out" in README.md. The
     left edge of this frame is clean white marble (mean 244 of 255, spread 4), so that
     continuation is seamless rather than merely hidden; the 60px below continues the
     bench and lands inside the hero's dissolve. The extra height is what lets the picture
     go past 0.83x: without it the height term 596/720 would have been the binding one. */
  --hero-photo: image-set(url("../img/hero-ingredients.webp") type("image/webp") 1x,
                          url("../img/hero-ingredients.jpg")  type("image/jpeg") 1x);
  /* 65%, the lowest of the three: everything in this frame — the dishes, the roots, the
     capsules in the foreground — sits in the bottom two thirds, and the top third is the
     empty bright room. `cover` has to take 245px out of a 16:9 frame in a hero this
     shallow, and at 42% it was taking them off the bottom, cutting the front capsules. */
  --hero-photo-pos: center 65%;
}
.page--bg-webinars .page-hero {
  /* 1760 x 790, widened like the other three. This was the last one still working from the
     supplied 1280 x 720, and at 1440 that meant the picture was drawn at 1.12x — enlarged,
     while its neighbours were at 0.80 to 0.90. 480px added on the left and 70px below; the
     extra height matters because the height term was 627/720 = 0.87 and would otherwise
     have become the binding one. Now 0.82x at 1440. The left strip continues a plain
     blue-grey wall (mean 223/238/243, spread under 9) so it is exact; the bottom strip
     continues the desk and lands inside the hero's dissolve. */
  --hero-photo: image-set(url("../img/hero-webinars.webp") type("image/webp") 1x,
                          url("../img/hero-webinars.jpg")  type("image/jpeg") 1x);
  /* 38%: the presenter's head sits near the top of this frame, so the crop is taken off
     the bottom rather than shared. */
  --hero-photo-pos: center 38%;
}

@media (min-width: 64rem) {
  /* One selector for all three pages, via :is(). An earlier version listed the three page
     classes out longhand in every rule — eight lists, each needing a new entry whenever a
     page joins. Adding Ingredients to seven of the eight and missing the one that sets the
     type colour left its h1 and lede white on a white photograph: invisible, and invisible
     in a way no automated contrast check catches, because as far as the DOM is concerned
     the text is still there and still has a colour. The lists are gone. :is() takes the
     specificity of its heaviest argument and these are all single classes, so nothing else
     in the cascade moved. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero {
    background: none;
    /* 8rem, down from 12. The extra 4rem was dissolve room for the photograph, but with
       the hero's text ending well above it the left-hand half read as an empty page —
       248px between the last line of the hero and the first line under it. At 8rem the
       dissolve still runs 190px at 1440, which is more than enough for a picture that is
       already at 30% opacity when it starts, and the gap is back to a readable 120px.
       The drawing scale is untouched at 1440 and 1920, where the width term binds. */
    padding-bottom: 4.25rem;
    color: var(--blue);
  }

  /* The h1 is held to the same measure as the lede, and that is not decoration. Health
     Areas' heading is two short words and would have cleared the subject on its own; on
     Webinars, "Webinars & Masterclasses" set on one line ran to 63% of the hero, straight
     across the camera and the tripod, and measured 2.42:1 — failing even the 3:1 that
     large text is allowed. Wrapped to two lines it sits back on the wash. Any heading
     added to a photographic hero later inherits the same limit. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero h1,
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero p { max-width: 25rem; }

  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: linear-gradient(to right, rgba(255,255,255,0.860) 0.0%, rgba(255,255,255,0.860) 46.0%, rgba(255,255,255,0.836) 47.0%, rgba(255,255,255,0.771) 48.0%, rgba(255,255,255,0.674) 49.0%, rgba(255,255,255,0.557) 50.0%, rgba(255,255,255,0.430) 51.0%, rgba(255,255,255,0.303) 52.0%, rgba(255,255,255,0.186) 53.0%, rgba(255,255,255,0.089) 54.0%, rgba(255,255,255,0.024) 55.0%, rgba(255,255,255,0) 56.0%, rgba(255,255,255,0) 100.0%),
                      var(--hero-photo);
    background-size: cover, cover;
    background-position: 0 0, var(--hero-photo-pos);
    background-repeat: no-repeat, no-repeat;
    -webkit-mask-image: linear-gradient(to bottom, #000 0.0%, #000 70.0%, rgba(0,0,0,0.972) 73.0%, rgba(0,0,0,0.896) 76.0%, rgba(0,0,0,0.784) 79.0%, rgba(0,0,0,0.648) 82.0%, rgba(0,0,0,0.500) 85.0%, rgba(0,0,0,0.352) 88.0%, rgba(0,0,0,0.216) 91.0%, rgba(0,0,0,0.104) 94.0%, rgba(0,0,0,0.028) 97.0%, rgba(0,0,0,0) 100.0%);
            mask-image: linear-gradient(to bottom, #000 0.0%, #000 70.0%, rgba(0,0,0,0.972) 73.0%, rgba(0,0,0,0.896) 76.0%, rgba(0,0,0,0.784) 79.0%, rgba(0,0,0,0.648) 82.0%, rgba(0,0,0,0.500) 85.0%, rgba(0,0,0,0.352) 88.0%, rgba(0,0,0,0.216) 91.0%, rgba(0,0,0,0.104) 94.0%, rgba(0,0,0,0.028) 97.0%, rgba(0,0,0,0) 100.0%);
  }

  /* The section under the hero gives back most of the padding the hero borrowed, so the
     page barely grows — but not all of it. At zero the first thing in the section sat
     directly under a hero that dissolves rather than ends, and on Ingredients that first
     thing is the filter row: pills pressed against a soft edge, with nothing to say where
     one stopped and the other began. 3.5rem is enough air to separate them and still less
     than the 6rem the section would have had on its own. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero + .section { padding-top: 3.5rem; }

  /* The type, all of it in brand blue — measured per line against the rendered pixels,
     see README.md. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero h1,
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero p,
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero .breadcrumb,
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero .breadcrumb a { color: var(--blue); }
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero .eyebrow--dark {
    color: var(--blue);
    --eb-dot: var(--blue);
  }
  /* White dots would be invisible on a light ground. Blue, multiplied, at the same
     strength the paper sections use. */
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero.mesh-host::before {
    background-color: var(--blue);
    mix-blend-mode: multiply;
    opacity: 0.09;
  }
}

/* No photograph in print, and the blue block comes back with it so the white type on the
   printed page still has a ground. */
@media print {
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero::after { display: none; }
  :is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero { background: var(--blue); color: #fff; }
}

/* Publications: fully transparent, content is already on its own surfaces. */
.page--bg .section--on-bg { background: transparent; }
/* The focused record grows wider than the card. A transform still contributes to
   scrollable overflow, so without this the zoom adds a horizontal scrollbar every
   time a record reaches the middle of the screen. `clip`, never `hidden`: hidden
   would make this a scroll container and the records' view-timeline would resolve
   against it instead of the viewport. */
.section--on-bg { overflow-x: clip; }

/* The other four: content sections become sheets, so the artwork frames them. */
.page--bg:not(.page--bg-publications) .section--paper,
.page--bg:not(.page--bg-publications) .section--white {
  max-width: calc(var(--container) + 4 * var(--gutter));
  margin-inline: auto;
}
.page--bg:not(.page--bg-publications) .section--paper { background: var(--paper); }

/* ---------- The sheets as frosted glass ----------
   The sheets used to be opaque, which left the artwork visible only in the margins
   either side of them — on a long page that is almost none of it. They are now
   translucent and blur what passes behind, so the gradient reads through the whole
   column while the type stays on an even surface.

   The alpha is measured, not chosen for looks. Text sits directly on this surface,
   so the artwork's darkest point under a text line sets the floor. Every text
   element on a sheet, on all four artworks, at 1024 and 1920:

     white   worst contrast   colour that reads through
              (needs 4.5)     (mean CIELAB chroma inside a sheet)
      82%       5.47:1                1.89
      70%       5.10:1                3.15
      60%       4.80:1                4.33
      55%       4.67:1                4.91      <- set
      50%       4.51:1                5.45
      45%       4.35:1  FAIL          6.10

   55% is the last value with real headroom: 50% lands on 4.51:1, which is the
   requirement to two decimal places and no margin for a JPEG that compresses
   differently or a font that renders a shade thinner. For reference, the opaque
   sheet this replaces measured chroma 1.89 — so 55% is about two and a half times
   as much colour showing through, not a token amount.

   The binding case is Ingredients at 1920 every time. Its artwork is the darkest of
   the four where the page's own text sits.

   The blur does the real work for legibility: it removes the artwork's local
   detail, so the surface under a line of text is flat rather than a gradient
   crossing it. 24px is past the point where the cone's edges read as edges.
   `saturate(112%)` puts back the colour that blurring averages away, which is the
   whole reason for doing this.

   Kept off the blue sections: those are a brand surface and their contrast is built
   on being solid.

   `backdrop-filter` is Baseline widely available, but the @supports block still
   matters — without it a browser would paint a 55% white panel with no blur, and
   the text would sit on unblurred artwork at 4.0:1 or worse. The fallback is the
   opaque sheet, which is where this started, so nothing is lost. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .page--bg:not(.page--bg-publications) .section--paper,
  .page--bg:not(.page--bg-publications) .section--white {
    background: rgba(255, 255, 255, 0.55);
    -webkit-backdrop-filter: blur(24px) saturate(112%);
            backdrop-filter: blur(24px) saturate(112%);
    /* A hairline, so the sheet has an edge against the artwork rather than fading
       into it. */
    border-inline: 1px solid rgba(255, 255, 255, 0.5);
  }
  /* The paper sheet keeps a trace of its tint, so paper and white sheets are still
     two surfaces rather than one repeated. */
  .page--bg:not(.page--bg-publications) .section--paper {
    background: rgba(240, 245, 247, 0.55);
  }
}

@media print {
  .page--bg { background-image: none; }
}

/* ---------- 14b. Publications list ----------
   The library moved from a five-column table to a citation list. A table forced
   every record onto one line and made the paper title — the thing a reader
   actually scans for — compete with four other cells. Set as a citation list it
   reads the way PubMed's own results read: type and topic, then title, authors,
   source, identifiers. No selection checkboxes: nothing on this site acts on a
   selection, so they would have been controls that do nothing.
   ------------------------------------------------------------------------- */
/* The search field, filter pills, status line and caption sit on their own
   surface. They used to sit straight on the page, which was fine while the page
   was a flat paper colour — but on this page it is the client's artwork, and text
   on a photograph has no fixed contrast ratio. Measured against the rendered
   pixels before this panel existed: the pill labels came out at 3.8:1 at 1728px
   and 1.76:1 at 390px, where the crop lands on the saturated part of the gradient.
   Against 4.5:1 for normal text, the second figure is not a near miss.

   The panel is the same surface as a record, so every ratio in here is a fixed,
   checkable number again, and the artwork still shows around it and around the
   list. */
.pub-controls {
  background: var(--paper-tint);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}
.pub-controls > *:last-child { margin-bottom: 0; }

.pub-list__caption {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  margin: 0 0 0.85rem;
}
.pub-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--white);
  /* No `overflow: hidden` here, however tempting for the rounded corners: an
     element with overflow:hidden IS a scroll container, so the records'
     view-timeline resolved against this box — which never scrolls — instead of
     against the viewport. The focus animation then ran monotonically down the
     list rather than tracking the screen. The corners are handled on the first
     and last row instead. */
}
/* The <li> is layout only: it reserves the row's height and nothing else. Every
   painted property — surface, border, padding, corners — sits on .pub__inner,
   because .pub__inner is what the focus animation scales. Painting the surface on
   the <li> instead would enlarge the focused record's text while leaving its
   background at the original size, so the text would ride over the neighbouring
   row with nothing behind it. */
.pub { position: relative; }
.pub__inner {
  position: relative;
  z-index: 0;
  /* White sits underneath. The resting tint is painted by ::before on top of it
     and faded out as the record zooms, which is how the focused record turns
     white. See the focus-animation block for why it is done in this order. */
  background: var(--white);
  padding: 1.15rem 1.25rem 1.25rem;
  border-bottom: 1px solid var(--line);
}
.pub__inner::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;          /* behind the record's text, above its white background */
  background: var(--paper-tint);
  border-radius: inherit;
}
.pub:last-child .pub__inner { border-bottom: 0; }
/* Keeps a tinted first or last row from squaring off the card's corners. */
.pub:first-child .pub__inner { border-start-start-radius: var(--radius); border-start-end-radius: var(--radius); }
.pub:last-child  .pub__inner { border-end-start-radius: var(--radius); border-end-end-radius: var(--radius); }

/* No alternating stripes any more. They existed to give the eye something to
   track while scrolling, and the focus highlight now does that job better: the
   record being read is the only white one on the page. Keeping both would have
   meant a focused record that was already white showed no change at all. */
/* The tint itself is applied on .pub__inner above, so it scales with the row. */

/* ---------- Focus on hover, publications list ----------
   The record under the pointer lifts slightly and turns white; everything else
   stays tinted. It used to do this on scroll instead — a view-timeline per row,
   with the record nearest the middle of the screen zoomed — and that is now gone
   on request. Hover is the better fit anyway: the zoom marks the record you are
   reading rather than the one the page happens to have centred, and it cannot
   pick a record you were not looking at.

   What carried over from the scroll version, because it was right for reasons
   that have nothing to do with the trigger:

   1. The effect is on .pub__inner, never on .pub. .pub__inner carries the surface,
      the border and the padding; the <li> only reserves the row height. With the
      surface on the <li>, the focused record's text would be enlarged while its
      background stayed put, and the text would ride over the neighbouring row with
      nothing behind it.

   2. Scale, not opacity. Fading a record up from a tint was the first idea and it
      is unusable: the resting state has to be dim enough for the change to read,
      and at that level the body copy lands at 2.3:1 and the PMID links at 1.9:1
      against a 4.5:1 requirement. Scale costs no contrast at all.

   3. The white-out runs on the tint overlay's opacity rather than on
      background-color. background-color is a paint property; opacity is
      composited, so the surface change costs nothing on the main thread.

   4. transform-origin is the centre, so the record grows out of both sides.

   5. .section--on-bg keeps `overflow-x: clip` — a transform still contributes to
      scrollable overflow, so without it a hovered record would add a horizontal
      scrollbar. `clip`, never `hidden`: hidden would make the section a scroll
      container.

   What changed with the trigger: the zoom is 1.04, not 1.13. A scroll-driven zoom
   is a slow, continuous thing and can afford to be large; a hover zoom snaps, and
   at 13% it snapped so hard the row read as a jump rather than a highlight. It is
   also live under the pointer the whole time it is hovered, and text under a live
   transform is rasterised at its original size and then scaled — at 4% that is not
   visible, at 13% it was.

   `(hover: hover)` so a touch device, where "hover" fires on tap and sticks, gets
   nothing; `prefers-reduced-motion` respected. In both cases every record simply
   renders at rest, which is the state the page was designed in. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .pub__inner {
    transform-origin: center center;
    transition: scale 0.16s ease-out;
  }
  .pub__inner::before { transition: opacity 0.16s ease-out; }
  /* The row itself takes the stacking order: the scaled record has to paint over
     both its neighbours, and a z-index on .pub__inner alone would be trapped in
     the row's own stacking context. */
  .pub:hover { position: relative; z-index: 10; }
  .pub:hover .pub__inner { scale: 1.04; }
  .pub:hover .pub__inner::before { opacity: 0; }
}

/* Below roughly a tablet the card is nearly as wide as the screen, so even 4%
   pushes the citation's first and last characters past the edge, where `clip` cuts
   them off. Almost every device that narrow is a touch device and gets nothing
   anyway; this is for the narrow window that still reports a pointer. */
@media (hover: hover) and (max-width: 48rem) {
  .pub:hover .pub__inner { scale: 1.02; }
}

.pub[hidden] { display: none; }

.pub__kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0 0 0.3rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}
.pub__sep { color: var(--blue-light); }
.pub__topic { color: var(--ink-soft); }
/* When a long topic wraps to a second line, a standalone separator span was left
   dangling at the end of the first line. Gluing it to the front of the topic
   makes it wrap along with the thing it separates. */
.pub__kicker .pub__sep { display: none; }
.pub__topic::before { content: "·"; color: var(--blue-light); margin-right: 0.4rem; }

.pub__title {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--blue);
  margin: 0 0 0.45rem;
  max-width: 54rem;
}
.pub__title a {
  color: inherit;
  text-decoration-color: rgba(0, 91, 130, 0.35);
}
.pub__title a:hover { color: var(--blue-light); text-decoration-color: currentColor; }

.pub__authors, .pub__authors p {
  margin: 0;
  font-size: 0.875rem;
  color: var(--ink);
  max-width: 54rem;
}
.pub__authors-full { margin-top: 0.35rem; }
/* When the disclosure is open the ten-author line is redundant. Browsers without
   :has() simply show both, which is a cosmetic loss and not a broken state. */
.pub__authors:has(details[open]) .pub__authors-short { display: none; }

.authors-more { margin-top: 0.1rem; }
.authors-more summary {
  display: inline-block;
  font-family: var(--font-label);
  font-size: 0.8125rem;
  color: var(--blue-light);
  cursor: pointer;
  list-style: none;
  padding: 0.15rem 0;
}
.authors-more summary::-webkit-details-marker { display: none; }
.authors-more summary::marker { content: ""; }
.authors-more summary:hover { color: var(--blue); text-decoration: underline; }
.authors-more[open] summary { color: var(--ink-soft); }
.authors-more .more-hide { display: none; }
.authors-more[open] .more-show { display: none; }
.authors-more[open] .more-hide { display: inline; }

.pub__source {
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}
.pub__source cite { font-style: italic; color: var(--ink); }

.pub__ids {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0.45rem 0 0;
  font-family: var(--font-label);
  font-size: 0.8125rem;
}

@media (max-width: 30rem) {
  .pub { padding: 1rem; }
  .pub__title { font-size: 1rem; }
}

/* ---------- 15. FAQ ---------- */
.faq { max-width: 52rem; }
.faq details {
  border-bottom: 1px solid var(--line);
  padding: 0.35rem 0;
}
.faq summary {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
  padding: 0.95rem 2.5rem 0.95rem 0;
  position: relative;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "";
  position: absolute;
  right: 0.4rem;
  top: 50%;
  width: 0.6rem;
  height: 0.6rem;
  border-right: 2px solid var(--blue-light);
  border-bottom: 2px solid var(--blue-light);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.2s ease;
}
.faq details[open] summary::after { transform: translateY(-30%) rotate(-135deg); }
.faq details > div { padding: 0 0 1.1rem; }
.faq p { font-size: 0.9375rem; color: var(--ink-soft); }

/* ---------- 16. Callout / definition block (GEO) ---------- */
.definition {
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 1.75rem);
}
.definition p { font-size: 1.0625rem; margin-bottom: 0; }

.note {
  background: rgba(0, 91, 130, 0.05);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  font-size: 0.875rem;
  color: var(--ink-soft);
}
.note strong { color: var(--blue); }

/* ---------- 17. CTA band ---------- */
.cta-band {
  position: relative;
  background: var(--footer-bg);
  color: #fff;
  overflow: hidden;
  isolation: isolate;
}
.cta-band__media { position: absolute; inset: 0; z-index: -2; }
.cta-band__media img { width: 100%; height: 100%; object-fit: cover; opacity: 0.3; }
.cta-band::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg, var(--footer-bg) 20%, rgba(4, 34, 47, 0.8) 100%);
}
.cta-band__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.75rem;
  padding-block: clamp(2.75rem, 6vw, 4rem);
}
.cta-band h2, .cta-band h3 { color: #fff; margin-bottom: 0.5rem; }
.cta-band p { color: var(--blue-pale); max-width: 46ch; margin-bottom: 0; }

/* ---------- 18. Forms ---------- */
.form-grid { display: grid; gap: 1.1rem; max-width: 40rem; }
@media (min-width: 40rem) { .form-grid .row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; } }

.field label {
  display: block;
  font-family: var(--font-label);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--blue);
  margin-bottom: 0.4rem;
}
.field input, .field select, .field textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  min-height: 2.75rem;
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:user-invalid, .field textarea:user-invalid, .field select:user-invalid {
  border-color: #9B2C2C;
}
.field input:user-invalid + .hint, .field textarea:user-invalid + .hint { color: #9B2C2C; }
.hint { font-size: 0.78rem; color: var(--ink-soft); margin-top: 0.3rem; display: block; }

.form-status {
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  font-size: 0.9375rem;
  background: rgba(0, 91, 130, 0.07);
  border-left: 3px solid var(--blue);
}
.form-status[hidden] { display: none; }

/* ---------- 19. Footer ---------- */
.site-footer { background: var(--footer-bg); color: #93AEB9; padding-block: clamp(2.75rem, 6vw, 4rem); }
.site-footer h2 {
  font-family: var(--font-label);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: #fff;
  margin-bottom: 1rem;
}
.site-footer__grid {
  display: grid;
  gap: 2.25rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}
.site-footer ul { list-style: none; margin: 0; padding: 0; font-size: 0.875rem; }
.site-footer li + li { margin-top: 0.55rem; }
.site-footer a { color: #C7D9E1; text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
/* Larger, to match the certification row's new presence.

   The negative margin lines the logo's first row of ink up with the cap height of
   LIBRARY / LEARN / 1Q HEALTH GROUP. Measured: the two boxes already shared a top
   edge, but the artwork's topmost dots start 4px in, so the column read as sitting
   low. */
.site-footer img.footer-logo { height: 5rem; width: auto; margin-top: -0.25rem; }
/* The certification row sits where both 1qhealth.com and the shop put theirs:
   directly above the legal bar. Set as quiet type separated by hairlines rather
   than logo tiles, so it reads as provenance and not as a badge collection. */
.site-footer__quality {
  /* Generous air above the marks: they are the footer's second subject, not a
     footnote to the link columns. */
  margin-top: 3.75rem;
  padding-top: 3.25rem;
  border-top: 1px solid rgba(255,255,255,0.12);
}
.site-footer__certs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1.5rem 3.5rem;
  margin: 0 0 1.5rem;
  max-width: none;
}
/* ---------- Certification marks ----------
   The five files are already white with a transparent ground, so no CSS filter is
   involved — see the README for how they were prepared from the supplied
   Illustrator and EPS artwork.

   Sized by eye rather than to a common height. A single height would make the two
   wide marks (IFS at 1.8 : 1, the organic leaf at 1.7 : 1) shout and the round
   ones disappear; matching their *optical* size means the wide ones sit a little
   lower. `object-fit: contain` guards the ratio if a file is ever swapped for one
   with different proportions. */
/* Much larger and centred: the row is the one place the group's credentials are
   visible, and at 2.5rem it read as fine print. The source PNGs are 180px tall, so
   even at 5rem they are still being downscaled on a 2× screen. */
.site-footer__certs img {
  width: auto;
  height: 5rem;
  object-fit: contain;
  opacity: 0.92;
  transition: opacity 0.2s ease;
}
.site-footer__certs img[alt^="IFS"],
.site-footer__certs img[alt^="EU organic"] { height: 4rem; }
.site-footer__certs img:hover { opacity: 1; }
@media (max-width: 60rem) {
  .site-footer__certs { gap: 1.25rem 2.5rem; }
  .site-footer__certs img { height: 4rem; }
  .site-footer__certs img[alt^="IFS"],
  .site-footer__certs img[alt^="EU organic"] { height: 3.2rem; }
}
@media (max-width: 40rem) {
  .site-footer__certs { gap: 1rem 1.75rem; }
  .site-footer__certs img { height: 3rem; }
  .site-footer__certs img[alt^="IFS"],
  .site-footer__certs img[alt^="EU organic"] { height: 2.4rem; }
}

/* Centred under the row and on one line. The `max-width` had it wrapping after
   "1Q Academy"; `none` plus `balance` keeps it single-line wherever it fits and
   splits it evenly rather than raggedly when it does not. */
.site-footer__certnote {
  margin: 0 auto;
  max-width: none;
  text-align: center;
  text-wrap: balance;
  font-size: 0.8125rem;
  color: var(--blue-pale);
}

.site-footer__bar {
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255,255,255,0.12);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
  font-size: 0.8125rem;
}

/* ---------- 20. Article page ---------- */
.article { max-width: 46rem; }
.article h2 { margin-top: 2.5rem; }
.article h3 { margin-top: 1.75rem; }
.article__meta {
  font-family: var(--font-label);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 2rem;
}
.refs { font-size: 0.875rem; }
.refs ol { padding-left: 1.3rem; }
.refs li { margin-bottom: 0.7rem; color: var(--ink-soft); }
/* The breadcrumb now sits on paper in its own thin bar below the blue hero, so
   the logo panel can have the hero's top-left corner. Colours follow. */
.crumb-bar {
  background: var(--white);
  border-bottom: 1px solid var(--line);
}
.crumb-bar .breadcrumb { margin: 0; padding-block: 0.85rem; color: var(--ink-soft); }
.crumb-bar .breadcrumb a { color: var(--blue-light); }
.crumb-bar .breadcrumb a:hover { color: var(--blue); }

.breadcrumb {
  font-size: 0.8125rem;
  margin-bottom: 1.25rem;
  /* Same correction as .page-hero p, for the same reason: the dot mesh lightens the
     blue under this line, and --blue-pale on the mesh's brightest point is 3.97:1.
     #DBE6EC reads 4.61:1 there. At 13px this is small text, so 4.5:1 applies with no
     large-text exemption to fall back on. */
  color: #DBE6EC;
}
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; }
.breadcrumb li + li::before { content: "/"; margin-right: 0.5rem; opacity: 0.55; }
.breadcrumb a { color: inherit; }

/* ---------- 20b. Video — privacy-first click-to-play facade ----------
   Nothing is requested from YouTube until the visitor presses play: the poster
   is a local image, so the page sets no third-party cookie and loads no
   third-party script on arrival. On click, main.js swaps in a
   youtube-nocookie.com iframe. Without JavaScript the button degrades to a
   plain link to the video (see the <noscript> fallback in the markup). */
.video {
  position: relative;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--blue);
}
.video__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
}
.video__frame img,
.video__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}
.video__frame img { opacity: 0.55; }

/* The play control covers the whole frame so the entire poster is clickable. */
.video__play {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  width: 100%;
  background: linear-gradient(180deg, rgba(0, 91, 130, 0.44), rgba(0, 91, 130, 0.84));
  border: 0;
  cursor: pointer;
  color: #fff;
  font-family: var(--font-label);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 1.5rem;
  text-align: center;
  transition: background-color 0.2s ease;
}
.video__play:hover { background: linear-gradient(180deg, rgba(0, 91, 130, 0.34), rgba(0, 91, 130, 0.76)); }
.video__play:hover .video__icon { transform: scale(1.06); }

.video__icon {
  display: grid;
  place-items: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  background: var(--blue);
  transition: transform 0.2s ease;
  flex: none;
}
@media (prefers-reduced-motion: reduce) {
  .video__play:hover .video__icon { transform: none; }
}
.video__icon svg { width: 1.5rem; height: 1.5rem; fill: #fff; margin-left: 0.2rem; }
.video__duration {
  font-weight: 400;
  letter-spacing: 0.08em;
  opacity: 0.85;
  font-size: 0.8rem;
}

.video figcaption,
.video__caption {
  padding: 0.9rem 1.1rem;
  background: var(--white);
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--ink-soft);
  line-height: 1.5;
}
.video__caption strong { color: var(--blue); }

/* Compact teaser used on the landing page to point at the full section. */
.video-teaser {
  display: grid;
  gap: 1.25rem;
  align-items: center;
  grid-template-columns: 1fr;
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius);
  padding: 1.1rem;
  text-decoration: none;
  color: inherit;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
@media (min-width: 40rem) {
  .video-teaser { grid-template-columns: 14rem 1fr; }
}
.video-teaser:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
@media (prefers-reduced-motion: reduce) { .video-teaser:hover { transform: none; } }
.video-teaser__thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 2px;
  overflow: hidden;
  background: var(--blue);
}
.video-teaser__thumb img { width: 100%; height: 100%; object-fit: cover; opacity: 0.6; }
.video-teaser__thumb span {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
.video-teaser__thumb svg { width: 2.4rem; height: 2.4rem; }
.video-teaser h3 { margin: 0 0 0.35rem; font-size: 1.15rem; }
.video-teaser p { font-size: 0.9rem; color: var(--ink-soft); margin: 0; }
.video-teaser .card__more { padding-top: 0.7rem; }

/* ---------- 21. Media figures ---------- */
.figure { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }
.figure img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.figure--wide img { aspect-ratio: 16 / 9; }

/* Portrait crop for headshots: top-anchored, so the very small margin the
   studio left above each head is trimmed off first if anything has to go. */
.figure--portrait img { aspect-ratio: 3 / 4; object-position: top center; }

/* ---------- 21b. Author cards ----------
   Reuses .card for the border/radius/padding already used for the category
   tiles, and .split for the responsive photo/text layout already used on
   Webinars and Contact — no new layout primitive needed for three entries.
   .card's own hover-lift is scoped to a.card/.card--link, since only linked
   cards should look pressable; an author entry isn't a link, so the same
   lift is repeated here, scoped to this list, rather than widening that
   selector to every plain .card on the site. */
.author-list { display: flex; flex-direction: column; gap: 1.75rem; }
/* All three photos are the same white-background headshot crop, but a
   headshot is still taller than three short paragraphs of bio — centering
   the text against that height reads as an intentional pairing; top-aligned,
   the leftover gap below the text reads as a layout bug. */




/* The photos are white-background cutouts, and the card and page around them
   are white/paper too — without something to separate them, a photo reads as
   floating in empty space rather than sitting in a card. Tinting the text
   half with the same --brand-panel tone used behind the header logo gives
   the row a plate to sit on without introducing a new colour. */
.author-card__body {
  background: var(--brand-panel);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
}
.author-card__body h2 { margin-top: 0; }
.author-card__body p + p { margin-top: 0.85rem; }
/* The role line carries as much as the name does — which organisation, and in what
   position — so it is set as a second line of the heading rather than as a caption:
   the body face, near the name's size, in brand blue. At 0.8125rem in the label face
   it read as metadata and the eye skipped it. */
.author-card__role {
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--blue);
  margin: 0.2rem 0 1rem;
}

/* ---------- About in the top navigation ----------
   One page, not a category, so it carries no chevron and no dropdown. It is
   set in brand blue to lift it out of the row of grey category links, but
   deliberately NOT in 600 with an underline: that combination is what
   aria-current uses for "you are on this page", and reusing it here would
   make About look active on all ten pages. Colour alone is the difference;
   weight and rule stay reserved for the current page. */
/* About was singled out in blue when the rest of the row was grey. The row is blue now,
   so the rule was redundant — and worse than redundant: it sat later in the stylesheet
   with the same specificity as the new hover rule, so it won on source order and kept
   About's label blue on its blue hover box. 1.00:1, an invisible label, and nothing in
   the markup or the declared values looked wrong. Measuring the rendered pixels is what
   found it. The rule is gone; blue is the default now and About needs no help. */


/* ==========================================================================
   EU health claims
   ==========================================================================
   256 records, so the page is a list of short blocks rather than a table: the
   longest "conditions of use" in the register runs to 2,123 characters, which no
   column width survives. Each record gives the wording the label may carry, what
   it has to relate to, and the Regulation it comes from; the conditions sit in a
   disclosure, because they are what you read once you have found your claim, not
   what you scan to find it.

   The list reuses the publications page's search and filter wiring unchanged —
   one predicate over filter and query together — so a search inside an article
   filter behaves the way it does there. */
/* A reading measure without re-centring. Narrowing .container itself would have
   pulled the text off the left edge every other section starts at — visible as a
   step of about 120px between the hero's first line and the paragraph under it. */
.container.measure > * { max-width: 52rem; }

.claim-list { list-style: none; margin: 0; padding: 0; }
.claim {
  padding: 1.35rem 0;
  border-bottom: 1px solid var(--line);
}
.claim:first-child { padding-top: 0; }
.claim[hidden] { display: none; }
.claim__kicker { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0 0 0.5rem; }
.claim__substance { font-size: 1.0625rem; margin: 0 0 0.45rem; }
/* The claim itself is the thing a reader came for, so it is set larger than the
   apparatus around it and in the body face rather than the label face — it is a
   sentence to be read, not a tag to be scanned. */
.claim__wording { font-size: 1rem; color: var(--ink); margin: 0 0 0.6rem; }
.claim__rel, .claim__legal { font-size: 0.8125rem; color: var(--ink-soft); margin: 0.25rem 0 0; }
.claim__label {
  font-family: var(--font-label);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.68rem;
  color: var(--ink-soft);
  margin-right: 0.35rem;
}
.claim__conditions { margin-top: 0.6rem; }
.claim__conditions summary {
  font-family: var(--font-label);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--blue);
  cursor: pointer;
  list-style: none;
}
.claim__conditions summary::-webkit-details-marker { display: none; }
.claim__conditions summary::marker { content: ""; }
.claim__conditions summary::before { content: "+ "; }
.claim__conditions[open] summary::before { content: "\2212 "; }
.claim__conditions summary:hover { text-decoration: underline; }
.claim__conditions p {
  font-size: 0.875rem;
  color: var(--ink-soft);
  margin: 0.5rem 0 0;
  padding-left: 0.9rem;
  border-left: 2px solid var(--line);
}

/* Three bars, three colours. General function is the bulk of the register and is
   left neutral on purpose: colouring 241 of 256 records would be colouring the
   page. The two rare types are the ones worth spotting. */
.claim-type {
  display: inline-flex;
  font-family: var(--font-label);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.22rem 0.5rem;
  border-radius: 2px;
  border: 1px solid currentColor;
}
.claim-type--general  { color: var(--ink-soft); }
.claim-type--risk     { color: #B54200; }
.claim-type--children { color: #764ED1; }
.claim__prop {
  font-size: 0.72rem;
  color: var(--ink-soft);
  font-style: italic;
}

/* A note the reader has to pass before the list. Not a decorative aside — it
   carries the two limits of this copy of the register, so it sits in the flow at
   full measure with a rule down its side rather than floating beside the text. */
.callout {
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius);
  padding: clamp(1.1rem, 2.2vw, 1.5rem);
  margin-bottom: 2rem;
}
.callout p { margin-top: 0; }
.callout ul { margin: 0.75rem 0 0; padding-left: 1.1rem; }
.callout li { font-size: 0.9375rem; color: var(--ink-soft); }
.callout li + li { margin-top: 0.5rem; }

/* The one line on the ingredients page that points at the claims register. It sits
   above the filter row rather than inside the hero, because the hero's type is held
   to 25rem for the photograph's sake and this line belongs with the controls. */
.claims-pointer {
  margin: 0 0 1.5rem;
  font-size: 0.9375rem;
  color: var(--ink-soft);
}
.claims-pointer a { font-weight: 600; }

/* The remainder of a hero's opening paragraph, set below the hero — see hero_intro()
   in build.py for why it is not in the hero. Larger than body copy, because it is
   still the page's opening sentence, and held to a reading measure. */
.hero-intro {
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  line-height: 1.65;
  color: var(--ink);
  margin: 0;
}
/* The section below a photographic hero already gives back most of the padding the
   hero borrowed; when that section is the intro paragraph it needs a little more
   room under it than the 3.5rem the shared rule sets. */
:is(.page--bg-concept-lab, .page--bg-ingredients, .page--bg-health-indications,
     .page--bg-publications, .page--bg-webinars) .page-hero + .section .hero-intro { margin-bottom: 0.5rem; }

/* The hero's third call to action, from the client's copy. A line rather than a
   third button: three buttons at these label lengths wrap into a stack on a laptop
   and none of them reads as the primary one any more. */
/* "Here's what we offer:" — a lead-in to the list under it, not a paragraph of its
   own, so it sits tighter to what follows than a paragraph would. */
.card__lead-in { margin-bottom: 0.5rem; font-weight: 600; color: var(--ink); }

/* On Publications the content sections are transparent so the page artwork shows
   around them. The intro paragraph under the hero is a .section--paper like every
   other, so without this it laid an opaque band across the artwork. */
.page--bg-publications .page-hero + .section { background: transparent; }

/* ---------- The intro paragraph's spacing ----------
   The paragraph under a hero used to sit in a .section--tight, which meant its own
   bottom padding and then the next section's full top padding stacked: 160px of empty
   paper between two paragraphs that belong together. The intro now owns the space
   under it and the section after it starts flush.

   The top padding stays as the shared photographic-hero rule sets it — that is the air
   under a hero that dissolves rather than ends, and it is doing a job. */
.section--intro { padding-bottom: 2.25rem; }
.section--intro + .section { padding-top: 0; }

/* The eyebrow sits closer to the heading it labels. 1.35rem read as two separate
   things stacked; the dots and the line they mark belong together. */
.page-hero .eyebrow { margin-bottom: 0.55rem; }

/* ---------- Line breaking in the heroes ----------
   `text-wrap: balance` on the heading and the opening line. The browser distributes the
   words evenly across the lines instead of filling each line greedily, which is what
   fixes the breaks the client flagged: on Health Indications the lede was breaking as
   "…for X?” rarely / has one clean answer", leaving "rarely" stranded at the end of the
   quoted question. Balanced, the question keeps its own line and "rarely" starts the
   second.

   Only the hero heading and the hero lede. The algorithm is a binary search and the
   guidance is explicit that it should not be applied broadly; it also gives up above
   six lines in Chromium, and every hero here is well under that. Anything without
   support ignores the property and wraps as before — the text is readable either way,
   this only makes the rag better. */
:is(.page-hero, .hero) h1,
:is(.page-hero, .hero) .lede { text-wrap: balance; }
/* A phrase that has to keep its own line. */
.lede__line { display: block; }

/* ---------- The landing page's own rhythm ----------
   The shared .section padding is built for pages whose sections are long — a filter
   row and thirty records, a table of claims. The landing page is a run of short
   blocks: a heading, a paragraph, two buttons. At the shared rhythm the gap between
   two of them measured 200px, and between the Concept Lab and the webinars block that
   is most of a screen of nothing.

   Scoped to .page--home so the content pages keep the air they need. */
.page--home .section { padding-block: clamp(2.5rem, 4vw, 3.25rem); }

/* The hero's third call to action sits on the button row rather than under it. */
.btn-row--with-link { align-items: center; }
.hero__third { font-size: 0.9375rem; font-weight: 600; }

/* ---------- The landing hero has to fit on a laptop ----------
   Everything in it — heading, opening line, both buttons and the third link — has to be
   visible at 1440x800 without scrolling. That is the whole constraint, and every value
   here was set against it rather than chosen.

   The two buttons stack rather than sitting side by side: their labels come from the
   client's document ("Explore ingredient science", "Discover the latest on health
   indications") and together they are wider than the hero's text column. Shortening
   either would buy 68px, but it would be changing the client's words to win space, so
   the space came from the spacing instead — 8px off each gap, and a tighter line on the
   lede.

   At 1440x800 the content now ends at about 780px. At 1280x720 it does not fit, and
   cannot without cutting copy: the heading alone is 185px and the panel above it ends
   at 220. */
.hero--light h1 { margin-bottom: 1.5rem; }
.hero--light .lede { line-height: 1.5; margin-bottom: 1.5rem; }
.hero--light .btn-row { margin-top: 0; gap: 0.7rem; }
/* 793px of 800 was visible but only just. Seven pixels is not a margin. */
.hero--light .hero__third { margin-top: -0.15rem; }

/* The first line of the opening band: the rest of the document's opening paragraph.
   Sized between the hero's lede (21px) and the promise line (16px) so the three read as
   a descending sequence rather than as three unrelated sizes. It keeps the promise
   line's blue and its face; the rule under it separates the two statements without
   putting a gap between them. */
.promise-band p.promise-band__lead {
  /* `.promise-band p` sets the size, the quote face and `text-align-last: justify` —
     the apparatus that stretches the promise line across the full content width. The
     lead is a sentence, not a stretched line, so it resets all three. The selector
     carries the element to outweigh `.promise-band p`; without it the size below lost
     and the lead rendered at the promise line's 16px, justified. */
  font-family: inherit;
  white-space: normal;
  text-align-last: auto;
  font-weight: 400;
  font-size: clamp(1.0625rem, 0.72rem + 0.7vw, 1.22rem);
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 0.9rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--line);
  max-width: 62ch;
}
/* The band carried its padding on the paragraph, which is now the second of two. */
.promise-band .promise-band__lead + p { padding-block: 0; }
.promise-band { padding-block: clamp(1.6rem, 3vw, 2.4rem); }

/* ---------- The opening band, back on paper; the proof strip stays blue ----------
   The band was reversed onto the blue together with the proof strip; the band is
   reversed back. White ground, 1Q Health blue type — 7.45:1, the site's own pairing —
   and the hairline between the two statements returns to --line. The proof strip below
   keeps its blue: the two blocks were inverted in one move but only the band was asked
   back, and the contrast between them separates the statements from the figures.

   What is *not* reverted is the size. The promise line still loses the apparatus that
   held it to one line — it was sized by dividing the content width by its own length in
   ems, which is what produced 16px and made it look undersized. Both paragraphs stay at
   1.375rem over two lines.

   The extra room asked for sits at the foot of the band, so the second statement is not
   pressed against the strip below it. */
.promise-band {
  background: var(--white);
  border-block: 1px solid var(--line);
  color: var(--ink);
  /* One value on both sides, so the text sits in the middle of the band. It had 22.4px
     above and 57.6px below — the top had been cut to lift the first line above the fold
     and the bottom had been enlarged separately, and the two changes never met. The
     paragraphs carry no padding of their own any more (see below); all the room is on
     the band, which is the only way the two halves can be equal by construction rather
     than by arithmetic that has to be redone every time something moves.

     41.6px at 1440. The first line still clears the fold: it lands at 804px. */
  padding-block: clamp(1.9rem, 3.2vw, 2.6rem);
}
/* The paragraphs' own `padding-block` came from the promise line's original one-line
   apparatus. With two bands and one statement in each it is what made both blocks
   top-heavy or bottom-heavy depending on which rule won. The band owns the spacing. */
.promise-band p { padding-block: 0; }
.promise-band p.promise-band__lead,
.promise-band p {
  font-size: clamp(1.125rem, 0.9rem + 0.55vw, 1.375rem);
  line-height: 1.5;
  /* Centred on the horizontal. The measure stays at 64ch — centring is about where the
     block sits in the band, not about letting the line run the full 1080px, which would
     be unreadable. `margin-inline: auto` centres the block, `text-align` centres the
     lines inside it; both are needed or the text sits left inside a centred box.
     Both paragraphs, not just the first: they are one component either side of the
     proof strip and a centred sentence above a left-set one reads as a mistake. */
  max-width: 64ch;
  margin-inline: auto;
  text-align: center;
}
/* The pairing the band had before it was reversed: the lead in --ink, the promise line
   in --blue. Only the ground and the sizes changed; the two voices stay as they were. */
.promise-band p { color: var(--blue); }
.promise-band p.promise-band__lead {
  color: var(--ink);
  border-bottom-color: var(--line);
  padding-top: 0;
}
.promise-band strong,
.promise-band .caps { color: var(--blue); }

/* ---------- The promise line moved below the proof strip ----------
   Two bands now, one either side of the figures. Three things follow from the split:

   1. The lead's hairline was the divider between the two statements. There is no second
      statement under it any more, so the rule would be a line under a single paragraph
      with nothing beneath it. Gone.
   2. The lead kept a padding-bottom that belonged to that hairline, and the band's own
      generous foot padding is what gives the block its room. The paragraph's is dropped
      so the two do not add up.
   3. The lower band's top border would sit on the blue strip, where a --line hairline is
      neither visible nor wanted. Removed; the strip's own edge is the division.

   The promise line is the only paragraph in the lower band, so it takes the padding from
   `.promise-band p` that the `+ p` rule above used to zero for it. That is now the block's
   top spacing and it is left alone. */
/* `p` and the element in the selector: the rule that sets the hairline is
   `.promise-band p.promise-band__lead`, and a plain `.promise-band__lead:only-child`
   loses to it on specificity — the line stayed and the measurement showed 1px. */
.promise-band p.promise-band__lead:only-child {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}
/* No top border: a --line hairline on the blue strip is neither visible nor wanted.
   The padding is the band's own, equal top and bottom, like the block above the strip. */
.promise-band--after-proof { border-top: 0; }

@media (min-width: 80rem) {
  .promise-band p:not(.promise-band__lead) {
    white-space: normal;
    font-size: clamp(1.125rem, 0.9rem + 0.55vw, 1.375rem);
    /* Was `left` — it has to follow the centring above, or the promise line alone
       would jump back to the left edge at 80rem and nowhere else. */
    text-align: center;
    text-align-last: auto;
  }
}

.proof {
  background: var(--blue);
  border-bottom: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.22);
}
.proof__num { color: #fff; }
.proof__label { color: var(--blue-pale); }

/* ---------- 22. Utilities ---------- */
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.mt-2 { margin-top: 2rem; }
.mt-3 { margin-top: 3rem; }
.center { text-align: center; }
.muted { color: var(--ink-soft); }
/* content-visibility intentionally not used: pages are short and it degrades
   print output and find-in-page for a negligible paint saving. */

/* ---------- Room for the logo panel ----------
   The panel hangs 4.5rem below the header bar, so the first element of every
   hero needs to start below it. The space is reserved permanently rather than
   only while the panel is out: content must not move as you scroll. In practice
   it is invisible — the only moment you see the top of a hero is the moment the
   panel is there.

   This block sits at the end of the file on purpose. An earlier attempt placed
   it next to the hero rules, where `.page-hero { padding-block: … }` further
   down reset the top padding again and left the breadcrumb 2px under the panel.
   `padding-block` overwrites `padding-top`, so order decides. */
@media (min-width: 76rem) {
  /* Clears the panel. The padding is asymmetric — more above than below — so the
     panel does not push the promise band down with it.

     14rem when the panel held a separate "Provided by" line and ended at 284px.
     The claim moved into the logo artwork, the panel lost 4rem of depth and now
     ends at 220px, so this followed it down — first to 10rem (25px of clearance,
     the same as the old pairing had), then to 12rem because 25px read as crowded
     under the panel. The H1 now starts at 277px, 57px below the panel's edge,
     which is about one line of its own leading.

     The cost is 32px of the 64px the shallower panel handed back: the hero's two
     buttons end at 792px, so they stay above the fold down to an 800px-tall
     window. Below that — a 768px viewport — they fall under it. Anything more than
     12rem here trades the CTA for whitespace. */
  /* Same change as on the subpages, where the panel is larger: 57px of clearance was
     tighter than it looked. */
  /* 12rem, and the hero has to earn it: everything in it must be visible on a
     1440x800 laptop without scrolling. Measured at that size the content now ends at
     about 700px. The panel ends at 220, so 12rem puts the heading 60px below it —
     there is no eyebrow on this page, so the heading follows the box directly and does
     not need the 85px the subpages give their dot row. */
  /* padding-bottom 2rem → 1.25rem: 12px of the ~54px needed to lift the first line of
     the opening band above the fold. See the band's own rule for the other two. */
  .hero--light .hero__inner { padding-top: 11rem; padding-bottom: 1.25rem; }
  /* Same follow-through on the inner pages: their compact panel ended at 204px and
     now ends at 156px, so 8.9rem becomes 5.9rem. */
  /* 8.5rem, up from 5.9. The panel ends 156px down and the eyebrow started at 179 —
     23px, which read as the text being pushed up against the box above it; 11rem put
     105px there, which was too much. 9.75rem lands on 85px. The photographic heroes give the same amount
     back at the bottom, so their height, and with it the picture's drawing scale, does
     not move. */
  .page-hero { padding-top: 9.75rem; }
}

/* ==========================================================================
   Dot mesh in the background
   Two meshes from 1QH_Graphics_All_01.ai, the parent brand's own dot artwork.

   They are stored as **masks**, not as pictures. The artwork is a single-colour
   dot field, so all the information is in one channel: one mask per mesh serves
   both the white and the blue appearance, the colour comes from
   `background-color`, and the two files replace the eight RGBA versions the first
   attempt produced. `scaleX(-1)` mirrors them, so two files cover four distinct
   appearances.

   The mask carries the mesh in its **alpha channel**, not as grey values. CSS
   masking defaults to `mask-mode: match-source`, which for an image means alpha —
   a plain grayscale file has no alpha, is therefore fully opaque everywhere, and
   the mask does nothing. That mistake rendered the mesh as a flat rectangle at 14%
   opacity, which is exactly what it looked like. `mask-mode: luminance` would be
   the other fix, but alpha is understood everywhere and needs no prefixing.

   Rules that keep it decoration rather than noise:
   - Always a pseudo-element, never markup. Nothing here is content, and
     `pointer-events: none` means it cannot be selected or clicked.
   - Opacity in single digits on paper, low teens on dark. On paper the blue is
     also multiplied, which is what makes it read as part of the surface rather
     than a sticker on top of it.
   - Every host gets `isolation: isolate` so `mix-blend-mode` cannot reach past its
     own section, and `overflow: clip` so a mesh bleeding off the edge cannot widen
     the page.
   - Hidden in print and below 48rem, where a large decorative field only costs
     bandwidth.
   ========================================================================== */
.mesh-host {
  position: relative;
  isolation: isolate;
  /* `clip` rather than `hidden`: hidden makes a scroll container, which would
     break the sticky table head on the publications page and interfere with
     scroll anchoring. `clip` only clips. The `hidden` line stays as a fallback
     for anything that does not know `clip` yet. */
  overflow: hidden;
  overflow: clip;
}
.mesh-host > * { position: relative; z-index: 1; }
.mesh-host::before {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
}

/* --- on paper: blue, multiplied, very faint --- */
.mesh--paper::before {
  background-color: var(--blue);
  mix-blend-mode: multiply;
  opacity: 0.09;
}
/* --- on blue and on the footer: white --- */
.mesh--dark::before {
  background-color: #fff;
  opacity: 0.14;
}

/* Placements. Each bleeds off an edge so the mesh reads as a fragment of
   something larger rather than a motif dropped into a box. */
.mesh--a::before {
  -webkit-mask-image: url("../img/mesh/mesh-a.webp");
          mask-image: url("../img/mesh/mesh-a.webp");
  width: 44rem; height: 80rem;
  right: -13rem; top: -20rem;
}
.mesh--b::before {
  -webkit-mask-image: url("../img/mesh/mesh-b.webp");
          mask-image: url("../img/mesh/mesh-b.webp");
  width: 40rem; height: 66rem;
  left: -12rem; bottom: -18rem;
  transform: scaleX(-1);
}
.mesh--c::before {
  -webkit-mask-image: url("../img/mesh/mesh-b.webp");
          mask-image: url("../img/mesh/mesh-b.webp");
  width: 36rem; height: 59rem;
  right: -9rem; top: -13rem;
}
.mesh--d::before {
  -webkit-mask-image: url("../img/mesh/mesh-a.webp");
          mask-image: url("../img/mesh/mesh-a.webp");
  width: 50rem; height: 91rem;
  right: -15rem; bottom: -30rem;
  transform: scaleX(-1);
}
.mesh--e::before {
  -webkit-mask-image: url("../img/mesh/mesh-a.webp");
          mask-image: url("../img/mesh/mesh-a.webp");
  width: 32rem; height: 58rem;
  left: -10rem; top: -15rem;
  opacity: 0.11;
}

@media (max-width: 48rem) {
  .mesh-host::before { display: none; }
}
@media print {
  .mesh-host::before { display: none !important; }
}

/* ==========================================================================
   AUTHORS — masthead left, authors right
   ==========================================================================
   A trial, and deliberately self-contained: this block and the markup in
   build_authors() are the whole change, so it can be taken out in one piece.

   Every other page opens with a full-width blue banner and starts its content under
   it. On this page the banner was the largest thing on the screen and the first
   author began 700px down, which on a page whose subject is three people is a lot of
   colour before the first face. The masthead is now the left column; the first author
   starts at the top of the right one.

   The section keeps the class `.page-hero` for one reason only — the top padding that
   reserves the logo panel is set on it, and that reservation is not negotiable. Its
   blue is replaced here. */
.page-hero--masthead {
  background: var(--paper);
  color: var(--ink);
  padding-bottom: clamp(2.75rem, 5vw, 4rem);
}
.page-hero--masthead h1 { color: var(--blue); }
/* The banner's 53.6px was set for a heading that had 1080px to run in. In a 21rem
   column it broke into four lines — "The / scientists / behind / the briefs" — which
   reads as a stack of fragments rather than as a sentence. 41.6px sets it in three,
   and the column is a column of type now, not a banner. */
@media (min-width: 62rem) {
  .page-hero--masthead h1 { font-size: clamp(1.9rem, 1.1rem + 1.9vw, 2.6rem); line-height: 1.12; }
}
.page-hero--masthead p { color: var(--ink); }
.page-hero--masthead .lede { color: var(--ink-soft); max-width: none; }
/* The mesh belongs to the blue banner; on paper it would be a grey smudge. */
.page-hero--masthead::before { display: none; }

.authors-split__head > * + * { margin-top: 0.85rem; }
.authors-split__head .eyebrow { margin-bottom: 0; }
.authors-split__head h1 { margin-bottom: 0; }

@media (min-width: 62rem) {
  .authors-split {
    display: grid;
    grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
    gap: clamp(2rem, 3.5vw, 3.25rem);
    align-items: start;
  }
  /* The masthead stays while the authors move past it. 7.5rem clears the header bar
     (85px) with room to spare; `align-self: start` is what gives sticky something to
     stick inside — a stretched grid item is as tall as the row and never moves. */
  .authors-split__head {
    position: sticky;
    top: 7.5rem;
    align-self: start;
  }
  /* The first author has to start level with the eyebrow, not with the heading. */
  .authors-split .author-list { margin-top: 0; }
  .authors-split .author:first-child { padding-top: 0; }
  /* The right column is about 700px, not the full 1080 the cards were built for, so
     the portrait takes a fixed narrow column instead of 42% of the width. */
  .authors-split .author .split {
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
  }
}

/* Below the split the page is what it was: masthead, then authors under it. */
@media (max-width: 61.999rem) {
  .authors-split .author-list { margin-top: 2rem; }
}

/* ---------- The hero's third call to action, beside the second button ----------
   It sat on its own line because the text column is 34rem and the button plus the link
   need 746px. The column is widened to 47rem from 76rem up — the width where the
   side-by-side hero layout starts anyway — and the heading and the lede keep their own
   measures (20ch and 34ch), so nothing but the button row uses the extra width.

   The row's right edge lands at 966px on a 1440 viewport. The photograph's mask is
   fully opaque from 889px, so the tail of the link sits on the picture: the contrast
   of that tail is measured against the rendered pixels, not assumed. */
/* 82rem, not 76. The row reaches about 70% of the viewport width, and the photograph's
   wash is clear from 56% — so below 1312px the tail of the link lands on the subject
   rather than on the wash. Measured at 1216 it read 3.48:1 at its worst with 1.8% of the
   glyph pixels under AA. Above 82rem the same tail measures 5.44:1 and better. Below it
   the link takes its own line again, where it sits on the washed half. */
/* 49.5rem, from 76rem up. The two buttons only need 562px side by side, and a first
   attempt narrowed the column to 36rem on that basis — which pushed the heading from two
   lines to three and dropped the opening band's first line to 896px on a 900px window,
   4px above the fold. The column is wide enough for the heading, not for the row. */
@media (min-width: 76rem) {
  .hero--light .hero__inner { grid-template-columns: minmax(0, 49.5rem); }
  .hero--light .btn-row--with-link { flex-wrap: wrap; }
  /* The first button takes a line of its own; the second and the link share the next.
     The break is an empty flex item in the markup: a wrapping flex row cannot be broken
     any other way, and without it the two buttons share the first line and the link is
     pushed onto its own again. */
  .hero--light .btn-row--with-link .btn-row__break { flex-basis: 100%; height: 0; margin: 0; }
  .hero--light .hero__third { white-space: nowrap; margin-top: 0; }
}

/* Inert everywhere but in the hero's wide row above. */
.btn-row__break { display: none; }
@media (min-width: 76rem) {
  .hero--light .btn-row--with-link .btn-row__break { display: block; }
}

/* The photograph's dissolve, pushed right so the link has paper under it.
   The row now ends at 966px on a 1440 viewport and the old mask was fully opaque from
   889 — measured against the rendered pixels, the link read 4.11:1 median and 2.39:1 at
   its worst, well under AA, and nothing in the declared values showed it.

   The image is not moved and not rescaled: only the mask changes, so the frame, the
   crop and the drawing scale are exactly what they were. What it costs is the left part
   of the picture, which is the empty bright room — the subject sits in the right third
   and is past 66% of the media's width, where the mask is fully opaque. */
@media (min-width: 76rem) {
  .hero--light .hero__media {
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.10) 42%, rgba(0,0,0,0.55) 56%, #000 68%);
            mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.10) 42%, rgba(0,0,0,0.55) 56%, #000 68%);
  }
}
/* At 1280 the fade still leaves 2.7% of the link's strip under AA against the site's
   link blue (#006F9E). The brand blue is a step darker and clears it everywhere. */
@media (min-width: 76rem) {
  .hero--light .hero__third { color: var(--blue); }
}

/* ---------- The subpage header photograph beside each teaser ----------
   Each band on the landing page now carries the same picture its subpage opens with,
   to the right of the text. The teaser already has its own artwork on ::before — a
   full-bleed background behind a white wash — so this is a second image in the same
   band, and the two have to stay clearly separated: the background is washed out to a
   tint, the photograph is a framed card at full strength.

   The files are 760px derivatives of the hero originals (1800–2000px wide), cut here
   rather than shipped at full size for a slot that is 352px wide. `object-position`
   repeats the value each hero uses, so the same part of each frame is in view.

   Below 62rem the picture is not rendered at all: `display: none` with `loading="lazy"`
   costs no request, and stacked above the text it would push each band's own words a
   screen down. */
@media (min-width: 62rem) {
  .teaser__link {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
    gap: clamp(1.5rem, 3vw, 2.75rem);
    align-items: center;
  }
  .teaser__body { max-width: none; }
  .teaser__figure {
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(0, 45, 64, 0.10), 0 10px 26px rgba(0, 45, 64, 0.12);
  }
  .teaser__figure img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }
  /* Horizontally `right`, not `center`. All five originals are wide panoramas whose
     left half is empty room — it is what the page heroes use to carry their headline.
     A 4:3 window centred on those frames keeps the empty half and cuts the subject at
     the right edge; anchored right it holds the subject and drops the room. The second
     value is the one each hero already uses. */
  .teaser--ingredients        .teaser__figure img { object-position: right 65%; }
  .teaser--health-indications .teaser__figure img { object-position: right 42%; }
  .teaser--publications       .teaser__figure img { object-position: right 24%; }
  .teaser--webinars           .teaser__figure img { object-position: right 38%; }
  .teaser--concept-lab        .teaser__figure img { object-position: right 50%; }
}
@media (max-width: 61.999rem) {
  .teaser__figure { display: none; }
}

/* ==========================================================================
   THE LANDING HERO, BUILT LIKE THE SUBPAGE HEROES
   ==========================================================================
   The new photograph is set the way the five photographic subpage heroes are set: the
   picture across the whole hero as a CSS background, a horizontal white wash over the
   left 46% that clears by 56%, and a vertical dissolve into the section below. Same
   numbers as those heroes — they are copied, not re-invented, so the six read as one
   family and any future change to the family reaches all of them.

   Two consequences worth recording:
   * The <img> in .hero__media is gone, and with it its `fetchpriority="high"`. A CSS
     background cannot carry that hint, which is the price of the shared construction;
     the file is 98 KB of WebP and is still the first thing painted.
   * The old right-hand mask apparatus (.hero__media width 58%, the horizontal
     dissolve, the 76rem override) is inert with the element gone, and is left in place
     rather than deleted so the previous construction can be restored in one step. */
/* Only from 64rem — the same breakpoint the five subpage heroes use for their
   photographs. Unscoped, this rule put the picture under the text at every width, and
   on a phone the heading sits across the full viewport while the wash only holds to 50%
   of it: measured at 768 the heading read 1.01:1 against its own background and the
   second button 2.86:1. Below 64rem the landing hero is paper with blue type, which is
   what every subpage does there too. */
@media (min-width: 64rem) {
  .hero--light {
    --hero-photo: image-set(url("../img/hero-home.webp") type("image/webp") 1x,
                            url("../img/hero-home.jpg")  type("image/jpeg") 1x);
    /* The bench and the subject sit in the lower two thirds of this frame; the top third
       is ceiling and window. */
    --hero-photo-pos: center 58%;
    position: relative;
    padding-bottom: 2.5rem;   /* dissolve room, as the subpage heroes take */
  }
  .hero--light > .container { position: relative; z-index: 2; }
  .hero--light::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* The family's ramp holds the wash to 46% and clears it at 56%. Here it is 50% and 60%.
       The subpage heroes hold their text to 25rem, about 28% of the viewport; this one
       carries a heading, a lede and a button row that reach roughly 70%. Measured at 1680
       the last glyphs of the heading sat exactly on the family's clearing point and read
       2.99:1 against the 3:1 large text is allowed. Four percentage points buy the margin
       and still leave the subject clear — she begins at 53%. */
    background-image: linear-gradient(to right, rgba(255,255,255,0.860) 0.0%, rgba(255,255,255,0.860) 50.0%, rgba(255,255,255,0.836) 51.0%, rgba(255,255,255,0.771) 52.0%, rgba(255,255,255,0.674) 53.0%, rgba(255,255,255,0.557) 54.0%, rgba(255,255,255,0.430) 55.0%, rgba(255,255,255,0.303) 56.0%, rgba(255,255,255,0.186) 57.0%, rgba(255,255,255,0.089) 58.0%, rgba(255,255,255,0.024) 59.0%, rgba(255,255,255,0) 60.0%, rgba(255,255,255,0) 100.0%),
                      var(--hero-photo);
    background-size: cover, cover;
    background-position: 0 0, var(--hero-photo-pos);
    background-repeat: no-repeat, no-repeat;
    -webkit-mask-image: linear-gradient(to bottom, #000 0.0%, #000 70.0%, rgba(0,0,0,0.972) 73.0%, rgba(0,0,0,0.896) 76.0%, rgba(0,0,0,0.784) 79.0%, rgba(0,0,0,0.648) 82.0%, rgba(0,0,0,0.500) 85.0%, rgba(0,0,0,0.352) 88.0%, rgba(0,0,0,0.216) 91.0%, rgba(0,0,0,0.104) 94.0%, rgba(0,0,0,0.028) 97.0%, rgba(0,0,0,0) 100.0%);
            mask-image: linear-gradient(to bottom, #000 0.0%, #000 70.0%, rgba(0,0,0,0.972) 73.0%, rgba(0,0,0,0.896) 76.0%, rgba(0,0,0,0.784) 79.0%, rgba(0,0,0,0.648) 82.0%, rgba(0,0,0,0.500) 85.0%, rgba(0,0,0,0.352) 88.0%, rgba(0,0,0,0.216) 91.0%, rgba(0,0,0,0.104) 94.0%, rgba(0,0,0,0.028) 97.0%, rgba(0,0,0,0) 100.0%);
  }
}

/* ---------- Publications' teaser picture, re-cut ----------
   The 760px derivative was a straight resize of the hero panorama, and the card's 4:3
   window then took a slice of it — which at that scale put the whole library building
   in the frame as a pale grey block. The file is now cut from the original at
   1043–1800 x 40–608 and resized: the same picture, closer, so the shelves and the
   staircase read as what they are.

   The top is 40, not 150: at 150 the figure walking along the upper gallery was cut off
   at her shoulders. The window is moved up rather than enlarged, so the scale is
   unchanged. The bottom at 608 still stays well clear of the last ~160 rows of that
   file, which are the invented edge-continuation strip recorded earlier in this README —
   visible as a blur — and would put fabricated pixels on the landing page.

   The file now *is* 4:3, so no further crop is wanted here. */
@media (min-width: 62rem) {
  .teaser--publications .teaser__figure img { object-position: center; }
}

/* ---------- More air between the second button and the link ----------
   `.btn-row` gaps at 0.7rem, which is right for two buttons side by side and too tight
   for a button next to a bare underlined link — they read as one object. The gap is a
   row-wide value, so it is raised only on the pair that shares the second line. */
/* The 2.25rem column gap existed to separate the ghost button from the link beside it.
   They are on separate lines now, so the row is back to the shared 0.7rem and the extra
   space goes between the two lines instead. */
@media (min-width: 76rem) {
  .hero--light .btn-row--with-link { gap: 0.8rem 0.7rem; }
}

/* ---------- The promise line, white on blue ----------
   It sat on paper under the blue figures strip; it is now on the same blue, so the two
   read as one block: the numbers and what they add up to. White measures 7.45:1 on this
   blue, the same pairing the strip's own figures use. The band's hairline borders go
   with it — on the strip's own blue there is nothing to divide. */
.promise-band--after-proof {
  background: var(--blue);
  border-block: 0;
  color: #fff;
}
.promise-band--after-proof p,
.promise-band--after-proof strong,
.promise-band--after-proof .caps { color: #fff; }

/* ---------- A wash of colour behind the Authors page ----------
   The supplied artwork is a smooth four-corner gradient — teal at the top, sand down
   the right, white out of the bottom left — with no detail in it at all. It is
   reproduced here as CSS gradients rather than shipped as a file: at the size it would
   need (the full page area) a photograph of a gradient is a 200–400 KB download for
   something three `background-image` layers draw exactly, at any size, with no request
   and no resampling. If the original file is wanted instead, this block is the only
   place to change.

   Under a --paper veil at 88%, so the colour is present and nothing on top of it has to
   work for its contrast. Measured against the rendered pixels below. */
.page--authors .page-hero--masthead {
  background-image:
    linear-gradient(rgba(239, 244, 246, 0.80), rgba(239, 244, 246, 0.80)),
    radial-gradient(150% 120% at 0% 100%, #FFFFFF 0%, rgba(255,255,255,0.92) 18%, rgba(255,255,255,0) 62%),
    radial-gradient(120% 95% at 100% 52%, #D6BC88 0%, rgba(214,188,136,0.75) 40%, rgba(214,188,136,0) 82%),
    linear-gradient(180deg, #4C9CB6 0%, #7FB0B2 32%, #C9C0A4 62%, #EDE3CE 85%, #F8F3E9 100%);
  background-size: cover;
  background-repeat: no-repeat;
}

/* The Ingredients heading breaks "The science / behind supplement / ingredients" —
   the non-breaking space in build.py ties the middle line together, and this widens the
   measure just enough to hold it. 25rem is the shared limit for headings on the
   photographic heroes and "behind supplement" sets at about 430px, so it would have
   pushed the line past the wash on its own. 28rem holds it; the contrast of the heading
   on this hero is re-measured against the rendered pixels. */
@media (min-width: 62rem) {
  .page--bg-ingredients .page-hero h1 { max-width: 28rem; }
}

/* With LinkedIn added there are three links in the row and the column is 700px wide, so
   the third wraps. The row gap was inheriting the 1.1rem meant for the space *between*
   links on one line, which left the wrapped link floating on its own. Tighter across
   rows, unchanged along them. */
.author-card__links { gap: 0.5rem 1.1rem; }

/* The LinkedIn profile link carries LinkedIn's own logo file, resized, not a redrawn
   lookalike. It is the full wordmark at 4.1:1, so it is sized by height and lets its
   width follow — a square rule here would squash it. 17px tall sits between the 14px of
   the link beside it and the 16px of the body copy, which is where the mark's own
   cap height lands. */
.author-card__profile--linkedin { display: inline-flex; align-items: center; line-height: 1; }
.author-card__mark { display: block; height: 17px; width: auto; flex: none; }
/* The link's underline ran straight through the logo. A wordmark is a picture, not a
   word the browser should decorate — the underline comes off, and hover is carried by
   the mark itself instead so the link still answers the pointer. */
.author-card__profile--linkedin:has(.author-card__mark) { text-decoration: none; }
.author-card__profile--linkedin .author-card__mark { transition: opacity 0.15s ease; }
.author-card__profile--linkedin:hover .author-card__mark,
.author-card__profile--linkedin:focus-visible .author-card__mark { opacity: 0.72; }
@media (prefers-reduced-motion: reduce) {
  .author-card__profile--linkedin .author-card__mark { transition: none; }
}

/* ---------- The links at the foot of an author card ----------
   Three things, two jobs: the two references to our own library and to PubMed belong
   together and stack; the LinkedIn mark is a different kind of proof and goes to the
   right edge. `space-between` does the pushing without a margin hack, and `align-items:
   end` puts the mark on the PubMed line rather than floating between the two.

   The old rule laid all three out in one wrapping row; with the logo added the third
   item wrapped and sat alone under the first. */
.author-card__links {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 0.75rem 1.1rem;
  margin-top: 1.1rem;
}
.author-card__refs {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

/* ---------- The hero has to dissolve into the band below it, not into a step ----------
   The photograph fades out over the last 30% of the hero, which is right — but it was
   fading into --paper (#EFF4F6) while the opening band under it is white, and the band
   also carried a --line hairline along its top. So the picture softened away and then
   met a flat edge and a rule 1px later: two hard lines exactly where the eye was being
   told there were none.

   The hero's own ground is now the same white, and the band's top border comes off. The
   dissolve ends on the colour it is dissolving into, and there is nothing to cross.

   This is the landing hero only. The subpage heroes dissolve into --paper sections and
   are correct as they are. */
.hero--light { background: var(--white); }
.hero--light + .promise-band { border-top: 0; }

/* ---------- Das letzte Klappmenü öffnet nach links ----------
   Die Navigation ist rechtsbündig, und das breiteste Panel (Publications, 544px)
   begann bisher an der linken Kante seines Punktes. Bei 1024px lief es damit 34px
   über den rechten Bildschirmrand und erzeugte auf jeder Seite einen waagerechten
   Scrollbalken — sichtbar erst, seit Webinars und Concept Lab ausgeblendet sind und
   die verbleibenden Punkte nach rechts gerückt sind.

   `build.py` markiert das jeweils letzte Menü der Reihe; die Markierung wandert mit,
   wenn sich die Navigation ändert. */
.site-nav > ul > li.has-menu--right > .submenu { left: auto; right: -1rem; }
