@charset "UTF-8";
/* GENERATED by seo/apply/build-blog-skin.mjs — do not hand-edit.
   Source of truth: mockups/blog-apothecary/blog-skin.css
   Only two transforms are applied: the @charset line above, and the two @font-face
   src paths rewritten from assets/fonts/*.woff2 to *.woff2 (the faces ship flat beside
   this file). Everything else is byte-identical to the reviewed mockup.
   Re-run the build after any aesthetic revision; `--check` fails if this file is stale. */
/* ============================================================================
   ilab-blog-skin.css — "Honest Apothecary" skin for the IntoleranceLab blog
   ----------------------------------------------------------------------------
   Candidate stylesheet for the mu-plugin. Every rule is scoped under the body
   class `.ilab-apothecary`, which the plugin adds via `body_class` on
   is_single()/is_home()/is_category(). Nothing here leaks to WooCommerce,
   the checkout, product pages or the funnel.

   CASCADE STRATEGY (see NOTES.md §2 for the full reasoning)
   ---------------------------------------------------------
   The blog page has three CSS authors we have to coexist with:
     (a) the THEME  — `theme-style.css`, mostly 0,2,0 – 0,3,0 selectors, all
         hardcoded hex (#FB5C71 / #EBEBEB / #8B9094). We must beat these.
     (b) ELEMENTOR KIT 6 — `.elementor-kit-6 {…}` globals + per-widget CSS.
         Per-widget CSS is *deliberate design intent* on 80 posts; we must NOT
         beat it, only re-point the global variables it references.
     (c) INLINE <style> BLOCKS IN THE BODY — the hero background-image (~char
         51 500), `#ilab-funnel-css`, `#loop-701`. These sit AFTER every head
         stylesheet, so source order can never save us: only specificity can.

   Therefore:
     · LAYER 1 (base rhythm) uses `:where(…)` → specificity 0,0,0. Any
       Elementor per-widget rule outranks it, so bespoke widget styling on the
       80 Elementor posts survives untouched.
     · LAYER 2 (brand) uses ordinary descendant selectors deliberately pitched
       one step above the theme rule it replaces. Font-family, palette, link
       colour and heading weight only.
     · NO `!important` IN THE CASCADE. There is no WooCommerce CSS war to win
       here, and every conflict below is resolvable on specificity alone.
       ROUND 3 (G15): this used to read "NO `!important` anywhere", which was
       false — there are exactly FOUR, all inside the `prefers-reduced-motion`
       block in §13, where the WCAG 2.3.3 escape hatch has to beat any author
       rule including its own. That is the one sanctioned use and the build
       script asserts the count. A stylesheet this heavily annotated loses more
       from one false annotation than a sloppy one does, so the claim is now
       the true one.
     · NO `@layer`. All theme/Elementor CSS is unlayered, and unlayered rules
       always outrank layered ones — a cascade layer would silently lose.

   FILE MAP
     §0  Fonts + zero-CLS fallbacks
     §1  Tokens
     §2  Page canvas
     §2A Site header — Elementor template 729  (added round 2)
     §3  Elementor kit-6 global re-declaration
     §4  LAYER 1 — base rhythm (:where, zero specificity)
     §5  LAYER 2 — brand overrides (font / colour / weight)
     §6  Hero
     §7  Article body — classic (Gutenberg) posts
     §8  Article body — Elementor posts
     §9  Injected funnel CTA + related reading
     §10 Post meta — tags, share row, comments
     §11 Journal index + category archive
     §12 Engagement popup
     §13 Utilities, reading progress, motion + 375px guards
     §14 Forced colors (Windows High Contrast)   (added round 3, G12)
     §15 Print                                   (added round 3, G11)
   ========================================================================= */


/* ============================================================================
   §0  FONTS — self-hosted woff2, never Google Fonts.
   @font-face cannot be scoped; these are the only unscoped rules in the file
   and they are inert until something asks for the family.
   ========================================================================= */

@font-face {
  font-family: "Fraunces";
  src: url("fraunces.woff2") format("woff2");
  font-weight: 100 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Hanken Grotesk";
  src: url("hanken-grotesk.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Size-adjusted local fallbacks. `font-display:swap` normally costs a layout
   shift when the real face lands; overriding the fallback's metrics to match
   makes the swap dimensionally invisible (CLS ≈ 0). Values are a measured
   first pass — see NOTES.md §9 before shipping.

   ANDROID (round 3, F15). The original `local()` lists named only Georgia,
   Times, Segoe UI, Helvetica Neue and Arial — NONE of which exist on stock
   Android. `local()` that never resolves makes the whole @font-face inert, so
   the metric override silently no-opped on the largest mobile segment and the
   swap cost a real layout shift there. `Noto Serif` / `Droid Serif` and
   `Roboto` / `Noto Sans` are the Android-side equivalents; they are appended
   rather than substituted so desktop keeps the closer metric match first. */
@font-face {
  font-family: "Fraunces Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times"),
       local("Noto Serif"), local("Droid Serif");
  size-adjust: 97%;
  ascent-override: 96%;
  descent-override: 24%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Hanken Fallback";
  src: local("Segoe UI"), local("Helvetica Neue"), local("Arial"),
       local("Roboto"), local("Noto Sans");
  size-adjust: 99%;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}


/* ============================================================================
   §1  TOKENS
   Namespaced `--ilab-*` so nothing collides with Elementor's `--e-*`,
   WooCommerce's `--wc-*` or the theme's own custom properties. Declared on
   `.ilab-apothecary` (the <body>) so they inherit to every descendant,
   including Elementor widgets and the in-body inline <style> blocks.
   ========================================================================= */

.ilab-apothecary {
  /* palette — apothecary */
  --ilab-bone:        #F6F1E6;   /* page */
  --ilab-bone-deep:   #EEE7D6;   /* alternate band */
  --ilab-paper:       #FCFAF4;   /* card surface */
  --ilab-ink:         #20271F;   /* near-black green */
  --ilab-ink-soft:    #4A554A;   /* body prose */
  --ilab-forest:      #1E4D38;   /* primary / headings */
  --ilab-forest-deep: #143526;
  --ilab-sage:        #5E7A68;   /* DECORATIVE / large text only: 4.18:1 on bone */
  --ilab-sage-ink:    #4E6857;   /* TEXT sage: 5.42 bone / 5.84 paper / 4.95 bone-deep */
  --ilab-sage-soft:   #DCE7DC;
  --ilab-clay:        #BE4F2E;   /* CTA fill */
  --ilab-clay-deep:   #A23F22;   /* CTA hover + link text (AA on bone) */
  --ilab-clay-soft:   #F4E2D6;
  --ilab-ochre:       #B68A2E;   /* DECORATIVE ONLY — rules, underlines, seals */
  --ilab-ochre-ink:   #826320;   /* TEXT ochre. #B68A2E is 2.80:1 on bone and
                                    fails AA outright. #826320 clears 4.5:1 on all
                                    three surfaces: bone 4.97, bone-deep 4.54,
                                    paper 5.35. See NOTES.md §8. */
  --ilab-ochre-lite:  #D9AE55;   /* ochre for the DARK hero only.
                                    ROUND 3 (G13) — THE FIGURES HERE WERE WRONG
                                    AND ARE NOW CORRECT. The old note quoted the
                                    lightest composite as rgb(81,95,89), which is
                                    the .74-alpha LINEAR scrim over a white photo
                                    and nothing else. But §6's `::before` lists
                                    the `radial-gradient` FIRST in the background
                                    shorthand, so the sage bloom paints ABOVE the
                                    scrim. The true lightest composite is
                                    .42×rgb(94,122,104) over rgb(81,95,89) =
                                    rgb(87,106,95), which is lighter still.
                                    Recomputed there: #D9AE55 is 2.79:1 (below
                                    WCAG 1.4.11's 3.0) and bone on the badge chip
                                    (.10-alpha bone fill) is 4.2:1 (below 4.5).
                                    WHY THERE IS STILL NO LIVE FAILURE, and why
                                    this is a documentation fix rather than a
                                    contrast fix: the bloom is
                                    `120% 90% at 22% 8% … transparent 62%`, i.e.
                                    it is gone by roughly the upper two-thirds of
                                    the band, and every element that uses this
                                    token — the byline clock, the author dot, the
                                    badge chip — sits at the BOTTOM edge, because
                                    the hero is `align-items: end`. At that
                                    position the linear scrim is .92 alpha and the
                                    bloom contributes nothing: composite
                                    rgb(39,56,49), where #D9AE55 measures 5.98:1
                                    and bone on the badge chip 8.18:1. Both pass.
                                    The 2.79 / 4.2 figures describe a point on the
                                    band where none of this ink is ever drawn.
                                    NEVER use this on bone/paper — it is 2.0:1. */
  --ilab-rose-soft:   #F3E1DA;
  --ilab-line:        rgba(30, 77, 56, .15);
  --ilab-line-strong: rgba(30, 77, 56, .28);   /* DECORATIVE card edges only */
  --ilab-control-edge: #778E80;  /* FORM-CONTROL edges. --ilab-line-strong
                                    resolves to #BECABF over paper = 1.63:1 and
                                    fails WCAG 1.4.11 (needs 3.0) for any border
                                    that is the only thing identifying an input.
                                    #778E80: paper 3.37, bone 3.13. (Do not put a
                                    control on bone-deep — 2.86 there.) */
  --ilab-on-clay:     #FFF7F0;   /* 4.55:1 on --ilab-clay */

  --ilab-shadow-sm: 0 1px 2px rgba(20,39,31,.06), 0 2px 8px rgba(20,39,31,.05);
  --ilab-shadow-md: 0 4px 14px rgba(20,39,31,.08), 0 16px 40px rgba(20,39,31,.07);
  --ilab-shadow-lg: 0 10px 30px rgba(20,39,31,.10), 0 30px 60px rgba(20,39,31,.10);

  /* type */
  --ilab-display: "Fraunces", "Fraunces Fallback", Georgia, "Times New Roman", serif;
  --ilab-body: "Hanken Grotesk", "Hanken Fallback", -apple-system, BlinkMacSystemFont,
               "Segoe UI", system-ui, sans-serif;
  --ilab-mono: ui-monospace, "Cascadia Code", "SF Mono", "Segoe UI Mono", Menlo, monospace;

  /* rhythm */
  --ilab-measure: 38rem;         /* editorial prose column. ROUND 3 (F5): this
                                    was 42rem annotated "~68ch" — the annotation
                                    was simply wrong; 42rem at the old 17px base
                                    measured 74ch. At the new 19px base (see §2)
                                    38rem ≈ 64ch, inside the 45–75ch band and on
                                    the calm side of it for long-form health
                                    reading. Measured, not asserted. */
  --ilab-container: 1140px;      /* matches Elementor kit 6 --container-max-width */
  /* ROUND 3 (G8) — THE STICKY-HEADER HEIGHT, AS ONE NUMBER.
     `scroll-padding-top` was hard-coded at 6rem (96px) in two places while the
     header the skin actually builds measures 50px: the promo strip is
     `display:none` on blog contexts (§2A), so the band is
     .5rem + clamp(26px, 2.3vw, 34px) + .5rem = 42–50px. 96px is nearly TWICE
     the header, so a reader arriving on a scroll-to-text fragment lands with
     the quoted sentence pushed 46px below where it should be — the correction
     over-corrects and the target sits mid-viewport instead of just under the
     bar. Both the scroll offsets and the hero's own top padding now derive
     from this one token, so they cannot drift from the header again.
     3.125rem = 50px = the tallest the band gets (34px logo + 2×.5rem). */
  --ilab-header-h: 3.125rem;
  /* header + a small clearance margin, so the landing has air under the bar
     without being thrown down the page. 3.125 + .5 = 3.625rem ≈ 58px. */
  --ilab-scroll-offset: calc(var(--ilab-header-h) + .5rem);
  --ilab-gutter: clamp(1.1rem, 4vw, 2.5rem);
  --ilab-section-y: clamp(3rem, 7vw, 5.5rem);
  --ilab-radius: 18px;
  --ilab-radius-sm: 12px;
  --ilab-radius-lg: 28px;

  /* the paper grain, reused by hero + cards */
  --ilab-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}

@media (max-width: 760px) {
  .ilab-apothecary { --ilab-section-y: clamp(2.4rem, 6vw, 3.6rem); }
}


/* ============================================================================
   §2  PAGE CANVAS
   The theme leaves `body {}` empty; the page background comes from the browser
   default (white). We own it outright.
   ========================================================================= */

.ilab-apothecary {
  background-color: var(--ilab-bone);
  background-image: var(--ilab-grain);
  color: var(--ilab-ink);
  font-family: var(--ilab-body);
  /* ROUND 3 (F4). Was clamp(1rem, .96rem + .2vw, 1.0625rem) = 16→17px. 17px is
     a 2024 body size; this is a 25-minute read on a health page, often on a
     phone, often by someone who feels unwell. 17→19px. */
  font-size: clamp(1.0625rem, 1rem + .28vw, 1.1875rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* ROUND 3 (F10). Was `overflow-x: hidden`, which makes <body> a scroll
     container: that fights the sticky header, and `animation-timeline:
     scroll(root block)` in §13 then measures a container that no longer
     scrolls. `clip` gives the identical 375px overflow insurance WITHOUT
     creating a scroll container. */
  overflow-x: clip;            /* 375px insurance — see §13 */
  /* The page is a light surface by design and has no dark-mode variant; declare
     it so form controls, scrollbars and the UA's own widgets stop rendering in
     the visitor's dark palette against bone paper. */
  color-scheme: light;
  /* Inert here (see the html rule below) but kept as the declaration of intent
     that belongs with the token that sets the header height. */
  scroll-padding-top: var(--ilab-scroll-offset);
}

/* ROUND 3 (F11). `scroll-padding-top` only does anything on the SCROLL
   CONTAINER. With `overflow-x: clip` above, <body> is not one — the root
   element is. So the working declaration has to land on <html>, and `:has()`
   keeps it gated on the same body class as everything else in this file: no
   blog page, no rule. Browsers without `:has()` fall back to the per-heading
   `scroll-margin-top` in §7, which covers the same ground element by element.
   WHY THIS MATTERS: Google's featured-snippet deep links use scroll-to-text
   fragments, so a reader arriving from a snippet lands on the sentence Google
   quoted — underneath a sticky header, today. */
html:has(> body.ilab-apothecary) {
  /* ROUND 3 (G8). The `--ilab-*` tokens are declared on the <body>, and custom
     properties inherit DOWN only — so `var(--ilab-scroll-offset)` is invalid
     here. The token is re-declared on this element (still gated on the same
     body class, so it cannot leak) rather than hard-coding a second literal
     that would drift from §1 the first time the header changes. */
  --ilab-header-h: 3.125rem;
  scroll-padding-top: calc(var(--ilab-header-h) + .5rem);
}

/* Elementor's own header/footer templates sit on white containers; let the bone
   read through where they don't paint their own background. */
.ilab-apothecary .elementor-location-archive,
.ilab-apothecary .blog-details-area,
.ilab-apothecary .content-block { background-color: transparent; }

.ilab-apothecary :focus-visible {
  outline: 3px solid var(--ilab-forest);
  outline-offset: 2px;
  border-radius: 4px;
}
/* ROUND 3 (F12). The ring above is `--ilab-forest` on a `--ilab-forest-deep`
   hero = 1.0:1. Every focusable element inside the hero band (the category
   pill, any breadcrumb, any link in a title) was therefore literally invisible
   to a keyboard user. Bone on the scrim's lightest realistic composite is
   5.94:1. `outline-color` only — the width, offset and radius are unchanged, so
   this is one property, not a second focus style to keep in sync. */
.ilab-apothecary .blog-details-hero :focus-visible { outline-color: var(--ilab-bone); }


/* ============================================================================
   §2A  SITE HEADER  —  Elementor theme-builder template 729
   ----------------------------------------------------------------------------
   The header ships completely unstyled by the skin in round 1, and it wrecked
   every plate: §5 gives every `h2` on the page Fraunces at heading weight, and
   the promo bar's copy is an `<h2>` — so the utility strip rendered as the
   loudest type on a post page, above the article's own `<h1>`.

   THE MARKUP WE ARE STYLING (verbatim from the 2026-07-30 capture):

     div.elementor.elementor-729.elementor-location-header
     ├── div.e-con.e-con-boxed.elementor-element-7225556          ← promo strip
     │   └── .e-con-inner > .elementor-element-1bcbae3.elementor-widget-heading
     │       └── .elementor-widget-container
     │           └── h2.elementor-heading-title
     │               "GET YOUR INTOLERANCE TEST HERE 20% OFF"      ← bare text node
     │               a.intolarane-link  "Get My Intolerance Test"  ← real hook, sic
     └── div.e-con.e-con-boxed.elementor-element-c3e98aa           ← logo/cart band
         └── .e-con-inner
             ├── .e-con.e-con-full.elementor-element-8c692a3
             │   └── .elementor-element-917237b.elementor-widget-image > … > img
             └── .e-con.e-con-full.elementor-element-6a81f8e
                 └── .elementor-element-3a7b5fa.elementor-widget-woocommerce-menu-cart
                     └── … .elementor-menu-cart__toggle
                         └── a#elementor-menu-cart__toggle_button
                             .elementor-button.elementor-size-sm
                             ├── span.elementor-button-text > .woocommerce-Price-amount
                             └── span.elementor-button-icon
                                 ├── span.elementor-button-icon-qty[data-counter]
                                 └── svg.e-eicon-basket-medium

   TWO ELEMENT IDS ARE LOAD-BEARING — `elementor-element-7225556` (promo strip)
   and `elementor-element-c3e98aa` (band). They are template-729 IDs and would
   change only if the containers were deleted and re-added in Elementor. Every
   rule that uses one is paired with a structural twin (`> .e-con:first-child` /
   `:last-child`) so a re-save cannot silently un-style the header.

   CASCADE. The band's layout is re-pointed through Elementor's own container
   custom properties (`--flex-direction`, `--justify-content`, `--padding-*`),
   the same mechanism §11 uses on the loop grid — so it stays compatible if the
   template is edited later. Live has a `post-729.css` we never captured, whose
   per-element selectors run `.elementor-729 .elementor-element.elementor-element-x`
   = 0,3,0; everything here is pitched at 0,4,0 or above so it wins outright
   rather than on enqueue order. The grey `#818a91` cart box is Elementor's
   default `.elementor-button` at 0,1,0.

   THE RULE THIS SECTION EXISTS TO ENFORCE: on a post page the article `<h1>` is
   the loudest thing on the screen, always. Nothing in the header is larger than
   12 px of mono, and the only display type is the logo, which is an image.
   ========================================================================= */

/* --- the shell. Sticky per the band's own live `data-settings` (`sticky:top`,
       all three breakpoints); we lift the whole header rather than the band
       alone, because a sticky child inside a 90 px parent un-sticks after 34 px.
       z-index sits under `ilab-chat` (99 998) and the popup (100 000). ------ */
.ilab-apothecary .elementor-location-header {
  position: sticky;
  top: 0;
  z-index: 90;
}

/* --- 1. PROMO STRIP -------------------------------------------------------
   DECISION (David, 2026-07-30): the sitewide promo strip is SUPPRESSED on blog
   contexts. It advertised 20% off while the reader-facing offer is the popup's
   10%, and two competing numbers on one screen kills both. The strip keeps
   running everywhere else — this rule only fires where `.ilab-apothecary` is,
   i.e. single posts, the blog index and post archives.

   Suppression is `display:none` on the container, NOT a template edit, so
   removing the skin restores the strip exactly. It is also why the strip's own
   styling below is retained rather than deleted: if the offer is ever unified
   the suppression is a one-line revert and the treatment is already built.

   The retained treatment is deliberately OFFER-AGNOSTIC: no rule reads, asserts
   or reserves space for a percentage, so "20%", "10%", "£10 off" or no offer at
   all are the same restyle. `overflow: hidden` is the 375 px floor — if the copy
   ever grows past the strip it clips instead of widening the document. */
/* 2026-08-03: SEO-34 deleted 7225556, making c3e98aa the first child. This
   must stay ID-scoped: a first-child selector would now hide the live header. */
.ilab-apothecary .elementor-location-header .e-con.elementor-element-7225556 {
  display: none;
}

.ilab-apothecary .elementor-location-header .e-con.elementor-element-7225556.ilab-promo-visible {
  display: flex;
  --justify-content: center;
  --align-items: center;
  --gap: 0px;
  --padding-top: .42rem;
  --padding-bottom: .42rem;
  --padding-left: var(--ilab-gutter);
  --padding-right: var(--ilab-gutter);
  background-color: var(--ilab-forest-deep);
  overflow: hidden;
}

/* The `<h2>` becomes a single-line flex row. The offer copy is a bare text node
   with no element around it, so it can only be reached as an anonymous flex
   item — which is why the strip is a flex container rather than a text block.
   `--ilab-bone` on `--ilab-forest-deep` = 11.90:1. */
.ilab-apothecary .elementor-location-header .elementor-element-1bcbae3
  .elementor-widget-container .elementor-heading-title {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  margin: 0;
  font-family: var(--ilab-mono);
  font-size: clamp(.62rem, .5rem + .35vw, .72rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ilab-bone);
}

/* Its CTA. A clay pill — the brand's one accent, which only ever means "act".
   `--ilab-on-clay` on `--ilab-clay` = 4.55:1. The clay fill against the strip is
   only 2.77:1, below the 3:1 that WCAG 1.4.11 wants for a control's boundary,
   so the pill carries a bone hairline that does the identifying instead. */
.ilab-apothecary .elementor-location-header .elementor-heading-title a.intolarane-link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  padding: .26rem .72rem;
  border: 1px solid rgba(246, 241, 230, .5);
  border-radius: 999px;
  background-color: var(--ilab-clay);
  color: var(--ilab-on-clay);
  font-family: var(--ilab-mono);
  font-size: inherit;
  font-weight: 600;
  letter-spacing: inherit;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .18s ease, border-color .18s ease;
}
.ilab-apothecary .elementor-location-header .elementor-heading-title a.intolarane-link:hover {
  background-color: var(--ilab-clay-deep);
  border-color: var(--ilab-bone);
}

/* Below ~600 px the sentence and the link's 23 characters cannot share one line
   at a readable size, so the link shortens to an arrow. `font-size: 0` collapses
   the glyphs without touching the accessibility tree — the accessible name is
   still "Get My Intolerance Test". The strip stays ONE line at every width. */
@media (max-width: 600px) {
  .ilab-apothecary .elementor-location-header .elementor-heading-title a.intolarane-link {
    padding: .3rem .55rem;
    font-size: 0;
    letter-spacing: 0;
  }
  .ilab-apothecary .elementor-location-header .elementor-heading-title a.intolarane-link::after {
    content: "\2192";
    font-size: .78rem;
    line-height: 1;
  }
}

/* --- 2. LOGO + CART BAND --------------------------------------------------
   The landing kit's sticky treatment. Without this the band's `.e-con-inner`
   inherits Elementor's default `--flex-direction: column`, which is what put the
   logo on its own line above a grey cart box. `--flex-wrap-mobile: nowrap`
   cancels substrate/Elementor's 767 px wrap so the row survives 375 px. */
.ilab-apothecary .elementor-location-header > .e-con:last-child,
.ilab-apothecary .elementor-location-header .e-con.elementor-element-c3e98aa {
  --flex-direction: row;
  --justify-content: space-between;
  --align-items: center;
  --gap: 1rem;
  --flex-wrap: nowrap;
  --flex-wrap-mobile: nowrap;
  --padding-top: .5rem;
  --padding-bottom: .5rem;
  --padding-left: var(--ilab-gutter);
  --padding-right: var(--ilab-gutter);
  /* ROUND 3 (F18). Was 86%-alpha bone + `backdrop-filter: saturate(140%)
     blur(10px)`. What passes under this bar is a FLAT bone field carrying 0.035
     grain — so the frost was blurring nothing and saturating nothing, while
     costing a compositor layer on every scroll frame and, on the article page,
     a repaint of the band on every tick. A solid bone and the existing hairline
     do the whole job. The hairline is the separation; frost was decoration
     pretending to be it. */
  background-color: var(--ilab-bone);
  background-image: var(--ilab-grain);   /* the same paper, opaquely */
  box-shadow: inset 0 -1px 0 var(--ilab-line);
}

/* The two half-width children shrink to their content so `space-between` has
   something to work with. */
.ilab-apothecary .elementor-location-header .e-con.elementor-element-8c692a3,
.ilab-apothecary .elementor-location-header .e-con.elementor-element-6a81f8e {
  --flex-direction: row;
  --align-items: center;
  --gap: .75rem;
  --width: auto;
  flex: 0 0 auto;
}
.ilab-apothecary .elementor-location-header .e-con.elementor-element-6a81f8e {
  --justify-content: flex-end;
}

/* Logo. 260 × 52 SVG; height-driven so the band's rhythm is one number. */
.ilab-apothecary .elementor-location-header .elementor-element-917237b .elementor-widget-container { line-height: 0; }
.ilab-apothecary .elementor-location-header .elementor-element-917237b img {
  display: block;
  width: auto;
  height: clamp(26px, 2.3vw, 34px);
  max-width: 100%;
}

/* --- 3. CART WIDGET -------------------------------------------------------
   Elementor's `.elementor-button` default paints this `#818a91` with a white
   icon — the grey box. Replaced with a paper pill: forest basket, mono price
   (`--ilab-forest` on `--ilab-paper` = 9.26:1) and a clay count badge
   (`--ilab-on-clay` on `--ilab-clay` = 4.55:1). */
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .34rem .5rem .34rem .8rem;
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  background-color: var(--ilab-paper);
  color: var(--ilab-forest);
  fill: var(--ilab-forest);
  box-shadow: var(--ilab-shadow-sm);
  text-decoration: none;
  transition: background-color .18s ease, border-color .18s ease;
}
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button:hover {
  background-color: var(--ilab-bone-deep);
  border-color: var(--ilab-line-strong);
}
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button .elementor-button-text,
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button .woocommerce-Price-amount {
  font-family: var(--ilab-mono);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .03em;
  line-height: 1;
  color: var(--ilab-forest);
}
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button .elementor-button-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-inline-end: 2px;
}
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button svg {
  display: block;
  width: 20px;
  height: 20px;
  fill: currentColor;
}
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button .elementor-button-icon-qty {
  position: absolute;
  top: -7px;
  right: -9px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background-color: var(--ilab-clay);
  color: var(--ilab-on-clay);
  font-family: var(--ilab-mono);
  font-size: .58rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
}
/* An empty basket should not shout. The count badge is only interesting once
   there is something in it — `data-counter` is written by WooCommerce fragments. */
.ilab-apothecary .elementor-location-header .elementor-menu-cart__toggle_button
  .elementor-button-icon-qty[data-counter="0"] {
  background-color: var(--ilab-sage-soft);
  color: var(--ilab-forest);
}

/* The mini-cart panel is `aria-hidden` until Elementor's JS opens it; with no
   Elementor Pro CSS in the mockup it would otherwise render as an empty block. */
.ilab-apothecary .elementor-location-header .elementor-menu-cart__container[aria-hidden="true"] { display: none; }


/* ============================================================================
   §3  ELEMENTOR KIT 6 — GLOBAL RE-DECLARATION
   `post-6.css` declares these on `.elementor-kit-6` (0,1,0). `.ilab-apothecary`
   lands on the same <body> element, so the compound below is 0,2,0 and wins
   regardless of stylesheet order. 2 700 `var(--e-global-*)` references across
   122 Elementor CSS files re-point from this one block.

   This is a real lever but NOT a complete one: the theme and roughly a third of
   the Elementor CSS use hardcoded hex. §5–§11 are the explicit layer that
   catches the rest.
   ========================================================================= */

.ilab-apothecary.elementor-kit-6 {
  --e-global-color-primary:   var(--ilab-forest);
  --e-global-color-secondary: var(--ilab-ink);
  --e-global-color-text:      var(--ilab-ink-soft);
  --e-global-color-accent:    var(--ilab-clay-deep);
  --e-global-color-bf873df:   var(--ilab-clay);   /* was #E33243 */

  --e-global-typography-primary-font-family:   "Fraunces";
  --e-global-typography-primary-font-weight:   460;
  --e-global-typography-secondary-font-family: "Hanken Grotesk";
  --e-global-typography-secondary-font-weight: 400;
  --e-global-typography-text-font-family:      "Hanken Grotesk";
  --e-global-typography-text-font-weight:      400;
  --e-global-typography-accent-font-family:    "Hanken Grotesk";
  --e-global-typography-accent-font-weight:    500;
}


/* ============================================================================
   §4  LAYER 1 — BASE RHYTHM  (`:where()`, specificity 0,0,0)
   Sizes, measure and vertical rhythm only. Because these carry zero
   specificity, ANY Elementor per-widget rule (`.elementor-2438
   .elementor-element-xyz .elementor-heading-title{font-size:38px}`) outranks
   them and the 80 hand-built posts keep their intended proportions.
   ========================================================================= */

:where(.ilab-apothecary h1, .ilab-apothecary h2,
       .ilab-apothecary h3, .ilab-apothecary h4) {
  line-height: 1.08;
  letter-spacing: -.015em;
  font-variation-settings: "opsz" 110, "SOFT" 30, "WONK" 0;
  text-wrap: balance;
  margin: 0 0 .55em;
}
/* ROUND 3 (G5) — THE HEADING SCALE WAS LEFT ON THE 17px BASE.
   F4 raised the prose base 17→19px and these three clamps did not move with it,
   so measured against 19px prose at 1375px: h4 rendered 19.2px (1.01× body —
   the tier was invisible, an h4 that is indistinguishable from a paragraph is
   not a heading) and h3 rendered 23.2px (1.22×, too close to call at a glance).
   `rem` here is the ROOT font size (16px) — the base raise happened on <body>,
   so nothing expressed in rem scaled with it. That is the whole bug.
   Re-pitched so the ladder is legible at the new base: 32 / 25.6 / 22.4 / 19px
   at 1375px = h3 1.35×, h4 1.18× of body.

   THE CLAMP *MINIMA* MATTER AS MUCH AS THE MAXIMA, and the obvious values do
   not work. The body base is itself a clamp with a 1.0625rem (17px) floor, so
   at 375px the body is at ITS minimum at the same moment the headings are at
   theirs — and the ratio at that point is whatever the two floors happen to
   give. With h3 at 1.3rem and h4 at 1.15rem that is 1.22× and **1.08×**: the
   same invisible h4 this fix exists to remove, reappearing on mobile, which is
   where most blog traffic actually is. The floors are therefore set from the
   ratio backwards — h4 1.2rem = 1.13× and h3 1.4rem = 1.31× against a 17px
   body — rather than from the desktop curve forwards.

   NOTE, so nobody re-measures this and thinks the h2 rule is broken: on classic
   posts `h2` computes to 32px, not the 36px this clamp allows, because the
   THEME's own `h2` rule outranks it — Layer 1 is `:where()`, specificity 0, by
   design (see the cascade note at the top of the file), so any theme or
   Elementor rule wins. That is the intended behaviour and the ladder still
   reads correctly: 32 / 25.6 / 22.4 / 19. */
:where(.ilab-apothecary .internal-content-wrap h2) { font-size: clamp(1.6rem, 1.2rem + 1.7vw, 2.25rem); }
:where(.ilab-apothecary .internal-content-wrap h3) { font-size: clamp(1.4rem, 1.1rem + .8vw, 1.6rem); line-height: 1.18; }
:where(.ilab-apothecary .internal-content-wrap h4) { font-size: clamp(1.2rem, 1.05rem + .45vw, 1.4rem); line-height: 1.25; }

:where(.ilab-apothecary .internal-content-wrap p) { line-height: 1.65; margin: 0 0 1.15em; text-wrap: pretty; }
:where(.ilab-apothecary .internal-content-wrap li) { line-height: 1.6; margin-bottom: .5em; }
:where(.ilab-apothecary .internal-content-wrap ol,
       .ilab-apothecary .internal-content-wrap ul) { margin: 0 0 1.3em; }

:where(.ilab-apothecary .internal-content-wrap strong,
       .ilab-apothecary .internal-content-wrap b) { font-weight: 650; }


/* ============================================================================
   §5  LAYER 2 — BRAND OVERRIDES
   Deliberately specific. Restricted to font-family, palette, link colour and
   heading weight — the four things that have to be consistent across 134 posts
   built by two different editors.
   ========================================================================= */

/* Font families. `.elementor-kit-6 h1{font-family:"Mulish"}` is 0,1,1; the
   compound below is 0,2,1 so it wins without depending on enqueue order.
   The plain `.ilab-apothecary h1` twin keeps this working if the kit ID ever
   changes (it then wins on source order instead). */
.ilab-apothecary.elementor-kit-6 h1, .ilab-apothecary h1,
.ilab-apothecary.elementor-kit-6 h2, .ilab-apothecary h2,
.ilab-apothecary.elementor-kit-6 h3, .ilab-apothecary h3,
.ilab-apothecary.elementor-kit-6 h4, .ilab-apothecary h4,
.ilab-apothecary.elementor-kit-6 h5, .ilab-apothecary h5,
.ilab-apothecary.elementor-kit-6 h6, .ilab-apothecary h6 {
  font-family: var(--ilab-display);
  font-weight: 460;
  color: var(--ilab-forest);
}
.ilab-apothecary.elementor-kit-6 h1, .ilab-apothecary h1 { font-weight: 480; }

.ilab-apothecary.elementor-kit-6,
.ilab-apothecary.elementor-kit-6 a { font-family: var(--ilab-body); }

/* Links. The theme paints every in-article link `#FB5C71` via
   `.blog-details-area a` (0,1,1). `.ilab-apothecary .blog-details-area a` is
   0,2,1 and wins. `--ilab-clay-deep` is 5.70:1 on bone; plain `--ilab-clay`
   is only 4.29:1 and would fail AA at body size. */
.ilab-apothecary .blog-details-area a,
.ilab-apothecary .internal-content-wrap a {
  color: var(--ilab-clay-deep);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  text-decoration-color: color-mix(in srgb, var(--ilab-clay) 45%, transparent);
  transition: color .18s ease, text-decoration-color .18s ease;
}
.ilab-apothecary .blog-details-area a:hover,
.ilab-apothecary .internal-content-wrap a:hover {
  color: var(--ilab-forest);
  text-decoration-color: var(--ilab-forest);
}

/* Body prose colour. Theme sets nothing, so 0,2,0 is ample.

   ROUND 3 (F4). Was `--ilab-ink-soft` = 6.93:1 on bone. That token is the kit's
   SECONDARY colour — captions, notes, meta — and it had been made the primary
   reading surface for ~25 000px of article, while `p:first-of-type` was
   special-cased to `--ilab-ink` at 13.59:1. The reader got one generous
   paragraph and then a permanent step down in BOTH size and contrast, exactly
   where they decide whether to stay. 6.93 clears AA on paper and fails the real
   test, which is twenty-five minutes. Promoted to `--ilab-ink` (13.59:1 bone /
   14.67:1 paper / 12.42:1 bone-deep). `--ilab-ink-soft` keeps its proper job:
   captions, meta, microcopy, the CTA sub-line. */
.ilab-apothecary .internal-content-wrap p,
.ilab-apothecary .internal-content-wrap li,
.ilab-apothecary .internal-content-wrap td { color: var(--ilab-ink); }


/* ============================================================================
   §6  HERO  —  `.blog-details-hero`
   ----------------------------------------------------------------------------
   Three problems solved at once:

   1. THE INLINE <style> IN THE BODY. `single.php` prints
      `.blog-details-hero{background-image:url(…)}` (0,1,0) at ~char 51 500,
      after every head stylesheet. We never touch `background-image` — the
      photo is the content. Everything we add is either a higher-specificity
      property on the element (`background-color`, sizing) or a pseudo-element
      layer, so the cascade fight never happens.

   2. WILDLY MIXED IMAGE QUALITY. 134 featured images ranging from 300px stock
      to 1500px shots, portrait and landscape, blown-out and murky. They are
      normalised by (a) a fixed aspect band with `background-size:cover`,
      (b) a forest-tinted scrim whose *minimum* alpha (.74) still yields
      5.94:1 for the title against a pure-white photo, and (c) the same paper
      grain the page uses, which pulls JPEG mush into the brand's texture.

   3. NO FEATURED IMAGE (post 35615, the only one). No inline <style> is
      printed at all, so `background-image` is `none` and our
      `background-color` + radial sage bloom + grain become the design rather
      than a hole. Nothing else changes: identical scrim, identical grain,
      identical type — it reads as a deliberate variant.
   ========================================================================= */

.ilab-apothecary .blog-details-hero {
  position: relative;
  isolation: isolate;
  display: grid;
  align-items: end;
  /* ROUND 3 (F3) — THE FOLD.
     Was min-height clamp(340px, 44vw, 540px) + padding-top clamp(96px, 12vw,
     150px). Measured at 1375×706 that put the first line of article text 644px
     into the document — i.e. a reader who arrived from Google with a specific
     question saw a title and nothing else. And we were spending 540px of the
     only screen we are guaranteed on an image the design deliberately hides:
     the scrim runs .74–.92 alpha precisely BECAUSE the 134 featured images are
     poor. Paying the fold to show something we made 80% invisible is the worst
     trade in the file.
     The band is now sized to the type it carries, not to the photo behind it.
     Mobile was already fine (401px into 812px) and only improves: at 375px the
     padding-top drops 96→74px and nothing else in the mobile stack moves. */
  min-height: clamp(190px, 20vw, 280px);
  /* ROUND 3 (G8): was `clamp(74px, 5vw, 92px)` — a pair of literals that had to
     be kept in step with the sticky header by hand. The band starts underneath
     a `position: sticky` header, so the first thing its padding has to buy is
     the header's own height; everything above that is breathing room. Now
     stated that way. Resolves to 74px at 375, ~78px at 1375, ~92px at 2560 —
     i.e. the same band as before, but derived rather than guessed. */
  padding-top: calc(var(--ilab-header-h) + clamp(1.5rem, 2vw, 2.6rem));
  padding-bottom: clamp(1.5rem, 2.5vw, 2.2rem);
  margin-bottom: clamp(1.8rem, 3.5vw, 2.8rem);
  background-color: var(--ilab-forest-deep);   /* the no-image floor */
  background-repeat: no-repeat;
  color: var(--ilab-bone);
  overflow: hidden;
}

/* The scrim. Sits above the featured image, below the copy. Minimum alpha is
   tuned to the worst realistic case (a pure-white photo) — see NOTES.md §8. */
.ilab-apothecary .blog-details-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(120% 90% at 22% 8%, rgba(94,122,104,.42), transparent 62%),
    linear-gradient(180deg,
      rgba(20,39,31,.74) 0%,
      rgba(20,39,31,.80) 45%,
      rgba(20,39,31,.92) 100%);
}

/* Grain + a hairline horizon that ties the band to the bone page below. */
.ilab-apothecary .blog-details-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--ilab-grain);
  opacity: .9;
  box-shadow: inset 0 -1px 0 rgba(182,138,46,.55);
}

/* The theme wraps the copy in a black `rgba(26,26,26,.79)` box. That box is the
   single ugliest thing on the current blog; the full-bleed scrim above replaces
   it, so we neutralise it rather than restyle it. 0,1,0 → 0,2,0.

   ROUND 3 (F3): the wrap is LEFT-aligned. A centred title over a centred mono
   meta row in a centred dark band is the 2019 magazine-hero default; left-set
   type also puts the H1's first character on the same optical axis as the first
   character of the article's first paragraph, which is what makes the band read
   as the top of a page rather than as a separate poster.
   NOTE the cascade detail: `.col-12` above this element carries Bootstrap's
   `.text-center`, which IS `!important`. We are not fighting it — we declare
   `text-align` on a DIFFERENT element (this one), and a direct declaration
   always beats an inherited value regardless of the parent's !important.

   ROUND 3 (G7) — THE OPTICAL-AXIS CLAIM ABOVE WAS FALSE, AND IS NOW TRUE.
   It was written as a justification and never measured. `max-width: 46rem`
   (736px) centred in the column, against prose at `--ilab-measure` (38rem =
   608px) centred in the SAME column, puts the H1 exactly (736 − 608) / 2 =
   64px to the left of the first paragraph — measured at 1375 and at 2560, 64px
   at both, because the offset is a constant of the two widths and not of the
   viewport. The two elements share a Bootstrap `.container > .row > .col`
   (single.php nests the hero copy inside one just as it does the article), so
   the fix is not a full-bleed padding calculation — it is simply to give the
   hero wrap the article's own measure. The delta is then structurally 0 at
   every viewport width with no arithmetic and no magic numbers, and the
   sentence above becomes a description instead of an assertion.
   The cost is 128px of title width, which the G10 title-length scale below
   more than repays on exactly the long titles that needed it. */
.ilab-apothecary .blog-details-hero-wrap {
  background-color: transparent;
  padding: 0;
  max-width: var(--ilab-measure);
  margin-inline: auto;
  text-align: left;
}

/* CATEGORY BADGE — `.post-category`
   Theme paints it solid #FB5C71. We cannot read the term in CSS, and 13 posts
   print "Uncategorized" while 37 have more than one category and print
   whichever sorts first alphabetically. A loud pill would shout a wrong or
   meaningless word. So the badge becomes a quiet outlined mono chip: legible,
   never a claim. The data fix belongs in PHP — see NOTES.md §6.
   Contrast: bone text on the tinted worst-case scrim = 4.73:1. */
.ilab-apothecary .blog-details-hero-wrap .post-category {
  display: inline-block;
  margin: 0 0 1.15rem;
  padding: .34rem .82rem;
  border: 1px solid rgba(246,241,230,.38);
  border-radius: 999px;
  background: rgba(246,241,230,.10);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  font-family: var(--ilab-mono);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ilab-bone);
}
/* If the PHP filter returns no term, don't leave a floating empty pill. */
.ilab-apothecary .blog-details-hero-wrap .post-category:empty { display: none; }

/* THE FREE WIN. The theme only ever styles `h2.internal-page-title`
   (theme-style.css:561, :3111, :3307; theme-responsive.css:115) but single.php
   emits `<h1 class="internal-page-title">`. The h1 is therefore completely
   unstyled today — we own it with no conflict at all. */
.ilab-apothecary .blog-details-hero-wrap h1.internal-page-title {
  font-family: var(--ilab-display);
  font-weight: 480;
  font-size: clamp(1.95rem, 1.2rem + 2.6vw, 3rem);
  line-height: 1.05;
  letter-spacing: -.018em;
  font-variation-settings: "opsz" 130, "SOFT" 30, "WONK" 0;
  color: var(--ilab-bone);
  margin: 0 0 1.3rem;
  text-wrap: balance;
  overflow-wrap: break-word;      /* 96-character titles exist. See §13. */
  /* ROUND 3 (F3): `text-shadow: 0 1px 24px rgba(20,39,31,.35)` REMOVED. The
     scrim already carries the contrast (bone on the lightest realistic
     composite = 5.94:1), so the shadow was buying nothing measurable and
     costing the letterforms their edge — a 24px blur under 58px Fraunces is
     exactly the halo that dates a hero. Contrast belongs in the scrim, which
     is tunable and testable; not in a blur under the glyphs. */
}

/* ROUND 3 (G10) — THE TITLE-LENGTH CAP. `clamp()` is viewport-aware and
   title-length-BLIND, which is the wrong axis for this archive: the 2024 bulk
   publishing run produced routinely 90+ character unpunctuated Title Case
   headlines, so the long title is the common case here, not the edge case.
   Measured before this rule, at 1375×706:
     · 66-char title  → 3 lines, first paragraph 478px (68% of the fold)
     · 94-char title  → 4 lines, first paragraph 539px (76%), hero 384→445px
     · same 94 chars on the uncategorized plate → 5 lines
   i.e. the F3 fold fix only delivered on short titles, and the posts that most
   need the fold are the ones that lost it.
   `ilab-blog-skin.php` adds `ilab-t--long` (>52 characters) and `ilab-t--xlong`
   (>72) to `body_class` — it already filters `body_class` for the scope class,
   so this costs one `strlen()` and no new hook. The three steps below are a
   deliberate ladder rather than a formula: each keeps the H1 the loudest thing
   on the page while giving a long headline back the line it was buying with
   the reader's fold.
   `line-height` opens from 1.05 to 1.16 on both long tiers: 1.05 is a display
   leading, correct for one or two lines and claustrophobic across four. */
.ilab-apothecary.ilab-t--long .blog-details-hero-wrap h1.internal-page-title {
  font-size: clamp(1.8rem, 1.1rem + 2vw, 2.45rem);
  line-height: 1.16;
}
.ilab-apothecary.ilab-t--xlong .blog-details-hero-wrap h1.internal-page-title {
  font-size: clamp(1.65rem, 1rem + 1.6vw, 2.1rem);
  line-height: 1.16;
}

/* DATE + AUTHOR. The `<i class="far fa-clock">` renders as nothing because no
   Font Awesome loads anywhere on the front end (header.php's kit <script> is
   bypassed by Elementor's theme-builder header). We restore the glyph with a
   masked inline SVG — no icon font, no extra request, inherits currentColor. */
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;   /* ROUND 3 (F3) — follows the left-set wrap */
  gap: .35rem 1rem;
  font-family: var(--ilab-mono);
  font-size: .74rem;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(246,241,230,.92);
}
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author > div { padding: 0; }
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author .date {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  letter-spacing: .1em;
  font-weight: 500;
}
/* The author is a name, not a label, so it is the one item in this mono row
   that keeps its own casing. On live it currently reads "by admin_david" —
   that is a user-nickname data fix in wp-admin, not CSS. See NOTES.md §6c. */
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author .author {
  position: relative;
  padding-left: 1rem;
  text-transform: none;
  letter-spacing: .04em;
}
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author .author::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: 5px; height: 5px;
  margin-top: -2.5px;
  border-radius: 999px;
  /* ROUND 3 (F17): `--ilab-ochre` here is 2.12:1 against the scrim's lightest
     realistic composite and fails WCAG 1.4.11's 3.0 for a meaningful non-text
     graphic. `--ilab-ochre-lite` is 3.23:1 there / 8.40:1 at the dark end.
     Hero-only — this token is 2.0:1 on bone and must never leave this band. */
  background: var(--ilab-ochre-lite);
}
.ilab-apothecary .blog-details-hero-wrap .post-date-and-author i {
  display: inline-block;
  width: 13px; height: 13px;
  background-color: var(--ilab-ochre-lite);   /* see F17 note above */
  -webkit-mask: var(--ilab-icon-clock) center / contain no-repeat;
  mask: var(--ilab-icon-clock) center / contain no-repeat;
}
.ilab-apothecary {
  --ilab-icon-clock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm0 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16Zm-.9 3v5.6l4.3 2.5.9-1.5-3.7-2.1V7h-1.5Z'/%3E%3C/svg%3E");
}

@media (max-width: 767px) {
  /* theme-responsive.css:115 sets `display:block` here for the h2 case. */
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author { display: flex; }
}


/* ============================================================================
   §7  ARTICLE BODY — CLASSIC (Gutenberg) POSTS
   All 54 classic posts are 2024-03 → 2024-05. Markup: `p.wp-block-paragraph`,
   `h2/h3.wp-block-heading`, `figure.wp-block-image > img + figcaption
   .wp-element-caption`. Discriminated with `body:not(.elementor-page)`.
   ========================================================================= */

.ilab-apothecary .blog-details-area { padding: 0 0 var(--ilab-section-y); }

/* The editorial column. Only on classic posts — Elementor posts carry their own
   multi-column section layouts and must not be squeezed into one measure. */
.ilab-apothecary:not(.elementor-page) .internal-content-wrap {
  max-width: var(--ilab-measure);
  margin-inline: auto;
  /* ROUND 3 (F6). Quotation marks and hyphens hang into the margin instead of
     denting the measure. Safari has had this for a decade, Chromium 130+ has it
     now; everywhere else it is simply ignored. Free where supported. */
  hanging-punctuation: first last;
}

/* ROUND 3 (F11). The working half of the sticky-header landing fix. Google's
   featured-snippet deep links use scroll-to-text fragments (`#:~:text=`), which
   scroll to a text range inside one of these elements — and land it under the
   sticky header. `scroll-margin-top` on the element itself needs no `:has()`
   and no scroll-container reasoning, so it is the one that works everywhere.
   Paragraphs get it too: a snippet quote is usually a sentence, not a heading.

   ROUND 3 (G8): 6rem → the derived offset. Same reason as §1/§2 — 96px was
   nearly twice the 50px header, so every snippet landing over-corrected. */
.ilab-apothecary .internal-content-wrap :is(h2, h3, h4, p) { scroll-margin-top: var(--ilab-scroll-offset); }

/* Section counters. `01 —` set in mono ochre, hung in the left gutter on wide
   viewports and inline above the heading on narrow ones. This is the skin's
   signature move and it costs nothing in markup: WordPress already emits the
   h2s, and the counter is scoped to the content wrap so the CTA/related
   headings (also h2) are excluded explicitly below.

   ROUND 3 (F8) — CLASSIC POSTS ONLY. Both rules below were missing the
   `:not(.elementor-page)` gate that the drop cap and the measure correctly
   carry, so the numerals, the top rule and the 2.6em top margin were bleeding
   onto ~80 Elementor posts — where an `h2` is a hand-placed design element
   inside a bespoke section, often several per row, and numbering them asserts a
   sequence that does not exist. Anything that changes the look of the 80
   Elementor posts has to be deliberate; this one was not. */
.ilab-apothecary .internal-content-wrap { counter-reset: ilab-sec; }

.ilab-apothecary:not(.elementor-page) .internal-content-wrap h2:not(.ilab-cta__h):not(.ilab-related__h) {
  position: relative;
  margin-top: 2.6em;
  padding-top: .9rem;
  border-top: 1px solid var(--ilab-line);
}
.ilab-apothecary:not(.elementor-page) .internal-content-wrap h2:not(.ilab-cta__h):not(.ilab-related__h)::before {
  counter-increment: ilab-sec;
  content: counter(ilab-sec, decimal-leading-zero);
  display: block;
  margin-bottom: .5rem;
  font-family: var(--ilab-mono);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .18em;
  color: var(--ilab-ochre-ink);
}
@media (min-width: 1040px) {
  .ilab-apothecary:not(.elementor-page) .internal-content-wrap h2:not(.ilab-cta__h):not(.ilab-related__h)::before {
    position: absolute;
    left: -4.2rem;
    top: 1.15rem;
    margin: 0;
    text-align: right;
    width: 3rem;
  }
}

.ilab-apothecary .internal-content-wrap h3 {
  margin-top: 2em;
  color: var(--ilab-forest-deep);
}

/* DROP CAP. Classic posts only: the first paragraph is a direct child of
   `.internal-content-wrap`, so `:first-of-type` is reliable. On Elementor posts
   the opening paragraph is buried three containers deep and there is no CSS
   selector for "first paragraph in document order" — see NOTES.md §5.

   ROUND 3 (F4): the `font-size: 1.08em` + `color: --ilab-ink` block that used
   to sit under this rule is GONE. It made paragraph one bigger and darker than
   the 25 000px that followed — a step DOWN in both size and contrast exactly
   where the reader decides whether to stay. Body prose is now `--ilab-ink` at
   the raised base throughout (§5, §2), so the first paragraph needs no
   promotion: the drop cap alone marks the opening, which is the whole point of
   a drop cap. The `::first-letter` rule is untouched. */
.ilab-apothecary:not(.elementor-page) .internal-content-wrap > p.wp-block-paragraph:first-of-type::first-letter {
  float: left;
  font-family: var(--ilab-display);
  font-weight: 500;
  font-size: 3.9em;
  line-height: .84;
  padding: .06em .12em 0 0;
  margin: .04em .04em 0 0;
  color: var(--ilab-forest);
  font-variation-settings: "opsz" 144, "SOFT" 40, "WONK" 1;
}

/* ROUND 3 (F6) — PROGRESSIVE ENHANCEMENT.
   The float above is a hand-tuned approximation of a drop cap: the sink depends
   on the `line-height: .84` fudge holding, and it drifts the moment the base
   size or the leading changes. `initial-letter` is the real property — the UA
   aligns the cap height to the first line and the baseline to the third,
   exactly, at any body size.
   It CANNOT be merged into the rule above: `initial-letter` implies its own
   float behaviour, so declaring both leaves the result up to whichever the UA
   resolves last. So it goes in its own `@supports`, which unwinds the fudge
   (float, em size, line-height, the nudge margins) and lets the property do it.
   Chromium 110+ and Safari take this branch; Firefox keeps the float fallback,
   which is why the fallback stays exactly as it was rather than being deleted. */
@supports (initial-letter: 3) or (-webkit-initial-letter: 3) {
  .ilab-apothecary:not(.elementor-page) .internal-content-wrap > p.wp-block-paragraph:first-of-type::first-letter {
    -webkit-initial-letter: 3 3;
    initial-letter: 3 3;
    float: none;
    font-size: inherit;
    line-height: inherit;
    padding: 0 .1em 0 0;
    margin: 0;
  }
}

/* LISTS. The theme draws a 10px #FB5C71 circle via
   `.blog-details-area .internal-content-wrap ul li:before` (0,3,2). Ours is
   0,4,2 and replaces it with a sage rule — quieter, and it aligns to the
   x-height instead of floating. */
.ilab-apothecary .blog-details-area .internal-content-wrap ul { padding-left: 1.5rem; }
/* ROUND 3 (F21): the dash sets `top`/`left` but never declared `position`, and
   the `li` never declared `position: relative` — both were being borrowed from
   the theme rule this one replaces. If the theme's `ul li{position:relative}`
   or its `:before{position:absolute}` is ever dropped or re-scoped, every dash
   in every list silently collapses to a static inline box at the start of the
   line. Both are now declared here, where they are used. */
.ilab-apothecary .blog-details-area .internal-content-wrap ul > li { position: relative; }
.ilab-apothecary .blog-details-area .internal-content-wrap ul li::before {
  content: "";
  position: absolute;
  background-color: var(--ilab-sage);
  width: 11px;
  height: 2px;
  top: .72em;
  left: -1.5rem;
  border-radius: 2px;
}
.ilab-apothecary .blog-details-area .internal-content-wrap ol {
  padding-left: 1.6rem;
  list-style: none;
  counter-reset: ilab-ol;
}
.ilab-apothecary .blog-details-area .internal-content-wrap ol > li {
  position: relative;
  counter-increment: ilab-ol;
}
.ilab-apothecary .blog-details-area .internal-content-wrap ol > li::before {
  content: counter(ilab-ol) ".";
  position: absolute;
  left: -1.6rem;
  top: 0;
  font-family: var(--ilab-mono);
  font-size: .8em;
  font-weight: 600;
  color: var(--ilab-ochre-ink);
}

/* FIGURES. No forced aspect ratio here — several of these images are charts and
   tables, and cropping them would destroy information. Only the hero and the
   index cards get a fixed band. */
.ilab-apothecary .internal-content-wrap figure.wp-block-image,
.ilab-apothecary .internal-content-wrap figure { margin: 2rem 0 2.2rem; }
.ilab-apothecary .internal-content-wrap figure img,
.ilab-apothecary .internal-content-wrap img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--ilab-radius-sm);
  border: 1px solid var(--ilab-line);
  box-shadow: var(--ilab-shadow-sm);
}
/* ROUND 3 (G6) — THE FURNITURE WAS STILL ON THE 17px-ERA `rem` SCALE.
   `.72rem` is 11.52px whatever the body does, because `rem` is the ROOT size
   and F4 raised the BODY. Against 19px prose that is 0.61× — small enough that
   a caption on a chart (and several of these figures ARE charts) stops being
   readable for exactly the audience a health site cannot afford to lose.
   Expressed in `em` so it is a RATIO of the prose it sits under and can never
   fall out of step with the base again: .82em ≈ 15.6px, 0.82× body. */
.ilab-apothecary .internal-content-wrap figcaption,
.ilab-apothecary .internal-content-wrap .wp-element-caption {
  margin-top: .7rem;
  font-family: var(--ilab-mono);
  font-size: .82em;
  letter-spacing: .04em;
  line-height: 1.45;
  color: var(--ilab-ink-soft);
  text-align: center;
}

/* Blockquotes → apothecary pull-quote. */
.ilab-apothecary .internal-content-wrap blockquote {
  margin: 2rem 0;
  padding: .2rem 0 .2rem 1.4rem;
  border-left: 3px solid var(--ilab-sage);
  font-family: var(--ilab-display);
  font-size: 1.22em;
  font-weight: 400;
  font-style: italic;
  line-height: 1.4;
  color: var(--ilab-forest);
}
.ilab-apothecary .internal-content-wrap blockquote p { color: inherit; font-size: inherit; }

/* Tables. Woo/theme ship no article-table styling; several posts contain one.
   Scroll container prevents 375px overflow.

   ROUND 3 (G6): `font-size: .95rem` rendered 15.2px — 0.80× the 19px prose,
   for the same root-vs-body reason as the captions above. On THIS site the
   tables are not decoration: the symptom/food comparison tables are the
   payload a reader came for, and setting the payload smaller than the prose
   around it inverts the hierarchy. 1rem (16px) is the floor; the type is
   deliberately a shade tighter than the prose because a table is scanned in
   columns, not read in a line, but it is no longer a step DOWN in legibility
   from the body copy that introduces it. */
.ilab-apothecary .internal-content-wrap table {
  width: 100%;
  border-collapse: collapse;
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-line);
  border-radius: var(--ilab-radius);
  overflow: hidden;
  font-size: 1rem;
  margin: 1.8rem 0;
}
.ilab-apothecary .internal-content-wrap th,
.ilab-apothecary .internal-content-wrap td {
  padding: .8rem 1rem;
  text-align: left;
  border-bottom: 1px solid var(--ilab-line);
  vertical-align: top;
  line-height: 1.45;
}
.ilab-apothecary .internal-content-wrap thead th {
  background: var(--ilab-forest);
  color: var(--ilab-bone);   /* ROUND 3 (F21): was a stray hardcoded #EFF3EC —
                                the only off-palette literal in the file. Bone
                                on forest is 8.58:1. */
  font-family: var(--ilab-body);
  font-weight: 650;
}
.ilab-apothecary .internal-content-wrap .wp-block-table { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.ilab-apothecary .internal-content-wrap hr {
  border: 0;
  height: 1px;
  background: var(--ilab-line);
  margin: 2.4rem 0;
}


/* ============================================================================
   §8  ARTICLE BODY — ELEMENTOR POSTS  (80 posts, 2020-05 → 2023-01)
   `the_content()` returns a full `.elementor` tree. We touch structure as
   little as possible: the per-widget CSS on these posts is deliberate design.
   ========================================================================= */

/* Cap the measure on full-width text widgets only. Two-column sections
   (`.elementor-col-50`) keep their designed layout untouched. */
.ilab-apothecary.elementor-page .internal-content-wrap
  .elementor-col-100 .elementor-widget-text-editor > .elementor-widget-container {
  max-width: var(--ilab-measure);
  margin-inline: auto;
}

:where(.ilab-apothecary .internal-content-wrap .elementor-widget-text-editor p) {
  line-height: 1.65;
  margin-bottom: 1.15em;
}

/* Elementor headings inherit the brand from §5. The one thing we correct is
   the DUPLICATE H1: post 2623 (and most 2020–21 posts) put an
   `h1.elementor-heading-title` inside the content while single.php already
   printed one in the hero. We can't delete it in CSS, but we can stop it
   competing visually. The SEO fix needs a template/Elementor edit — NOTES §6. */
.ilab-apothecary.elementor-page .internal-content-wrap h1.elementor-heading-title {
  font-size: clamp(1.6rem, 1.2rem + 1.7vw, 2.25rem);
  font-weight: 460;
  margin-top: 0;
}

/* Elementor buttons inside articles → apothecary pill. `.elementor-button`
   base is 0,1,0 in Elementor's frontend.min.css; ours is 0,3,0. */
.ilab-apothecary .internal-content-wrap .elementor-button-wrapper .elementor-button {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  background: var(--ilab-clay);
  color: var(--ilab-on-clay);
  border-radius: 999px;
  padding: .92rem 1.7rem;
  font-family: var(--ilab-body);
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -.005em;
  text-decoration: none;
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 4px 12px rgba(20,39,31,.12);
  transition: transform .18s cubic-bezier(.2,.7,.3,1), background-color .18s ease, box-shadow .18s ease;
}
.ilab-apothecary .internal-content-wrap .elementor-button-wrapper .elementor-button:hover {
  background: var(--ilab-clay-deep);
  color: var(--ilab-on-clay);
  transform: translateY(-1px);
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 6px 16px rgba(20,39,31,.17);
}

/* Elementor images inside articles get the same paper framing as classic ones,
   but only where the widget hasn't been given its own border/radius. */
.ilab-apothecary .internal-content-wrap .elementor-widget-image img {
  border-radius: var(--ilab-radius-sm);
  border: 1px solid var(--ilab-line);
  box-shadow: var(--ilab-shadow-sm);
}

/* Accordions / toggles / tabs used on a few 2021 posts. */
.ilab-apothecary .internal-content-wrap .elementor-accordion .elementor-tab-title,
.ilab-apothecary .internal-content-wrap .elementor-toggle .elementor-tab-title {
  font-family: var(--ilab-display);
  font-weight: 460;
  color: var(--ilab-forest);
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-line);
  border-radius: var(--ilab-radius-sm);
}


/* ============================================================================
   §9  INJECTED FUNNEL CTA + RELATED READING  (`ilab-funnel.php`)
   Appended to $content at `the_content` priority 20, so these are the last
   children of `.internal-content-wrap`.

   `<style id="ilab-funnel-css">` is INLINE IN THE BODY and carries a
   `@media(prefers-color-scheme:dark)` block. Both its light and dark rules are
   0,1,0. Every selector below is 0,3,0 or higher, so it wins in both colour
   schemes without `!important` and without editing ilab-funnel.php.
   Target pattern: `.soft-cta` from the Honest Apothecary landing kit.
   ========================================================================= */

.ilab-apothecary .internal-content-wrap .ilab-cta {
  position: relative;
  isolation: isolate;
  margin: clamp(2.4rem, 5vw, 3.4rem) auto;
  max-width: var(--ilab-measure);
  padding: clamp(1.5rem, 3.5vw, 2.1rem) clamp(1.4rem, 3.5vw, 2.2rem);
  background-color: var(--ilab-paper);
  background-image: var(--ilab-grain);
  border: 1px solid var(--ilab-line);
  border-left: 3px solid var(--ilab-clay);
  border-radius: var(--ilab-radius);
  box-shadow: var(--ilab-shadow-md);
}
.ilab-apothecary .internal-content-wrap .ilab-cta__eyebrow {
  margin: 0 0 .7rem;
  font-family: var(--ilab-mono);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ilab-ochre-ink);
  display: inline-flex;
  align-items: center;
  gap: .55rem;
}
.ilab-apothecary .internal-content-wrap .ilab-cta__eyebrow::before {
  content: "";
  width: 26px;
  height: 1px;
  background: currentColor;
  opacity: .6;
}
.ilab-apothecary .internal-content-wrap .ilab-cta__h {
  margin: 0 0 .7rem;
  padding: 0;
  border: 0;
  font-family: var(--ilab-display);
  font-weight: 460;
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
  line-height: 1.12;
  letter-spacing: -.015em;
  color: var(--ilab-forest);
}
.ilab-apothecary .internal-content-wrap .ilab-cta__p {
  margin: 0 0 1.3rem;
  max-width: 34rem;
  line-height: 1.6;
  color: var(--ilab-ink-soft);
}

/* The button. `a[data-ilab-cta]` takes gold/silver/store; all three get the
   same terracotta pill so the article never has two competing CTA colours.

   ROUND 3 (F19) — THE HOVER POLICY, applied identically everywhere in this file
   (this button, the Elementor in-article button §8, the comment submit §10, the
   popup button §12, the share pills §10, the index cards §11).
   Was: `translateY(-2px)` (cards -3px) plus a COLOURED glow —
   `0 8px 20px rgba(190,79,46,.26)` resting, `0 12px 28px rgba(190,79,46,.34)`
   on hover. A terracotta glow underneath a terracotta button is a 2021 SaaS
   hover state: it makes the control look lit from inside, which is the opposite
   of a printed apothecary label, and it directly contradicts this file's own
   stated rule of "restraint, not decoration". It is also the single treatment
   most likely to date the page inside eighteen months.
   Now: the lift is halved (2px→1px, cards 3px→2px) so the affordance survives,
   and every shadow is the page's own neutral green-black ink at low alpha —
   the same ramp as `--ilab-shadow-*`. Nothing glows. */
.ilab-apothecary .internal-content-wrap .ilab-cta__btn,
.ilab-apothecary .internal-content-wrap a[data-ilab-cta] {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  background: var(--ilab-clay);
  color: var(--ilab-on-clay);
  border-radius: 999px;
  padding: .95rem 1.75rem;
  font-family: var(--ilab-body);
  font-weight: 700;
  font-size: 1.02rem;
  letter-spacing: -.005em;
  text-decoration: none;
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 4px 12px rgba(20,39,31,.12);
  transition: transform .18s cubic-bezier(.2,.7,.3,1), background-color .18s ease, box-shadow .18s ease;
}
.ilab-apothecary .internal-content-wrap .ilab-cta__btn::after,
.ilab-apothecary .internal-content-wrap a[data-ilab-cta]::after {
  content: "";
  width: 15px;
  height: 15px;
  flex: none;
  background-color: currentColor;
  -webkit-mask: var(--ilab-icon-arrow) center / contain no-repeat;
  mask: var(--ilab-icon-arrow) center / contain no-repeat;
  transition: transform .18s cubic-bezier(.2,.7,.3,1);
}
.ilab-apothecary .internal-content-wrap .ilab-cta__btn:hover,
.ilab-apothecary .internal-content-wrap a[data-ilab-cta]:hover {
  background: var(--ilab-clay-deep);
  color: var(--ilab-on-clay);
  transform: translateY(-1px);
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 6px 16px rgba(20,39,31,.17);
}
.ilab-apothecary .internal-content-wrap .ilab-cta__btn:hover::after,
.ilab-apothecary .internal-content-wrap a[data-ilab-cta]:hover::after { transform: translateX(3px); }

.ilab-apothecary .internal-content-wrap .ilab-cta__small {
  display: block;
  margin-top: 1rem;
  font-size: .8rem;
  line-height: 1.5;
  color: var(--ilab-ink-soft);
  max-width: 36rem;
}


/* ----------------------------------------------------------------------------
   §9A  `.ilab-band` — FULL-BLEED ALTERNATE SURFACE
        ** DEFINED, AVAILABLE, AND DELIBERATELY UNUSED. **
   ----------------------------------------------------------------------------
   WITHDRAWN IN ROUND 3 (G3). Added in the same round as F7, applied to the CTA,
   and removed from it again after measurement. The reasoning is recorded here
   rather than deleted, because the next person to notice that `--ilab-bone-deep`
   is tokenised "alternate band" and never used as one will otherwise rebuild
   exactly this and hit exactly these two problems.

   WHY IT WAS ADDED — and why that argument does not survive its own numbers.
   The case was "the article has exactly one non-bone surface, the `--ilab-paper`
   CTA card, and paper-on-bone is 1.079:1, a rounding error rather than a change
   of surface". True. But the band's own step, bone-deep-on-bone, measures
   1.094:1 — 1.4% larger. A critique that dismisses one step as invisible cannot
   then prescribe a step of the same size as the remedy. It is self-refuting,
   and the round-2 reviewer was right to say so.

   WHY REMOVING IT FROM THE CTA COST MORE THAN IT BOUGHT. `.ilab-band` sits
   after `.ilab-cta` at equal specificity, so promoting the CTA did not ADD a
   band on top of the card — it REPLACED the card. The paper surface, the 18px
   radius, the 3px clay left rule and `--ilab-shadow-md` all gave way, i.e. the
   four things that actually distinguished the one commercial block in a
   26 000px article were traded for a 1.09:1 change of tone. The CTA is now back
   to its §9 treatment and is the specimen of nothing.

   AND THE PAGE IS NOT MONOTONOUS. The claim in the withdrawn comment and in
   CRITIQUE-ROUND-1 E7 — "one texture, one value, top to bottom" — is simply
   false, and is corrected there too. On any classic post there is a full-bleed
   forest hero with a scrim, a sage bloom, grain and an ochre horizon; an ochre
   hairline and a hung mono numeral at every h2; figures framed in paper with
   borders and shadows; a sage-ruled blockquote; a forest-headed table; pill
   rows in bone-deep; and a terracotta CTA. Value contrast on this page is
   carried by ink, rules and the hero — not by paper tone, and it does not need
   to be.

   IF ANYTHING IS EVER PROMOTED TO A BAND, FIX THE BLEED FIRST (G4).
   The technique below is wrong as written. `box-shadow: 0 0 0 100vmax` paints a
   full-width field, but `border-block` is painted on the ELEMENT — whose box is
   the 608px measure column. Measured: a 608px hairline floating in a ~1360px
   band, top and bottom, which reads as a mistake rather than as a rule.
   The reason the honest technique was avoided — that `margin-inline: calc(50%
   - 50vw)` counts the classic scrollbar in `100vw` and overflows by 15–17px on
   Windows — no longer applies: F10 changed §2 from `overflow-x: hidden` to
   `overflow-x: clip`, which absorbs that overhang without making <body> a
   scroll container. So the correct shape today is
     `margin-inline: calc(50% - 50vw); padding-inline: calc(50vw - 50%);`
   which gives a real layout box the borders can span, and needs no
   `box-shadow`/`clip-path` pair at all.

   CONTRAST on the band, if it is ever used: ink 12.42:1, ink-soft 6.33:1,
   forest 7.83:1, ochre-ink 4.54:1 — every token that lands on it clears AA.
   ------------------------------------------------------------------------- */
.ilab-apothecary .internal-content-wrap .ilab-band {
  position: relative;
  isolation: isolate;
  max-width: var(--ilab-measure);
  margin-inline: auto;
  background-color: var(--ilab-bone-deep);
  background-image: var(--ilab-grain);
  clip-path: inset(0 -100vmax);
  border-radius: 0;
  border-inline: 0;
  border-block: 1px solid var(--ilab-line);
  /* Horizontal padding is zero BY DESIGN: the band's box is the measure column,
     so any inline padding would set the band's own text NARROWER than the prose
     above and below it and the eye would read it as a pull-out rather than as a
     change of paper. The breathing room is vertical only. */
  padding-inline: 0;
  padding-block: clamp(1.9rem, 4vw, 2.8rem);
  box-shadow: 0 0 0 100vmax var(--ilab-bone-deep);
}

/* NOTHING CARRIES `.ilab-band` (G3). The class is absent from all five post
   plates and from ilab-funnel.php's injected markup; grep before assuming
   otherwise. Left defined because the component is one small fix away from
   being correct and the reasoning above is worth more than the ~20 bytes it
   costs gzipped — but it is dead code today, and honest about it. */

/* RELATED READING → the `.related-link` pill row from the landing kit.
   Note the extra `.ilab-related` step needed to out-rank the theme's
   `.blog-details-area .internal-content-wrap ul li:before` pink dot. */
.ilab-apothecary .internal-content-wrap .ilab-related {
  max-width: var(--ilab-measure);
  margin: clamp(2.4rem, 5vw, 3.2rem) auto .5rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--ilab-line);
}
.ilab-apothecary .internal-content-wrap .ilab-related__h {
  margin: 0 0 1rem;
  padding: 0;
  border: 0;
  font-family: var(--ilab-mono);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ilab-ochre-ink);
}
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul {
  display: flex;
  flex-wrap: wrap;
  gap: .7rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li {
  margin: 0;
  max-width: 100%;
}
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li::before { display: none; }
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  /* ROUND 3 (F16): was `.58rem 1.05rem` → a 37px tall target. These are the
     LAST links on the page and the ones a satisfied reader is most likely to
     tap, on a phone, one-handed. `.8rem` block padding takes the pill to ~44px,
     the WCAG 2.5.5 (AAA) / iOS HIG figure, without changing the type or the
     pill's proportions noticeably. */
  padding: .8rem 1.15rem;
  font-size: .9rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ilab-forest);
  text-decoration: none;
  box-shadow: var(--ilab-shadow-sm);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a::before {
  content: "";
  width: 15px; height: 15px;
  flex: none;
  background-color: var(--ilab-clay);
  -webkit-mask: var(--ilab-icon-arrow) center / contain no-repeat;
  mask: var(--ilab-icon-arrow) center / contain no-repeat;
}
.ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a:hover {
  transform: translateY(-1px);
  box-shadow: var(--ilab-shadow-md);
  border-color: var(--ilab-line-strong);
  color: var(--ilab-forest);
}

/* ROUND 3 (G9) — THE PILLS ARE NOT PILLS AT 375px.
   Measured at 375: 345 × 65px. A 999px radius on a full-width, two-line block
   does not read as a pill — it reads as a rounded paragraph, and the row reads
   as three stacked boxes that happen to have curved ends. These titles are 40+
   characters ("Migraines, are yours caused by undiagnosed food intolerances?");
   they were never going to fit one line on a phone, and the pill shape is
   simply the wrong component once they don't.
   Below 560px the row becomes what it actually is — a stacked list of onward
   links: full-width rows, `--ilab-radius-sm`, the arrow pulled out to the left
   as a marker rather than an inline glyph, and a rule between them.
   The F16 touch target is preserved and improved: `.8rem` block padding on a
   two-line 1.3 line-height row is ~62px, well clear of the 44px floor. */
@media (max-width: 560px) {
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul {
    display: block;
    gap: 0;
  }
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li + li {
    border-top: 1px solid var(--ilab-line);
  }
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a {
    display: flex;
    align-items: flex-start;
    gap: .65rem;
    width: 100%;
    padding: .85rem .9rem;
    border-radius: var(--ilab-radius-sm);
    box-shadow: none;
    border-color: transparent;
    background: transparent;
  }
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a::before {
    margin-top: .18em;   /* optical alignment to the first line's cap height */
  }
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a:hover {
    transform: none;
    box-shadow: none;
    background: var(--ilab-paper);
    border-color: var(--ilab-line);
  }
}

.ilab-apothecary {
  --ilab-icon-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M13.2 5.2 12 6.4l4.7 4.7H4v1.8h12.7L12 17.6l1.2 1.2 6.8-6.8-6.8-6.8Z'/%3E%3C/svg%3E");
}


/* ============================================================================
   §10  POST META — TAGS, SHARE ROW, COMMENTS
   ----------------------------------------------------------------------------
   Two live defects handled here:

   · `.tags-rap` is EMPTY on 81 of 134 posts — the theme still renders an empty
     flex column next to a `border-top`. `:empty` collapses it (the div contains
     no whitespace, verified in the captured HTML).

   · THE SHARE ROW IS DEAD TWICE OVER. No Font Awesome loads anywhere on the
     front end, so `.fa-facebook` etc. render as nothing; and `main.js` has
     `// $('.prettySocial').prettySocial();` commented out, so all four anchors
     are inert `href="#"`. One of them is Google+ (dead since 2019).
     CSS can fix the *invisible* half — masked inline SVGs, no icon font, no
     extra request. It cannot fix the *inert* half: real share URLs need PHP or
     a few lines of JS. See NOTES.md §6.
   ========================================================================= */

.ilab-apothecary .post-meta-wrap {
  max-width: var(--ilab-measure);
  margin: clamp(2.4rem, 5vw, 3.4rem) auto 0;
  padding: 1.4rem 0 0;
  border-top: 1px solid var(--ilab-line);
}
.ilab-apothecary .post-meta-wrap .row { align-items: center; row-gap: 1rem; }
.ilab-apothecary .post-meta-wrap .tags-rap:empty { display: none; }

/* TAG PILLS. Theme paints them #F3F8F8 / #8B9094 with a #FB5C71 hover. */
.ilab-apothecary .post-meta-wrap .tags-rap ul {
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
}
.ilab-apothecary .post-meta-wrap .tags-rap ul a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  margin: 0;
  padding: .32rem .75rem;
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  background-color: var(--ilab-bone-deep);
  font-family: var(--ilab-mono);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ilab-ink-soft);
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}
.ilab-apothecary .post-meta-wrap .tags-rap ul a::before {
  content: "";
  width: 11px; height: 11px;
  flex: none;
  background-color: var(--ilab-sage);
  -webkit-mask: var(--ilab-icon-tag) center / contain no-repeat;
  mask: var(--ilab-icon-tag) center / contain no-repeat;
}
.ilab-apothecary .post-meta-wrap .tags-rap ul a:hover {
  background-color: var(--ilab-sage-soft);
  border-color: var(--ilab-line-strong);
  color: var(--ilab-forest);
}
.ilab-apothecary .post-meta-wrap .tags-rap ul a:hover::before { background-color: var(--ilab-forest); }

/* SHARE ROW. */
.ilab-apothecary .post-meta-wrap .post-share {
  display: flex;
  align-items: center;
  gap: .75rem;
  justify-content: flex-end;
}
.ilab-apothecary .post-meta-wrap .post-share strong {
  font-family: var(--ilab-mono);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ilab-ink-soft);
}
.ilab-apothecary .post-meta-wrap .post-share ul.rso-socials {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: 0;
}
.ilab-apothecary .post-meta-wrap .post-share ul.rso-socials li { display: inline-flex; }

/* Google+ has been dead since 2019. `:has()` removes the whole <li> so the
   flex gap closes; the `:nth-child(4)` twin is the fallback for the last
   browsers without `:has()` (single.php always prints exactly four items in a
   fixed order, so the positional selector is safe here). */
.ilab-apothecary .post-meta-wrap .rso-socials li:has(a[data-type="googleplus"]) { display: none; }
@supports not selector(:has(*)) {
  .ilab-apothecary .post-meta-wrap .rso-socials li:nth-child(4) { display: none; }
}

.ilab-apothecary .post-meta-wrap .post-share ul.rso-socials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin: 0;
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  background: var(--ilab-paper);
  box-shadow: var(--ilab-shadow-sm);
  color: var(--ilab-forest);
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease, transform .18s ease, border-color .18s ease;
}
.ilab-apothecary .post-meta-wrap .post-share ul.rso-socials a::before {
  content: "";
  width: 15px;
  height: 15px;
  background-color: currentColor;
  -webkit-mask: var(--ilab-share-icon) center / contain no-repeat;
  mask: var(--ilab-share-icon) center / contain no-repeat;
}
.ilab-apothecary .post-meta-wrap .post-share ul.rso-socials a:hover {
  background: var(--ilab-forest);
  border-color: var(--ilab-forest);
  color: var(--ilab-bone);
  transform: translateY(-1px);
}
.ilab-apothecary .rso-socials a[data-type="facebook"] { --ilab-share-icon: var(--ilab-icon-facebook); }
.ilab-apothecary .rso-socials a[data-type="twitter"]  { --ilab-share-icon: var(--ilab-icon-x); }
.ilab-apothecary .rso-socials a[data-type="linkedin"] { --ilab-share-icon: var(--ilab-icon-linkedin); }

.ilab-apothecary {
  --ilab-icon-tag: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M2 10.6V4a2 2 0 0 1 2-2h6.6a2 2 0 0 1 1.4.6l9.4 9.4a2 2 0 0 1 0 2.8l-6.6 6.6a2 2 0 0 1-2.8 0L2.6 12A2 2 0 0 1 2 10.6Zm5-4.1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z'/%3E%3C/svg%3E");
  --ilab-icon-facebook: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M13.5 21v-7.5H16l.4-3h-2.9V8.6c0-.87.24-1.46 1.49-1.46H16.5V4.46A20 20 0 0 0 14.2 4.3c-2.3 0-3.9 1.4-3.9 4v2.2H7.8v3h2.5V21h3.2Z'/%3E%3C/svg%3E");
  --ilab-icon-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.7 3h3.1l-6.8 7.8L22 21h-6.3l-4.9-6.4L5.1 21H2l7.3-8.3L2 3h6.4l4.5 5.9L17.7 3Zm-1.1 16.1h1.7L7.5 4.8H5.7l10.9 14.3Z'/%3E%3C/svg%3E");
  --ilab-icon-linkedin: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.94 8.5V21H3.06V8.5h3.88ZM5 3a2.25 2.25 0 1 1 0 4.5A2.25 2.25 0 0 1 5 3Zm4.5 5.5h3.72v1.7h.05a4.08 4.08 0 0 1 3.67-2.02C20.9 8.18 22 10.4 22 13.6V21h-3.88v-6.56c0-1.56-.03-3.57-2.18-3.57-2.18 0-2.51 1.7-2.51 3.46V21H9.5V8.5Z'/%3E%3C/svg%3E");
}

/* COMMENTS. */
.ilab-apothecary .comment-wrap {
  max-width: var(--ilab-measure);
  margin: clamp(2.4rem, 5vw, 3.4rem) auto 0;
}
.ilab-apothecary .comment-wrap .comment-reply-title {
  font-family: var(--ilab-display);
  font-weight: 460;
  font-size: clamp(1.3rem, 1.1rem + .8vw, 1.6rem);
  color: var(--ilab-forest);
  margin-bottom: .3rem;
}
.ilab-apothecary .comment-wrap .comment-notes {
  font-size: .85rem;
  color: var(--ilab-ink-soft);
  margin-bottom: 1.2rem;
}
.ilab-apothecary .comment-form { display: flex; flex-wrap: wrap; gap: .8rem; }
.ilab-apothecary .comment-form > .col-md-6 { flex: 1 1 min(100%, 15rem); max-width: 100%; padding: 0; }
.ilab-apothecary .comment-form > .col-md-12 { flex: 1 1 100%; padding: 0; }
.ilab-apothecary .comment-form input[type="text"],
.ilab-apothecary .comment-form input[type="email"],
.ilab-apothecary .comment-form textarea {
  width: 100%;
  padding: .8rem 1rem;
  font: inherit;
  /* ROUND 3 (G6) — iOS ZOOM. Was `.95rem` = 15.2px. Mobile Safari zooms the
     whole viewport when a form control under 16px receives focus, and it does
     not zoom back out on blur — so a reader who taps "Name" in the comment
     form is left on a horizontally-scrolled page for the rest of the session.
     The popup input was already protected at 16px for exactly this reason
     (§12); the comment form and the search field were not. 1rem is a floor,
     not a preference: do not "tidy" it back down. */
  font-size: 1rem;
  color: var(--ilab-ink);
  background: var(--ilab-paper);
  /* ROUND 3 (F9): was `--ilab-line-strong`, which resolves to #BECABF over
     paper = 1.63:1. On an input whose border is the ONLY thing distinguishing
     the field from the card behind it, that is a WCAG 1.4.11 failure (needs
     3.0) — the field is invisible to anyone with reduced contrast sensitivity,
     which on a health site is a meaningful share of the audience.
     `--ilab-line-strong` is unchanged and still correct for decorative card
     edges; only controls move to `--ilab-control-edge` (3.37:1 on paper). */
  border: 1px solid var(--ilab-control-edge);
  border-radius: var(--ilab-radius-sm);
}
.ilab-apothecary .comment-form input::placeholder,
.ilab-apothecary .comment-form textarea::placeholder { color: var(--ilab-sage-ink); }
/* ROUND 3 (G6). Checkboxes and submit buttons do not trigger the iOS focus-zoom
   (only text-entry fields do), but they were rendering at the UA default
   13.33px — a browser default, i.e. simply un-styled — and a submit label two
   thirds the size of the prose around it is the wrong signal on the one control
   that completes the action. Both are brought onto the 16px floor so the whole
   form answers "no control under 16px" without an exception list. */
.ilab-apothecary .comment-form input[type="checkbox"],
.ilab-apothecary .comment-form input[type="radio"] { font-size: 1rem; }
.ilab-apothecary .comment-form-cookies-consent {
  flex: 1 1 100%;
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  font-size: .85rem;
  color: var(--ilab-ink-soft);
}
.ilab-apothecary .comment-form .form-submit { flex: 1 1 100%; margin: 0; }
.ilab-apothecary .comment-form input#submit {
  background: var(--ilab-clay);
  color: var(--ilab-on-clay);
  border: 0;
  border-radius: 999px;
  padding: .9rem 1.7rem;
  font-family: var(--ilab-body);
  font-size: 1rem;        /* ROUND 3 (G6): was inheriting the UA's 13.33px */
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 4px 12px rgba(20,39,31,.12);
  transition: background-color .18s ease, transform .18s ease;
}
.ilab-apothecary .comment-form input#submit:hover { background: var(--ilab-clay-deep); transform: translateY(-1px); }


/* ============================================================================
   §11  JOURNAL INDEX + CATEGORY ARCHIVE
   Two different engines, two sets of selectors:
     /blog/        → Elementor archive template 683 + loop item 701
                     (`.e-loop-item`, `.elementor-loop-container.elementor-grid`)
     /category/*/  → the theme's archive.php + post-excerpt.php
                     (`.single-post-item`, `.post-large`, `.ppm-pagination`)
   ========================================================================= */

/* ---- shared journal furniture ---- */
.ilab-apothecary .ilab-journal-intro {
  max-width: 46rem;
  padding-block: clamp(2.4rem, 6vw, 4.2rem) clamp(1.6rem, 3vw, 2.4rem);
}
.ilab-apothecary .ilab-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--ilab-mono);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ilab-ochre-ink);
}
.ilab-apothecary .ilab-eyebrow::before {
  content: "";
  width: 26px;
  height: 1px;
  background: currentColor;
  opacity: .6;
}
.ilab-apothecary .ilab-journal-intro h1 {
  margin: .9rem 0 0;
  font-size: clamp(2.2rem, 1.4rem + 3.4vw, 3.6rem);
  font-weight: 480;
  line-height: 1.04;
  font-variation-settings: "opsz" 130, "SOFT" 30, "WONK" 0;
}
.ilab-apothecary .ilab-journal-intro p {
  margin: 1.1rem 0 0;
  max-width: 36rem;
  font-size: clamp(1.02rem, .98rem + .35vw, 1.16rem);
  line-height: 1.55;
  color: var(--ilab-ink-soft);
}

/* Category nav chips. Live, these are Elementor button widgets
   (`.elementor-widget-button .elementor-button`) inside the "TRENDING TOPICS"
   container; the same rule covers a hand-rolled `.ilab-chip` list. */
.ilab-apothecary .ilab-chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  padding-bottom: clamp(1.8rem, 4vw, 2.8rem);
  border-bottom: 1px solid var(--ilab-line);
}
.ilab-apothecary .ilab-chip,
.ilab-apothecary .elementor-location-archive .elementor-widget-button .elementor-button {
  display: inline-flex;
  align-items: baseline;
  gap: .45rem;
  padding: .42rem .9rem;
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  background: var(--ilab-paper);
  font-family: var(--ilab-mono);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--ilab-ink-soft);
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease, transform .18s ease;
}
.ilab-apothecary .ilab-chip:hover,
.ilab-apothecary .elementor-location-archive .elementor-widget-button .elementor-button:hover {
  background: var(--ilab-sage-soft);
  border-color: transparent;
  color: var(--ilab-forest);
  transform: translateY(-1px);
}
.ilab-apothecary .ilab-chip[aria-current="page"],
.ilab-apothecary .ilab-chip--on {
  background: var(--ilab-forest);
  border-color: var(--ilab-forest);
  color: var(--ilab-bone);
}
.ilab-apothecary .ilab-chip__n {
  font-size: .66rem;
  font-weight: 500;
  opacity: .62;
  font-variant-numeric: tabular-nums;
}

/* ---- /blog/ : Elementor loop grid ---- */
.ilab-apothecary .elementor-loop-container.elementor-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
  gap: clamp(1.1rem, 2.4vw, 1.7rem);
  padding-block: clamp(1.8rem, 4vw, 2.8rem);
}

.ilab-apothecary .elementor-loop-container .e-loop-item {
  display: flex;
  background-color: var(--ilab-paper);
  background-image: var(--ilab-grain);
  border: 1px solid var(--ilab-line);
  border-radius: var(--ilab-radius);
  overflow: hidden;
  box-shadow: var(--ilab-shadow-sm);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), box-shadow .2s ease, border-color .2s ease;
}
.ilab-apothecary .elementor-loop-container .e-loop-item:hover {
  transform: translateY(-2px);
  box-shadow: var(--ilab-shadow-md);
  border-color: var(--ilab-line-strong);
}
.ilab-apothecary .elementor-loop-container .e-loop-item > .e-con { width: 100%; }

/* CARD LAYOUT. Live, the loop item is a horizontal 33/66 split above 768px
   (`--width:33.3333%` on element 91b515c, `--flex-direction:row` on b141e8e,
   both from the in-body `<style id="loop-701">`). A vertical card reads far
   better in an auto-fill grid. We re-point Elementor's own layout custom
   properties rather than overriding the resolved declarations — the same
   mechanism Elementor uses — which needs 0,4,0 to beat the 0,3,0 inline rules. */
.ilab-apothecary .elementor-grid .e-loop-item > .e-con,
.ilab-apothecary .elementor-grid .e-loop-item .e-con .e-con {
  --flex-direction: column;
  --width: 100%;
  --container-widget-width: 100%;
  --container-widget-flex-grow: 0;
  --container-widget-align-self: initial;
}

/* IMAGE NORMALISATION. loop-701.css (injected inline, in the body) pins every
   card image to `height:141px` with
   `.elementor-701 .elementor-element.elementor-element-5b3d79c img` = 0,3,1,
   and a `.full-height img{…object-fit:cover}` custom-CSS block at 0,1,1.
   The selector below is 0,4,1 and beats both, regardless of source order.
   A single 3:2 band is what makes 134 mismatched featured images read as one
   publication. */
.ilab-apothecary .elementor-loop-container .e-loop-item
  .elementor-widget-theme-post-featured-image img {
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background-color: var(--ilab-sage-soft);   /* floor for missing/slow images */
  transition: transform .45s cubic-bezier(.2,.7,.3,1);
}
.ilab-apothecary .elementor-loop-container .e-loop-item:hover
  .elementor-widget-theme-post-featured-image img { transform: scale(1.035); }

.ilab-apothecary .elementor-loop-container .e-loop-item
  .elementor-widget-theme-post-featured-image { position: relative; overflow: hidden; }

.ilab-apothecary .elementor-loop-container .e-loop-item
  .elementor-widget-theme-post-title .elementor-heading-title {
  font-family: var(--ilab-display);
  font-weight: 460;
  font-size: clamp(1.12rem, 1rem + .5vw, 1.32rem);
  line-height: 1.16;
  letter-spacing: -.012em;
  color: var(--ilab-forest);
  text-wrap: balance;
}
.ilab-apothecary .elementor-loop-container .e-loop-item
  .elementor-widget-theme-post-title .elementor-heading-title a {
  color: inherit;
  text-decoration: none;
}
.ilab-apothecary .elementor-loop-container .e-loop-item:hover
  .elementor-widget-theme-post-title .elementor-heading-title a { color: var(--ilab-clay-deep); }

/* Card meta row — the post-info widget's terms list becomes mono chips.
   Loop item 701 emits the title first and the terms second. A category chip
   reads better ABOVE the title, and because the text container is an Elementor
   flex column we can reorder it in CSS instead of rebuilding the template. */
.ilab-apothecary .elementor-grid .e-loop-item .elementor-widget-post-info { order: -1; margin-block-end: 0; }
.ilab-apothecary .elementor-loop-container .e-loop-item .elementor-post-info {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ilab-apothecary .elementor-loop-container .e-loop-item .elementor-post-info .elementor-icon-list-item::after { display: none; }
.ilab-apothecary .elementor-loop-container .e-loop-item .elementor-post-info__terms-list-item {
  display: inline-block;
  /* ROUND 3 (F16): was `.2rem .55rem`, which at .64rem/normal computed to a
     22.8px tall target — under the 24px floor of WCAG 2.2 SC 2.5.8 (Target Size
     Minimum, AA). These chips are real category links sitting immediately above
     a card title, so a mis-tap costs the reader a wrong page. `.45rem` block
     padding clears 24px with margin to spare and does not change the chip's
     type, colour or radius. */
  padding: .45rem .7rem;
  border-radius: 999px;
  background: var(--ilab-bone-deep);
  font-family: var(--ilab-mono);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ilab-ink-soft);
  text-decoration: none;
}
.ilab-apothecary .elementor-loop-container .e-loop-item .elementor-post-info__terms-list-item:hover {
  background: var(--ilab-sage-soft);
  color: var(--ilab-forest);
}
.ilab-apothecary .ilab-card__excerpt {
  margin: 0;
  font-size: .92rem;
  line-height: 1.55;
  color: var(--ilab-ink-soft);
}
.ilab-apothecary .ilab-card__date {
  margin: 0;
  font-family: var(--ilab-mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ilab-sage-ink);
}

/* Pagination — missing entirely on /blog/ today (130 of 134 posts unreachable).
   Styled here so the sibling fix has somewhere to land. */
.ilab-apothecary .elementor-pagination,
.ilab-apothecary .ppm-pagination,
.ilab-apothecary .ilab-pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: .4rem;
  padding-block: clamp(1.8rem, 4vw, 2.6rem);
  border-top: 1px solid var(--ilab-line);
}
.ilab-apothecary .elementor-pagination .page-numbers,
.ilab-apothecary .ppm-pagination .page-numbers,
.ilab-apothecary .ilab-pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 .7rem;
  border: 1px solid var(--ilab-line);
  border-radius: 999px;
  background: var(--ilab-paper);
  font-family: var(--ilab-mono);
  font-size: .8rem;
  font-weight: 600;
  color: var(--ilab-ink-soft);
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}
.ilab-apothecary .elementor-pagination .page-numbers:hover,
.ilab-apothecary .ppm-pagination .page-numbers:hover,
.ilab-apothecary .ilab-pagination .page-numbers:hover {
  background: var(--ilab-sage-soft);
  border-color: transparent;
  color: var(--ilab-forest);
}
.ilab-apothecary .elementor-pagination .page-numbers.current,
.ilab-apothecary .ppm-pagination .page-numbers.current,
.ilab-apothecary .ilab-pagination .page-numbers.current {
  background: var(--ilab-forest);
  border-color: var(--ilab-forest);
  color: var(--ilab-bone);
}
.ilab-apothecary .ilab-pagination .page-numbers.dots {
  border-color: transparent;
  background: none;
}

/* Search widget on /blog/. */
.ilab-apothecary .elementor-search-form__input {
  width: 100%;
  padding: .85rem 1.1rem;
  font-family: var(--ilab-body);
  font-size: 1rem;   /* ROUND 3 (G6): was .98rem = 15.68px — under the 16px iOS
                        zoom threshold by a third of a pixel. See §10. */
  color: var(--ilab-ink);
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-control-edge);   /* ROUND 3 (F9) — see §10 */
  border-radius: 999px;
}
.ilab-apothecary .elementor-search-form__input::placeholder { color: var(--ilab-sage-ink); }

/* ---- /category/*/ : the theme's own archive cards ---- */
.ilab-apothecary .single-post-item {
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-line);
  border-radius: var(--ilab-radius);
  overflow: hidden;
  box-shadow: var(--ilab-shadow-sm);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), box-shadow .2s ease;
}
.ilab-apothecary .single-post-item:hover { transform: translateY(-2px); box-shadow: var(--ilab-shadow-md); }
.ilab-apothecary .single-post-item .post-content { background-color: transparent; padding: 1.15rem 1.25rem 1.35rem; }
.ilab-apothecary .single-post-item h2 {
  font-family: var(--ilab-display);
  font-weight: 460;
  font-size: clamp(1.1rem, 1rem + .5vw, 1.3rem);
  line-height: 1.16;
  color: var(--ilab-forest);
}
.ilab-apothecary .single-post-item span.post-category {
  display: inline-block;
  margin-bottom: .55rem;
  padding: .2rem .6rem;
  border-radius: 999px;
  background: var(--ilab-bone-deep);
  font-family: var(--ilab-mono);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ilab-ink-soft);
}
.ilab-apothecary .single-post-item img.wp-post-image {
  width: 100%;
  aspect-ratio: 3 / 2;
  height: auto;
  object-fit: cover;
  background-color: var(--ilab-sage-soft);
}
.ilab-apothecary .post-large .single-post-item img.wp-post-image { aspect-ratio: 21 / 9; height: auto; }
.ilab-apothecary .single-post-item .post-meta {
  font-family: var(--ilab-mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ilab-sage-ink);
  text-decoration: none;
}
.ilab-apothecary .single-sidebar-item {
  background: var(--ilab-paper);
  border: 1px solid var(--ilab-line);
  border-radius: var(--ilab-radius);
  padding: 1.25rem 1.35rem;
  margin-bottom: 1.2rem;
}
.ilab-apothecary .single-sidebar-item h2 {
  font-family: var(--ilab-display);
  font-weight: 460;
  font-size: 1.15rem;
  color: var(--ilab-forest);
}
.ilab-apothecary .single-sidebar-item h2::after { display: none; }   /* zig-zag gradient rule */
.ilab-apothecary .single-sidebar-item ul li a:hover { color: var(--ilab-clay-deep); }


/* ============================================================================
   §12  ENGAGEMENT POPUP  (`.ilab-pop`)
   ----------------------------------------------------------------------------
   Constraints that shaped this, all from RECON §B:
     · z-index must clear the `ilab-chat` widget (99 998 / 99 999) → 100 000.
       `#il-veil` sits at 2 147 483 000 and WILL cover this the instant a
       visitor clicks a /checkout link — accepted, documented.
     · Blog pages are full-page cached (not in siteground_optimizer_excluded_urls),
       so seen/subscribed state must be client-side only. No server branching,
       no PHP-rendered variants. This CSS assumes a `data-state` attribute
       flipped in JS.
     · First-party only. CookieYes auto-blocks klaviyo.com under `analytics`
       (denied by default), so a Klaviyo-hosted popup would never appear for
       visitors who ignore the banner.
     · Consent checkbox ships UNTICKED.
   ========================================================================= */

/* ROUND 2. The component owns its own box model rather than inheriting one.
   On a live blog page Bootstrap's `*{box-sizing:border-box}` is loaded and this
   is a no-op — but `popup-frame.html` loads nothing but this stylesheet, and
   without it the card computed 440 + 2×30 px padding = 502 px wide while its
   `width:100%` input and button overflowed the card's content box by 34 px.
   That was the "clipped panels" on the contact sheet, and it would also be the
   behaviour anywhere the popup is shown outside the themed blog templates. */
.ilab-apothecary .ilab-pop,
.ilab-apothecary .ilab-pop *,
.ilab-apothecary .ilab-pop *::before,
.ilab-apothecary .ilab-pop *::after { box-sizing: border-box; }

.ilab-apothecary .ilab-pop {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  font-family: var(--ilab-body, system-ui, sans-serif);
}
.ilab-apothecary .ilab-pop[hidden] { display: none; }

.ilab-apothecary .ilab-pop__scrim {
  position: absolute;
  inset: 0;
  background: rgba(20,39,31,.52);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  animation: ilab-pop-fade .32s ease both;
}

.ilab-apothecary .ilab-pop__card {
  position: relative;
  width: min(440px, 100%);
  max-height: calc(100dvh - 2.5rem);
  overflow-y: auto;
  padding: clamp(1.6rem, 4vw, 2.1rem);
  background-color: var(--ilab-paper, #FCFAF4);
  background-image: var(--ilab-grain);
  border: 1px solid var(--ilab-line, rgba(30,77,56,.15));
  border-radius: var(--ilab-radius, 18px);
  box-shadow: var(--ilab-shadow-lg, 0 30px 60px rgba(20,39,31,.2));
  animation: ilab-pop-rise .38s cubic-bezier(.2,.7,.3,1) both;
}
/* the ochre thread along the top edge — the same hairline as the hero horizon */
.ilab-apothecary .ilab-pop__card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  border-radius: var(--ilab-radius, 18px) var(--ilab-radius, 18px) 0 0;
  background: linear-gradient(90deg, var(--ilab-clay, #BE4F2E), var(--ilab-ochre, #B68A2E) 55%, var(--ilab-sage, #5E7A68));
}

.ilab-apothecary .ilab-pop__x {
  position: absolute;
  /* ROUND 3 (F16): 32×32 → 44×44. The offsets shrink by the same 6px each side
     so the glyph stays optically where it was; only the hit area grows. This is
     the dismiss control on a modal that interrupts a reader mid-article — the
     one control that must never need a second attempt. */
  top: .32rem; right: .32rem;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ilab-sage-ink, #4E6857);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease;
}
.ilab-apothecary .ilab-pop__x:hover { background: var(--ilab-bone-deep, #EEE7D6); color: var(--ilab-forest, #1E4D38); }

.ilab-apothecary .ilab-pop__eyebrow {
  margin: 0 0 .75rem;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--ilab-mono);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--ilab-ochre-ink, #826320);   /* ROUND 3 (F21): the fallback said
                                              #876721 while the token in §1 says
                                              #826320. Unified on the token —
                                              the tested value. */
}
.ilab-apothecary .ilab-pop__eyebrow::before { content: ""; width: 22px; height: 1px; background: currentColor; opacity: .6; }

.ilab-apothecary .ilab-pop__h {
  margin: 0 0 .7rem;
  font-family: var(--ilab-display);
  font-weight: 470;
  font-size: clamp(1.45rem, 1.2rem + 1.1vw, 1.78rem);
  line-height: 1.1;
  letter-spacing: -.017em;
  font-variation-settings: "opsz" 90, "SOFT" 30, "WONK" 0;
  color: var(--ilab-forest, #1E4D38);
  text-wrap: balance;
}
.ilab-apothecary .ilab-pop__body {
  margin: 0 0 1.25rem;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--ilab-ink-soft, #4A554A);
}

.ilab-apothecary .ilab-pop__field { display: block; }
.ilab-apothecary .ilab-pop__label {
  display: block;
  margin-bottom: .35rem;
  font-family: var(--ilab-mono);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ilab-ink-soft, #4A554A);
}
.ilab-apothecary .ilab-pop__input {
  width: 100%;
  padding: .85rem 1rem;
  font-family: var(--ilab-body);
  font-size: 1rem;              /* 16px — stops iOS zooming the viewport */
  color: var(--ilab-ink, #20271F);
  background: #fff;
  /* ROUND 3 (F9): `--ilab-line-strong` over white is 1.62:1 — the single
     lowest-contrast control boundary in the file, on the one field the whole
     component exists to get filled in. `--ilab-control-edge` is 3.52:1 on
     white. See the §10 note for why the token is separate. */
  border: 1px solid var(--ilab-control-edge, #778E80);
  border-radius: var(--ilab-radius-sm, 12px);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.ilab-apothecary .ilab-pop__input::placeholder { color: var(--ilab-sage-ink, #4E6857); }
/* ROUND 3 (F13). Was `:focus { outline: 0 }` with a `0 0 0 3px rgba(30,77,56,
   .16)` glow standing in for the ring — that glow measures ~1.3:1 against the
   card, i.e. this field HAD NO VISIBLE FOCUS INDICATOR, on a modal that traps
   focus. Two separate faults, both fixed: `outline: 0` is gone, so §2's 3px
   forest ring applies here like everywhere else; and the selector is
   `:focus-visible`, so a mouse user clicking into the field does not get a
   keyboard affordance they did not ask for. The box-shadow stays — it is a
   pleasant decoration once it is no longer pretending to be the ring. */
.ilab-apothecary .ilab-pop__input:focus-visible {
  border-color: var(--ilab-forest, #1E4D38);
  box-shadow: 0 0 0 3px rgba(30,77,56,.16);
}

.ilab-apothecary .ilab-pop__consent {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: .6rem;
  align-items: start;
  margin: .95rem 0 1.15rem;
  font-size: .82rem;
  line-height: 1.45;
  color: var(--ilab-ink-soft, #4A554A);
  cursor: pointer;
}
.ilab-apothecary .ilab-pop__consent input[type="checkbox"] {
  width: 18px;
  height: 18px;
  /* ROUND 3 (G6). A checkbox does not trigger the iOS focus-zoom and its own
     box is sized above, so this changes nothing visible — it is here so that
     "no form control on this page computes under 16px" is a rule with no
     exceptions to remember. The width/height are what actually size it. */
  font-size: 1rem;
  margin: .12rem 0 0;
  accent-color: var(--ilab-forest, #1E4D38);
  cursor: pointer;
}

.ilab-apothecary .ilab-pop__btn {
  width: 100%;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: .95rem 1.6rem;
  border: 0;
  border-radius: 999px;
  background: var(--ilab-clay, #BE4F2E);
  color: var(--ilab-on-clay, #FFF7F0);
  font-family: var(--ilab-body);
  font-weight: 700;
  font-size: 1.04rem;
  line-height: 1.15;
  letter-spacing: -.008em;
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 4px 12px rgba(20,39,31,.12);
  transition: transform .18s cubic-bezier(.2,.7,.3,1), background-color .18s ease, box-shadow .18s ease;
}
/* Sub-line hierarchy is size + tracking, NOT opacity. A 0.82-alpha sub-line on
   terracotta measures 3.56:1 and fails AA; at full alpha it inherits the
   button's 4.55:1. See NOTES.md §8. */
.ilab-apothecary .ilab-pop__btn-sub {
  font-family: var(--ilab-mono);
  font-weight: 500;
  font-size: .64rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: inherit;
}
.ilab-apothecary .ilab-pop__btn:hover {
  background: var(--ilab-clay-deep, #A23F22);
  transform: translateY(-1px);
  box-shadow: 0 1px 0 rgba(255,255,255,.25) inset, 0 6px 16px rgba(20,39,31,.17);
}
.ilab-apothecary .ilab-pop__btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }

.ilab-apothecary .ilab-pop__micro {
  margin: .9rem 0 0;
  font-size: .74rem;
  line-height: 1.45;
  color: var(--ilab-ink-soft, #4A554A);
  text-align: center;
}
.ilab-apothecary .ilab-pop__dismiss {
  display: block;
  width: 100%;
  margin-top: 1rem;
  padding: .35rem;
  border: 0;
  background: none;
  font-family: var(--ilab-body);
  font-size: .82rem;
  color: var(--ilab-sage-ink, #4E6857);
  text-decoration: underline;
  text-underline-offset: .16em;
  cursor: pointer;
}
.ilab-apothecary .ilab-pop__dismiss:hover { color: var(--ilab-forest, #1E4D38); }

/* --- error state --- */
.ilab-apothecary .ilab-pop__error {
  display: none;
  align-items: flex-start;
  gap: .5rem;
  margin: .8rem 0 0;
  padding: .7rem .85rem;
  border: 1px solid var(--ilab-clay, #BE4F2E);
  border-left-width: 3px;
  border-radius: var(--ilab-radius-sm, 12px);
  background: #FBF0EC;
  font-size: .84rem;
  line-height: 1.45;
  color: var(--ilab-clay-deep, #A23F22);
}
.ilab-apothecary .ilab-pop[data-state="error"] .ilab-pop__error { display: flex; }
.ilab-apothecary .ilab-pop[data-state="error"] .ilab-pop__input { border-color: var(--ilab-clay, #BE4F2E); }

/* --- success state --- */
.ilab-apothecary .ilab-pop__success { display: none; text-align: center; }
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__success { display: block; }
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__form,
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__body,
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__dismiss,
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__eyebrow,
.ilab-apothecary .ilab-pop[data-state="success"] .ilab-pop__h { display: none; }
.ilab-apothecary .ilab-pop__success h2 {
  margin: 0 0 .6rem;
  font-family: var(--ilab-display);
  font-weight: 470;
  font-size: clamp(1.4rem, 1.2rem + 1vw, 1.7rem);
  line-height: 1.1;
  letter-spacing: -.017em;
  color: var(--ilab-forest, #1E4D38);
}
.ilab-apothecary .ilab-pop__seal {
  width: 62px;
  height: 62px;
  margin: .3rem auto 1rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--ilab-sage-soft, #DCE7DC);
  color: var(--ilab-forest, #1E4D38);
}
.ilab-apothecary .ilab-pop__seal svg { width: 30px; height: 30px; }
.ilab-apothecary .ilab-pop__success p { margin: 0 0 .6rem; font-size: .95rem; line-height: 1.55; color: var(--ilab-ink-soft, #4A554A); }
.ilab-apothecary .ilab-pop__success strong { color: var(--ilab-forest, #1E4D38); font-weight: 650; }

/* --- mobile: bottom sheet, single field --- */
@media (max-width: 560px) {
  .ilab-apothecary .ilab-pop { padding: 0; place-items: end stretch; }
  .ilab-apothecary .ilab-pop__card {
    width: 100%;
    max-height: 86dvh;
    border-radius: var(--ilab-radius-lg, 28px) var(--ilab-radius-lg, 28px) 0 0;
    border-bottom: 0;
    padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
    animation: ilab-sheet-up .34s cubic-bezier(.2,.7,.3,1) both;
  }
  .ilab-apothecary .ilab-pop__card::before { border-radius: var(--ilab-radius-lg, 28px) var(--ilab-radius-lg, 28px) 0 0; }
  /* the grab handle */
  .ilab-apothecary .ilab-pop__card::after {
    content: "";
    position: absolute;
    top: .62rem; left: 50%;
    width: 38px; height: 4px;
    margin-left: -19px;
    border-radius: 999px;
    background: var(--ilab-line-strong, rgba(30,77,56,.28));
  }
  .ilab-apothecary .ilab-pop__x { top: .55rem; right: .55rem; }
  .ilab-apothecary .ilab-pop__eyebrow { margin-top: .5rem; }
  .ilab-apothecary .ilab-pop__h { font-size: 1.32rem; }
  .ilab-apothecary .ilab-pop__body { font-size: .9rem; margin-bottom: 1rem; }
  /* ROUND 3 (F1) — DECIDED, not deferred. See NOTES.md LAUNCH GATE + §7.1.
     ---------------------------------------------------------------------
     WHAT THIS USED TO DO, and why it had to change:
       `.ilab-pop--sheet .ilab-pop__consent { display: none }` inside this
       breakpoint, with `.ilab-pop--sheet` present in the markup at ALL widths,
       and `popup.html`'s validator skipping the consent check whenever
       `consent.offsetParent === null`. Net effect: on mobile — which is most
       blog traffic — a visitor was enrolled in marketing email with NO consent
       mechanism present at all. Not a weak consent record: no record.
       The old comment claimed the checkbox "collapses into the button's own
       consent line". It does not. The button's sub-line reads "Single-use ·
       valid 14 days", which is offer terms, not permission. The comment was
       describing a design that was never built.
     UK PECR reg. 22 needs prior, specific, freely-given, affirmative consent
     for electronic marketing, and the ICO's own guidance is that it must be
     recorded — who, when, what they were told. A checkbox hidden by a media
     query records nothing, and "they submitted the form" is exactly the
     bundled/implied consent the regulation excludes.
     SO: the consent checkbox stays visible at every width. It is ~40px of
     sheet height and it is not optional.
     What DOES collapse on mobile is the redundant "EMAIL ADDRESS" field label —
     the input carries a placeholder, `type="email"`, `autocomplete="email"` and
     `inputmode="email"`, so the visible label is duplication for a sighted
     user. It is visually hidden rather than removed, so the accessible name
     survives intact for screen readers: clip-path + 1px box is the technique
     that keeps the text in the a11y tree AND keeps it findable by voice
     control, which `display:none` and `visibility:hidden` both destroy. */
  .ilab-apothecary .ilab-pop--sheet .ilab-pop__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .ilab-apothecary .ilab-pop--sheet .ilab-pop__consent {
    display: grid;
    margin: .85rem 0 1rem;
  }
  .ilab-apothecary .ilab-pop--sheet .ilab-pop__btn { margin-top: .2rem; }
}

@keyframes ilab-pop-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes ilab-pop-rise { from { opacity: 0; transform: translateY(16px) scale(.985) } to { opacity: 1; transform: none } }
@keyframes ilab-sheet-up { from { transform: translateY(100%) } to { transform: none } }


/* ============================================================================
   §13  UTILITIES · READING PROGRESS · MOTION · 375px GUARDS
   ========================================================================= */

/* Highlight-underline for key headline words. Drawn as a background-image so it
   sits behind the descenders and never clips. */
.ilab-apothecary .u-mark {
  position: relative;
  white-space: nowrap;
  background-image: linear-gradient(180deg, transparent 62%, rgba(182,138,46,.34) 62%, rgba(182,138,46,.34) 92%, transparent 92%);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
.ilab-apothecary .ilab-pop__h .u-mark {
  white-space: normal;
  background-image: linear-gradient(180deg, transparent 64%, rgba(182,138,46,.34) 64%, rgba(182,138,46,.34) 94%, transparent 94%);
}

/* READING PROGRESS. CSS-only, scroll-driven, no extra markup: it hangs off
   `body::after`. Wrapped in @supports so browsers without scroll-driven
   animations simply never render it. Sits below the ilab-chat widget. */
@supports (animation-timeline: scroll()) {
  .ilab-apothecary.single-post::after {
    content: "";
    position: fixed;
    top: 0; left: 0;
    width: 100%;
    height: 3px;
    z-index: 99997;
    transform-origin: 0 50%;
    background: linear-gradient(90deg, var(--ilab-clay), var(--ilab-ochre));
    animation: ilab-progress linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes ilab-progress { from { transform: scaleX(0) } to { transform: scaleX(1) } }
}

/* Motion. Everything above degrades to an instant state change. */
@media (prefers-reduced-motion: reduce) {
  .ilab-apothecary *,
  .ilab-apothecary *::before,
  .ilab-apothecary *::after {
    animation-duration: .001ms !important;   /* the one sanctioned use of
                                                !important: an a11y escape
                                                hatch, per WCAG 2.3.3 */
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .ilab-apothecary .elementor-loop-container .e-loop-item:hover { transform: none; }
  .ilab-apothecary .elementor-loop-container .e-loop-item:hover
    .elementor-widget-theme-post-featured-image img { transform: none; }

  /* ROUND 3 (F14) — THE READING-PROGRESS BAR ESCAPES THE BLANKET ABOVE.
     `animation-duration` and `animation-iteration-count` are what neutralise
     every other animation in this file, and on a SCROLL-DRIVEN timeline both
     are ignored by definition: the animation's progress is the scroll position,
     not elapsed time, so the bar kept sweeping at full amplitude for exactly
     the users who asked for no motion. A 3px element that tracks the scroll
     one-to-one across the top of the viewport is a persistent moving object in
     peripheral vision — a real vestibular trigger, not a theoretical one.
     `display: none` is the only thing that actually stops a scroll timeline.
     It is inside the reduced-motion block, so nobody else loses the bar. */
  .ilab-apothecary.single-post::after { display: none; }
}

/* --- 375px guards -------------------------------------------------------- */

/* theme-responsive.css:110 sets a bare `a { word-break: break-all }` inside
   @media (max-width:767px). On a prose page that shatters every link mid-word.
   Replaced with the modern, grapheme-aware pair. */
@media (max-width: 767px) {
  .ilab-apothecary a,
  .ilab-apothecary .internal-content-wrap a {
    word-break: normal;
    overflow-wrap: anywhere;
  }
}

/* Long titles (the longest is 96 characters) and long unbroken URLs are the two
   real overflow sources on a 375px viewport. */
.ilab-apothecary .internal-content-wrap,
.ilab-apothecary .blog-details-hero-wrap { min-width: 0; }
.ilab-apothecary .internal-content-wrap :is(p, li, h1, h2, h3, h4, td, th) { overflow-wrap: break-word; }
.ilab-apothecary .internal-content-wrap pre { overflow-x: auto; }
.ilab-apothecary .elementor-loop-container .e-loop-item * { min-width: 0; }

/* ROUND 3 (F6) — DROP CAP, NARROW VIEWPORTS.
   At 3.9em the cap rendered 67.9px tall inside a 345px column: it occupied a
   fifth of the text column's width and pushed the first three lines into a
   ragged 250px gutter. The desktop figure (71.6px in 672px) is correct and is
   untouched; this only caps the ratio where the column is narrow. ~2.6em keeps
   the gesture and gives the first lines their measure back.
   The `initial-letter` branch gets the equivalent treatment — 2.6 size over a
   3-line sink, so the cap is smaller than its own sink and sits optically
   centred against the first three lines rather than filling them. */
@media (max-width: 560px) {
  .ilab-apothecary:not(.elementor-page) .internal-content-wrap > p.wp-block-paragraph:first-of-type::first-letter {
    font-size: 2.6em;
  }
  @supports (initial-letter: 3) or (-webkit-initial-letter: 3) {
    .ilab-apothecary:not(.elementor-page) .internal-content-wrap > p.wp-block-paragraph:first-of-type::first-letter {
      -webkit-initial-letter: 2.6 3;
      initial-letter: 2.6 3;
      font-size: inherit;
    }
  }
}

@media (max-width: 575px) {
  .ilab-apothecary .post-meta-wrap .post-share { justify-content: flex-start; }
  .ilab-apothecary .post-meta-wrap .col.text-right { text-align: left; }
  /* ROUND 3 (F3): was `min-height: 300px`, a floor set against the OLD
     clamp(340px, 44vw, 540px). With the band now sized to its type
     (clamp(190px, 20vw, 280px)) a 300px floor would silently re-inflate the
     mobile hero above the desktop one. Lowered to match. */
  .ilab-apothecary .blog-details-hero { min-height: 220px; }
}


/* ============================================================================
   §14  FORCED COLORS (Windows High Contrast)   —   ROUND 3, G12
   ----------------------------------------------------------------------------
   In `forced-colors: active` the UA overrides `background-color`,
   `background-image`, `border-color`, `color` and `box-shadow` with the user's
   system palette. That is the correct behaviour and this file must not fight
   it — but the skin restored ELEVEN icons by painting `background-color:
   currentColor` behind a `mask-image`, and a forced background is a forced
   background whether it is decoration or the glyph itself. Every one of them
   goes: the byline clock, the author's dot, the tag icon, the three share
   glyphs, the CTA arrow, the related-link arrows and the two eyebrow rules.
   Worse, list markers are drawn the same way — an 11×2px `background-color`
   dash on `li::before` with `list-style-type: none` on the `ul` — so in forced
   colors an unordered list loses its markers entirely and reads as a run of
   loose sentences. On a page whose whole job is "here are the symptoms", that
   is a comprehension failure, not a cosmetic one.

   The fix is to re-state the fills in SYSTEM colour keywords, which are the one
   thing forced-colors honours, and to give the lists a real marker back.
   `forced-color-adjust: auto` is left alone everywhere: nothing here opts out
   of the user's palette, it only tells the UA which system colour to use.
   ========================================================================= */

@media (forced-colors: active) {

  /* The masked icons. `CanvasText` is the user's foreground, so each glyph
     reappears in whatever ink their theme uses, mask intact. */
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author i,
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author .author::before,
  .ilab-apothecary .post-meta-wrap .tags-rap ul a::before,
  .ilab-apothecary .post-meta-wrap .post-share ul.rso-socials a::before,
  .ilab-apothecary .internal-content-wrap .ilab-cta__btn::after,
  .ilab-apothecary .internal-content-wrap a[data-ilab-cta]::after,
  .ilab-apothecary .blog-details-area .internal-content-wrap .ilab-related ul li a::before {
    background-color: CanvasText;
  }
  /* Hover states repaint these to a brand colour; keep them on the system ink
     so they do not vanish on hover instead of on rest. */
  .ilab-apothecary .post-meta-wrap .tags-rap ul a:hover::before,
  .ilab-apothecary .post-meta-wrap .post-share ul.rso-socials a:hover::before {
    background-color: CanvasText;
  }

  /* The eyebrow hairlines (CTA, journal intro, popup) are `background:
     currentColor` on a 1px pseudo-element — same problem, same fix. */
  .ilab-apothecary .internal-content-wrap .ilab-cta__eyebrow::before,
  .ilab-apothecary .ilab-eyebrow::before,
  .ilab-apothecary .ilab-pop__eyebrow::before {
    background: CanvasText;
  }

  /* LIST MARKERS. The sage dash cannot survive forced colors as a background,
     so the real marker comes back and the dash stands down. `padding-left` is
     restored to a value that suits a UA marker rather than the -1.5rem the
     absolutely-positioned dash was hung at. */
  .ilab-apothecary .blog-details-area .internal-content-wrap ul {
    list-style-type: disc;
    padding-left: 1.4rem;
  }
  .ilab-apothecary .blog-details-area .internal-content-wrap ul li::before {
    content: none;
  }
  /* Ordered lists use a mono `counter()` in a pseudo-element — that is TEXT,
     not a background, so it survives. Nothing to do. */

  /* The hero scrim is a gradient on a pseudo-element and is dropped, leaving
     bone-on-Canvas type. Hand the band back to the system pair explicitly
     rather than hoping. */
  .ilab-apothecary .blog-details-hero,
  .ilab-apothecary .blog-details-hero-wrap h1.internal-page-title,
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author { color: CanvasText; }
  .ilab-apothecary .blog-details-hero-wrap .post-category { border-color: CanvasText; }
}


/* ============================================================================
   §15  PRINT   —   ROUND 3, G11
   ----------------------------------------------------------------------------
   People print and PDF health articles — to take to a GP appointment, to hand
   to a family member, to read away from a screen. There was no `@media print`
   at all, and the default is not merely plain: it is BROKEN. Every element in
   the hero is bone `#F6F1E6` on a `--forest-deep` background, and UAs drop
   background colours and images when printing unless the user finds and ticks
   "background graphics". So on a default print the `<h1>`, the category badge
   and the byline are near-white ink on white paper and VANISH — the printout
   starts with a blank band and then the article, with no title on it.
   `thead th` does exactly the same: bone on forest, so a table prints with an
   invisible header row and the reader loses the column labels.

   Everything below is either restoring ink, removing screen furniture that
   cannot work on paper, or making link destinations recoverable.
   ========================================================================= */

@media print {

  /* 1. THE VANISHING HERO. */
  .ilab-apothecary .blog-details-hero {
    min-height: 0;
    padding: 0 0 .6rem;
    margin-bottom: 1.2rem;
    background: none;
    color: #000;
    border-bottom: 1px solid #000;
  }
  .ilab-apothecary .blog-details-hero::before,
  .ilab-apothecary .blog-details-hero::after { display: none; }
  .ilab-apothecary .blog-details-hero-wrap { max-width: none; }
  .ilab-apothecary .blog-details-hero-wrap h1.internal-page-title,
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author,
  .ilab-apothecary .blog-details-hero-wrap .post-category { color: #000; }
  .ilab-apothecary .blog-details-hero-wrap .post-category { border-color: #000; background: none; }
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author i,
  .ilab-apothecary .blog-details-hero-wrap .post-date-and-author .author::before { background-color: #000; }

  /* 2. THE VANISHING TABLE HEADER. Bone on forest becomes ink on paper with a
        rule under it — the header stays identifiable without needing the fill
        to print. */
  .ilab-apothecary .internal-content-wrap thead th {
    background: none;
    color: #000;
    border-bottom: 2px solid #000;
  }
  .ilab-apothecary .internal-content-wrap table { background: none; }

  /* 3. SCREEN FURNITURE THAT CANNOT WORK ON PAPER. The sticky header would
        print at the top of page one only and take a band with it; the share
        row is four dead links; the progress bar and the popup are interface. */
  .ilab-apothecary .elementor-location-header,
  .ilab-apothecary .elementor-location-footer,
  .ilab-apothecary .post-meta-wrap .post-share,
  .ilab-apothecary .comment-form,
  .ilab-apothecary .ilab-pop,
  .ilab-apothecary.single-post::after { display: none; }

  /* 4. LINKS. On paper an underlined phrase points nowhere, so in-article link
        destinations are expanded after the text. Deliberately NOT applied to
        the nav, the CTA or the related list, where it would print a wall of
        URLs; and `[href^="#"]`/`javascript:` are excluded because an expanded
        fragment or script URL is noise. */
  .ilab-apothecary .internal-content-wrap p a[href]:not([href^="#"]):not([href^="javascript:"])::after {
    content: " (" attr(href) ")";
    font-size: .85em;
    word-break: break-all;
  }
  .ilab-apothecary .internal-content-wrap a { color: #000; text-decoration: underline; }

  /* 5. PAPER ECONOMY AND LEGIBILITY. Full measure, ink on white, no shadows,
        and no orphaned heading at the foot of a page. */
  .ilab-apothecary {
    background: none;
    color: #000;
    font-size: 11pt;
  }
  .ilab-apothecary:not(.elementor-page) .internal-content-wrap,
  .ilab-apothecary .post-meta-wrap,
  .ilab-apothecary .internal-content-wrap .ilab-cta,
  .ilab-apothecary .internal-content-wrap .ilab-related { max-width: none; }
  .ilab-apothecary .internal-content-wrap p,
  .ilab-apothecary .internal-content-wrap li,
  .ilab-apothecary .internal-content-wrap td { color: #000; }
  .ilab-apothecary .internal-content-wrap :is(h2, h3, h4) {
    color: #000;
    break-after: avoid;
    page-break-after: avoid;
  }
  .ilab-apothecary .internal-content-wrap figure,
  .ilab-apothecary .internal-content-wrap table { break-inside: avoid; page-break-inside: avoid; }
  .ilab-apothecary .internal-content-wrap img,
  .ilab-apothecary .internal-content-wrap .elementor-widget-image img { box-shadow: none; }
  .ilab-apothecary .internal-content-wrap .ilab-cta { box-shadow: none; border: 1px solid #000; }
}
