/* ==========================================================================
   FoliumpX — Front page
   --------------------------------------------------------------------------
   Keeps the live page's visual language — white and pale-blue panels, teal
   headings, centred section heads, white icon cards, alternating product rows
   — and raises the craft: one type scale, one spacing rhythm, one card
   system, one shadow set, consistent image treatment.

   Loaded only on the front page (inc/enqueue.php), after global.css.

   Tailwind is deliberately NOT used here: it is enqueued on WooCommerce pages
   only, and pulling the whole bundle in for utilities would work against the
   reason this page was rewritten. The home page sits in the same "theme
   chrome" zone as the header and footer, which are plain CSS for the same
   reason.

   Selectors run one class heavier than strictly needed (.fpx-home a.fpx-btn)
   so plugin stylesheets with class+tag rules can't outrank them.
   ========================================================================== */

.fpx-home {
  /* Brand values come from :root in global.css so the palette keeps one
     source of truth; these are the page-level surfaces built on top. */
  --surface: #ffffff;
  --panel: #eef6f8;
  --sky: #f3faff; /* the site's own section tint */
  /* The light stop of the brand gradient, used as a heading accent. On white
     it measures 1.86:1 — under even the 3:1 large-text threshold — but it is
     the site's own accent colour and matches the live headings. #439fbd is
     the closest value that reaches 3:1 if that is ever needed. */
  --brand-cyan: #56ccf2;
  --ink: #12343a;
  --muted: #5a7076;
  --line: rgba(29, 134, 145, 0.16);

  /* One shadow set, used by every raised element on the page. */
  --lift-sm: 0 1px 2px rgba(18, 52, 58, 0.04), 0 3px 12px rgba(18, 52, 58, 0.06);
  --lift: 0 1px 2px rgba(18, 52, 58, 0.04), 0 6px 20px rgba(18, 52, 58, 0.07);
  --lift-hover: 0 2px 4px rgba(18, 52, 58, 0.05), 0 16px 34px rgba(18, 52, 58, 0.11);

  /* --brand-grad and --brand-ink now live in :root in global.css, so the
     front page and the theme chrome (the mobile drawer CTA) share one
     definition. The contrast note lives with them there. */

  /* The site's band gradient, matched to the live CSS. Note it is a
     DIFFERENT angle and different stops from the button gradient above —
     120deg / 20% here, 340deg / 30% there — so the two are separate tokens
     rather than one shared value. Same contrast caveat as --brand-grad. */
  --brand-band: linear-gradient(120deg, #148d9f 20%, #56ccf2 100%);

  /* Deep variant, for white text on large surfaces where a full-bleed cyan
     wash would overpower the page. Both stops clear 4.5:1 with white. */
  --teal-grad: linear-gradient(340deg, #0f7a8b 30%, #128091 100%);

  background: var(--surface);
  color: var(--ink);
  font-family: "Inter", sans-serif;
}

/* The shared .fpx-container is 1660px, which suits the shop grid but leaves
   this page's prose and cards stretched thin. Narrowing it here only affects
   the home page; the header and footer keep the full width. */
.fpx-home .fpx-container {
  max-width: 1240px;
}

/* The `hidden` attribute must actually hide. global.css sets
   `svg { display: block }`, and any author display rule outranks the UA
   stylesheet's `[hidden] { display: none }` — which left the hero's play and
   pause icons both visible at once. */
.fpx-home [hidden] {
  display: none !important;
}

.fpx-home .fpx-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Sections ────────────────────────────────────────────────────────────
   The section owns the vertical rhythm; nothing inside sets its own top or
   bottom padding, which is what stops section and component rules cancelling
   each other out. */

.fpx-home .fpx-section {
  padding: clamp(56px, 7vw, 104px) 0;
}

.fpx-home .fpx-section--panel {
  background: var(--panel);
}

.fpx-home .fpx-section--sky {
  background: var(--sky);
}

.fpx-home .fpx-section__head {
  max-width: 820px;
  margin: 0 auto clamp(36px, 4.5vw, 64px);
  text-align: center;
}

/* 4.4vw, not 3.4vw. At 3.4vw the preferred size does not clear the 26px
   floor until 765px, so every section title was an identical flat 26px from
   a 390px phone all the way to a small tablet — no growth across the entire
   range where most visitors are. 4.4vw crosses the floor at 591px and
   reaches the 40px ceiling at 909px, so the title actually scales through
   the middle. Both ends are unchanged. */
.fpx-home .fpx-section__title {
  margin: 0;
  font-family: "Glory", sans-serif;
  font-size: clamp(26px, 4.4vw, 40px);
  font-weight: 700;
  line-height: 1.16;
  letter-spacing: -0.02em;
  color: var(--color-primary-dark);
  text-wrap: balance;
}

/* Near-black heading with a cyan accent phrase, as on the live section. */
.fpx-home .fpx-section__title--ink {
  color: var(--color-text);
}

/* Gradient-filled text, matching the live heading:
   linear-gradient(340deg, #148D9F 0%, #56CCF2 100%).

   The flat colour is the fallback and stays first — browsers without
   background-clip: text keep readable teal rather than transparent text. */
.fpx-home .fpx-section__title .fpx-accent {
  color: #148d9f;
}

@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .fpx-home .fpx-section__title .fpx-accent {
    background-image: linear-gradient(340deg, #148d9f 0%, #56ccf2 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

/* The lede scales with the title it sits under. It was a flat 16px while the
   title ran 26-40px, so on a wide screen a 40px heading was followed by body
   text at the same size as a caption. The gap above it scales for the same
   reason: 16px under a 40px heading read as the two being one block. */
.fpx-home .fpx-section__lede {
  max-width: 62ch;
  margin: clamp(12px, 1.1vw, 18px) auto 0;
  font-size: clamp(16px, 1.15vw, 18px);
  line-height: 1.65;
  color: var(--muted);
  text-wrap: pretty;
}

/* The compliance line is quieter than a lede but must stay above the 4.5:1
   body-text threshold — do not lighten it further. It scales with the title
   above it for the same reason the lede does: flat 15px under a 40px heading
   reads as a footnote that got attached to the wrong thing. */
.fpx-home .fpx-section__note {
  max-width: 58ch;
  margin: clamp(12px, 1.1vw, 18px) auto 0;
  font-size: clamp(15px, 1.05vw, 16.5px);
  line-height: 1.65;
  color: var(--muted);
  text-wrap: pretty;
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */

/* Geometry matched to the live button: 15px/32px padding, 50px radius, 168px
   wide. min-width rather than a fixed width, because these labels vary
   ("Buy Now" vs "Shop Folium Immuno") and a hard 168px would clip them. */
.fpx-home a.fpx-btn,
.fpx-home button.fpx-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box; /* so the 2px border doesn't grow the button */
  min-width: 168px;
  min-height: 48px; /* keeps the tap target above the 44px minimum */
  padding: 15px 32px;
  border: 0;
  border-radius: 50px;
  font-family: "Inter", sans-serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
}

.fpx-home a.fpx-btn--primary,
.fpx-home button.fpx-btn--primary {
  background-color: transparent;
  background-image: var(--brand-grad);
  color: var(--brand-ink);
  border: 2px solid #fff;
}

/* brightness(), not a second gradient: keeps the deep→light relationship
   intact on hover instead of flattening it. */
.fpx-home a.fpx-btn--primary:hover,
.fpx-home button.fpx-btn--primary:hover {
  transform: translateY(-2px);
  filter: brightness(1.06);
}

.fpx-home .fpx-btn:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 3px;
}

/* In the hero the button sits on the dark overlay, where a teal ring would
   disappear. */
.fpx-hero .fpx-btn:focus-visible {
  outline-color: #fff;
}

/* ── Hero ────────────────────────────────────────────────────────────────
   Autoplaying carousel: centred white copy over a black overlay, a slow zoom
   on the active slide, arrows at the left and right edges, dots beneath.

   The overlay is what makes centred white text viable. Three of the five
   banners are very light (aX, pX, Immuno sit on pale green), and white text
   on those is unreadable without it.

   CONTRAST, and the trade-off that was made here. The overlay is ONE flat
   value (--fpx-hero-overlay) applied evenly. Measured worst-pixel behind the
   headline across all five slides, white text:

     0.46 -> passes AA, but greys out the whole banner
     0.30 -> current

   Two things were tried and rejected, so don't re-try them:
   - A radial "pool" darkening only the centre. Against a light base it does
     not read as shading, it reads as a dark blob in the middle of the
     banner. Centre-weighting only works when the base is dark enough to hide
     the falloff.
   - A text-shadow on the copy. Removed at the site owner's request.

   So nothing is currently carrying the shortfall and the hero copy runs
   below AA against the brightest parts of the banner. This is a known,
   accepted state, not an oversight.

   If an audit ever forces AA here, the exits are: put the base back to 0.46
   (dark), or set the copy on a contained translucent plate. */

.fpx-hero {
  position: relative;
  overflow: hidden;
  background: #0b2a2f;
  --fpx-hero-overlay: 0.30;
}

/* Horizontal track: slides sit in a row and the whole strip translates, so a
   new slide travels in from the right rather than cross-fading in place.
   home.js sets --fpx-hero-i to the active index. */
.fpx-hero__track {
  display: flex;
  transform: translateX(calc(var(--fpx-hero-i, 0) * -100%));
  /* On a slide transition the CURVE sets the perceived speed, not the
     duration. Measured progress against elapsed time:

                                    15%    28%    50%    70%
       (0.22, 1, 0.36, 1)  was      55%    81%    96%    99%
       (0.33, 0.7, 0.28, 1) now     34%    64%    90%    97%
       ease-in-out, rejected         5%    16%    50%    81%

     The old curve spent more than half the travel in its first 105ms, so
     lengthening the duration only stretched a tail nobody could see. This one
     puts the movement where it is actually watched while still launching
     promptly — it is past linear by the first sixth, which is what keeps it
     clear of the ease-in-out that read as the slide being DRAGGED. */
  transition: transform 0.7s cubic-bezier(0.33, 0.7, 0.28, 1);
  will-change: transform;
}

/* overflow: hidden is load-bearing. The active slide's image is scaled to
   1.12 by the zoom, so it overflows its own slide box by roughly 6% a side —
   about 40px at desktop width. The overlay is inset:0 on the SLIDE, not on
   the scaled image, so without clipping that overflow slid into view during a
   transition as a strip of raw, un-overlaid banner. */
.fpx-hero__slide {
  overflow: hidden;
  position: relative;
  flex: 0 0 100%;
  min-width: 0;
  display: grid;
  /* Wide art is 2.5:1, but the height is capped rather than derived from that
     ratio: on a 1920px display a true 2.5:1 hero is 768px tall and swallows
     the whole first screen. */
  min-height: clamp(420px, 42vw, 560px);
}

/* ONE composition at every width. Below 768px the portrait art takes over and
   the frame goes with it — 89vw is 9:8 at 390px, and the cap stops a large
   phone from having to scroll past the hero. An earlier build stacked the
   banner above a solid copy panel here instead; that was a workaround for
   having only wide art, and the art now covers it. */
@media (max-width: 767px) {
  .fpx-hero__slide {
    min-height: min(89vw, 400px);
  }
}

/* <picture> is a real layout box, so it carries the fill rather than leaving
   the <img> to position itself against the slide.

   Deliberately NOT `overflow: hidden`. The slide already clips (see above),
   and a second clip rect around the same image is what produced a hairline of
   un-overlaid banner during transitions: at a fractional track offset the
   picture clip and the overlay box round to different device pixels, so one
   column ended up with the image at full coverage and the overlay at partial
   coverage. One clip, one rounding. */
.fpx-hero__picture {
  position: absolute;
  inset: 0;
  display: block;
}

/* Resting transform is where the zoom ENDS, not where it starts. Removing
   .is-active from the outgoing slide drops its animation, and with the start
   value at rest the image snapped back to 1.01 while still sliding out of
   frame — visible for the whole 0.5s transition. */
.fpx-hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50%) center;
  display: block;
  transform: scale(1.12);
}

/* object-position follows the product. Both art files are composed for their
   own frame, so --focus only has work to do in the 768-899px band, where the
   wide art is squeezed into a frame far squarer than 2.5:1 and `cover` throws
   the sides away. 'focus' names the side the bottle is on. */
.fpx-hero__slide--focus-left {
  --focus: 30%;
}

.fpx-hero__slide--focus-right {
  --focus: 70%;
}

/* Declared after the two above so it wins on source order — a media query
   adds no specificity of its own. */
@media (max-width: 767px), (min-width: 900px) {
  .fpx-hero__slide--focus-left,
  .fpx-hero__slide--focus-right {
    --focus: 50%;
  }
}

/* Slow push-in on whichever slide is showing. Slightly longer than the 6s
   autoplay interval, so the movement never visibly stops or snaps. */
.fpx-hero__slide.is-active .fpx-hero__img {
  animation: fpx-hero-zoom 7s ease-out forwards;
}

@keyframes fpx-hero-zoom {
  from {
    transform: scale(1.01);
  }
  to {
    transform: scale(1.12);
  }
}

/* One even wash across the whole banner.

   An earlier version added a radial "pool" behind the copy to buy contrast
   locally while keeping the edges bright. Against a wash this light that
   pool stopped reading as shading and showed up as a visible dark blob in
   the middle of the banner. Keep this flat: a centre-weighted overlay only
   works when the base is dark enough to hide the falloff.

   The vertical pass is deliberately tiny — just enough to seat the banner
   against the header above and the teal band below, not enough to band.

   inset is -1px, not 0, and that is load-bearing. The track translates by a
   fractional number of device pixels mid-transition, so a box sitting exactly
   on the slide edge antialiases there — a semi-transparent overlay at partial
   coverage over banner art at full coverage reads as a bright 1-2px sliver
   down the leading edge, which is what "the overlay isn't applying on that
   part" looks like. Overhanging by a pixel puts the overlay's own edge
   outside the slide, where .fpx-hero__slide's overflow: hidden clips it, so
   every pixel actually on screen gets the full wash. */
.fpx-hero__overlay {
  position: absolute;
  inset: -1px;
  background:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, 0.09)),
    rgba(0, 0, 0, var(--fpx-hero-overlay));
}

.fpx-hero__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: inherit;
  padding-top: clamp(44px, 7vw, 72px);
  padding-bottom: clamp(52px, 8vw, 84px);
  text-align: center;
}

/* Narrower than the container so the subtitle doesn't run to the gutters, and
   so the copy reads as a block rather than as full-width text. */
.fpx-hero__copy {
  max-width: 46rem;
  margin-inline: auto;
}

/* Copy rises as the slide lands.

   The three elements are staggered rather than moving as one block — the
   headline settles first and the button last, which reads as the slide
   arriving and the copy catching up, instead of the whole panel sliding.

   The delays are tuned against the track: .fpx-hero__track transitions over
   0.7s on a fast-out curve, so most of the horizontal distance is covered in
   the first ~0.35s. Starting the rise at 0.30s means the two motions barely
   overlap. Running them together read as busy. If the track duration moves,
   move these with it — they are one timing, not two.

   Resting state is the FINAL state — opacity 1, no offset — for the same
   reason the zoom's resting transform is its end value: removing .is-active
   from the outgoing slide drops the animation, and if the resting state were
   the start value its copy would blank out while still sliding out of frame.
   `both` supplies the hidden start for the incoming slide only, during its
   delay. This also gives slide 1 a reveal on first paint. */
/* 24px, not 18px. At 0.55s the shorter travel was legible; stretched to 0.7s
   the same distance moved too slowly to read as a rise and started to look
   like a plain fade. Distance and duration have to scale together. */
@keyframes fpx-hero-rise {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fpx-hero__slide.is-active .fpx-hero__title,
.fpx-hero__slide.is-active .fpx-hero__text,
.fpx-hero__slide.is-active .fpx-hero__copy .fpx-btn {
  /* Same curve as the track, so the two motions share one rhythm. */
  animation: fpx-hero-rise 0.7s cubic-bezier(0.33, 0.7, 0.28, 1) both;
}

.fpx-hero__slide.is-active .fpx-hero__title {
  animation-delay: 0.3s;
}

.fpx-hero__slide.is-active .fpx-hero__text {
  animation-delay: 0.4s;
}

/* The button is inline-flex, so it needs a transform context of its own to
   travel without dragging the line box with it. */
.fpx-hero__slide.is-active .fpx-hero__copy .fpx-btn {
  animation-delay: 0.5s;
  will-change: transform, opacity;
}
/* Phones keep the same composition, only tighter. The inline padding is what
   keeps the centred copy clear of the edge arrows — it is load-bearing, not
   spacing taste. */
@media (max-width: 767px) {
  .fpx-hero__inner {
    padding-inline: 58px;
    padding-bottom: clamp(62px, 15vw, 82px);
  }

  .fpx-hero__copy {
    max-width: 22rem;
  }

  /* Scoped one class heavier: the base .fpx-hero__title rule is declared
     later in this file, so a bare class here loses on source order. */
  .fpx-hero .fpx-hero__title {
    font-size: clamp(28px, 7.6vw, 38px);
  }

  /* A touch smaller than the site-wide button, on phones only. The 168px /
     15px-32px / 16px geometry is the brand spec and is right everywhere
     else, but in a 22rem copy block on a 390px screen it dominated the
     slide. Tag selectors included so this outranks the base
     `.fpx-home a.fpx-btn` rule, which is (0,2,1).

     min-height stays at 48px deliberately — this is the slide's only
     control and shrinking a tap target below 44px to save a few pixels is
     not a trade worth making. The button gets narrower and lighter, not
     shorter. */
  .fpx-home .fpx-hero__copy a.fpx-btn,
  .fpx-home .fpx-hero__copy button.fpx-btn {
    min-width: 146px;
    padding: 14px 26px;
    font-size: 15px;
  }
}

/* The 768-899px band still shows the wide art. Trim the copy so it clears the
   arrows there too, and lean on --focus for the crop. */
@media (min-width: 768px) and (max-width: 899px) {
  .fpx-hero__inner {
    padding-inline: 76px;
  }

  .fpx-hero__copy {
    max-width: 32rem;
  }
}

/* Glory, explicitly. The markup used to carry a `font-heading` class, but that
   class is defined only in tailwind-output.css, which this page deliberately
   does not load (Tailwind is WooCommerce-only) — so it was dead here and the
   largest type on the site fell back to the body face.

   It was worse than a wrong face: only Inter 400 and 500 are on disk, so
   `font-weight: 700` had nothing real to resolve to and the browser faked the
   bold by smearing Inter Medium. Glory ships a true Bold, and it is what every
   other heading on this page already uses. */
.fpx-hero__title {
  margin: 0 0 clamp(12px, 1.4vw, 22px);
  font-family: "Glory", sans-serif;
  font-size: clamp(32px, 5.2vw, 60px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: #fff;
  text-wrap: balance;
}

/* Wide enough that all five sub-lines set on ONE line at desktop. At 40ch the
   longest of them broke with a single orphaned word on the second line, under
   a 60px headline, on four slides out of five. `balance` takes over once the
   viewport is too narrow for one line. */
.fpx-home .fpx-hero__text {
  margin: 0 auto 26px;
  max-width: 62ch;
  /* 16px floor: this is body copy on a phone, and 15px was under the page's
     own floor for it. */
  font-size: clamp(16px, 1.5vw, 19px);
  line-height: 1.5;
  text-wrap: balance;
  color: #fff;
}

/* ── Hero controls ───────────────────────────────────────────────────────
   No pause button. The WCAG 2.2.2 stop mechanism is the arrows and dots
   themselves: any manual navigation stops autoplay for good. Autoplay also
   holds while the copy block is hovered or anything inside the hero has
   focus, pauses in a hidden tab, and never starts under reduced motion.

   Hover is scoped to the copy on purpose — see initHero() in home.js. */

.fpx-hero__arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  transform: translateY(-50%);
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
  background: transparent;
  color: #fff;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.fpx-hero__arrow--prev {
  left: clamp(10px, 2.5vw, 34px);
}

.fpx-hero__arrow--next {
  right: clamp(10px, 2.5vw, 34px);
}

/* Arrows stay on at every width — same controls, same places, phone or
   desktop. They used to be hidden below 900px because they landed on top of
   the headline; what keeps them clear now is the inline padding on
   .fpx-hero__inner, not hiding them. Swipe still works alongside. */
@media (max-width: 767px) {
  .fpx-hero__arrow {
    width: 44px;
    height: 44px;
    border-color: rgba(255, 255, 255, 0.45);
  }

  .fpx-hero__arrow svg {
    width: 18px;
    height: 18px;
  }
}

/* No fill to darken on hover, so the ring does the work instead. */
.fpx-hero__arrow:hover {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

.fpx-hero__arrow:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.fpx-hero__dots {
  position: absolute;
  left: 50%;
  bottom: clamp(6px, 1.1vw, 12px);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 4px;
  transform: translateX(-50%);
}

/* 44px hit area around a small visual dot. */
.fpx-hero__dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.fpx-hero__dot-fill {
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  transition: background-color 0.25s ease, width 0.25s ease;
}

.fpx-hero__dot.is-active .fpx-hero__dot-fill {
  width: 20px;
  background: #fff;
}

.fpx-hero__dot:focus-visible {
  outline: 3px solid #fff;
  outline-offset: -4px;
  border-radius: 999px;
}

/* ── Promise band ────────────────────────────────────────────────────────
   The page's opening statement, and the top half of a pair: this and the
   contact strip are the only two brand-gradient bands, and they bookend
   everything between them. They are therefore set the same way — Glory,
   sentence case, weight 600 — so the pair reads as one device used twice
   rather than two unrelated slabs.

   It used to be 13.5-17px Inter, bold, uppercase, letter-spaced. That was
   carried over from the old page and it was the last thing on this page
   still shouting: every other statement here is Glory in sentence case. Small
   tracked-out uppercase is also the worst case for legibility on a light
   gradient, which matters here specifically — see below.

   CONTRAST. Measured white-on-band under the copy: 2.11:1 at 1440px, 1.94:1
   at 768px, 2.02:1 at 390px — the same accepted brand exception as the
   buttons. Neither ink colour rescues it: white fails at the #56ccf2 end
   (1.86:1), dark fails at the #148d9f end (3.09:1), and the copy spans both.
   Only changing the gradient or putting a scrim behind the text would fix it.

   What the resize does buy: at 24px+ (or 18.66px+ bold) WCAG applies the
   large-text threshold of 3:1 instead of 4.5:1, so the desktop line is now
   measured against a bar it is much closer to clearing. That is a real
   improvement in the gap, not a fix — the exception still stands. */

.fpx-promise {
  padding: clamp(26px, 3.6vw, 52px) 0;
  background-color: transparent;
  background-image: var(--brand-band);
}

.fpx-home .fpx-promise__text {
  margin: 0 auto;
  max-width: 54rem;
  font-family: "Glory", sans-serif;
  font-size: clamp(17px, 2.1vw, 25px);
  font-weight: 600;
  line-height: 1.34;
  letter-spacing: -0.012em;
  text-align: center;
  color: #fff;
  text-wrap: balance;
}

/* The second line was a pale tint (#bce9ee). The band runs to #56ccf2 at one
   end, where a tint like that is close to invisible, so both lines stay solid
   white and separate by face and size instead.

   It has to be FACE, not weight. Glory ships only SemiBold and Bold, so a
   `font-weight: 400` here was a no-op — it resolved straight back to SemiBold
   and rendered identically to the line above (measured: same advance width to
   0.1px). Inter has a real 400, and Glory-statement over Inter-aside is the
   pairing the rest of the site already uses. */
.fpx-promise__text span {
  display: block;
  margin-top: 10px;
  font-family: "Inter", sans-serif;
  /* Its own clamp, not an em of the line above: at 0.72em the punchline fell
     to 12px on a phone, below this page's floor for body text. */
  font-size: clamp(13.5px, 1.5vw, 18px);
  font-weight: 400;
  letter-spacing: 0;
}

/* ── Awards ──────────────────────────────────────────────────────────────── */

/* One per row on phones, two on tablets, four across on desktop. */
.fpx-home .fpx-awards {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(18px, 2vw, 24px);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 600px) {
  .fpx-home .fpx-awards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 900px) {
  .fpx-home .fpx-awards {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.fpx-awards__item {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ONE reveal on the <ul>, not one per item, and no stagger.

   Per-item staggered reveals were tried here and measured worse. Dropped
   frames over three runs each, images pre-decoded so only the arrangement
   differed:

     list, no stagger (this)        7, 10, 13   median 10
     no reveal at all              11, 11, 13   median 11
     per-item, no stagger          14, 16, 16   median 16
     per-item + stagger            19, 20, 24   median 20

   The list reveal is indistinguishable from having no animation at all, so
   the animation was never the cost — four separate transitions just mean
   four layer promotions instead of one, and a stagger keeps the compositor
   busy for longer. What actually made this section stutter was 2.44MB of
   award art decoding during the reveal; see awards.php. Do not "optimise"
   this into a stagger without re-running that measurement. */

/* No plate: the images sit straight on the section tint. */
.fpx-awards__link {
  display: block;
  border-radius: 6px;
  transition: transform 0.2s ease;
}

.fpx-awards__link:hover {
  transform: translateY(-3px);
}

.fpx-awards__link:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 6px;
}

/* The four award images are wildly different aspect ratios (a tall statuette
   beside a near-square certificate), so a fixed box with `contain` keeps the
   row optically even without cropping any of them. The box grows per
   breakpoint because the images get the full column width at one-up.

   The tallest box is 390px; the art ships at 880px (2x plus a little headroom). */
.fpx-awards__link img {
  display: block;
  width: 100%;
  /* One-up on a phone the column is ~350px wide, and the tallest source is
     0.46:1 — at 300px it rendered barely 139px across and left most of the
     column empty. */
  height: 380px;
  object-fit: contain;
  /* Sit on a shared baseline: a width-limited award (the wide Lasker
     statue) is shorter than its box and would otherwise float mid-air. */
  object-position: 50% 100%;
}

/* Bigger since 2026-09-26: at 340px the tall, narrow awards filled barely
   half their column and the row read as four small pictures with gaps. */
@media (min-width: 600px) {
  .fpx-awards__link img {
    height: clamp(260px, 34vw, 330px);
  }
}

@media (min-width: 900px) {
  .fpx-awards__link img {
    height: clamp(310px, 27vw, 390px);
  }
}

/* 14px, not 13px: this is the only caption on the page and it carries a real
   claim (the Lasker nomination), so it should not be set smaller than the
   page's smallest body text. Measured on --sky: 4.90:1, passes AA. */
.fpx-home .fpx-awards__caption {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}

/* ── Lightbox ────────────────────────────────────────────────────────────
   Built by home.js on first use and reused after that. Lives at the end of
   <body>, so it is not affected by any ancestor transform on the page. */

.fpx-lightbox {
  position: fixed;
  inset: 0;
  z-index: 100000; /* above the sticky header and the floating cart bubble */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 4vw, 48px);
  background: rgba(8, 26, 30, 0.88);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.22s ease, visibility 0.22s ease;
}

.fpx-lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.fpx-lightbox__figure {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin: 0;
  max-width: min(100%, 1100px);
}

.fpx-lightbox__img {
  display: block;
  max-width: 100%;
  /* Leaves room for the caption and the close button on short viewports. */
  max-height: min(78vh, 900px);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 6px;
  background: #fff;
}

.fpx-lightbox__caption {
  max-width: 62ch;
  margin: 0;
  font-family: "Inter", sans-serif;
  font-size: 14px;
  line-height: 1.55;
  text-align: center;
  color: rgba(255, 255, 255, 0.88);
}

.fpx-lightbox__btn {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.fpx-lightbox__btn:hover {
  background: rgba(0, 0, 0, 0.6);
  border-color: #fff;
}

.fpx-lightbox__btn:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.fpx-lightbox__close {
  top: calc(-1 * clamp(12px, 3vw, 20px) - 48px);
  right: 0;
}

.fpx-lightbox__prev {
  left: clamp(-64px, -4vw, -12px);
  top: 50%;
  transform: translateY(-50%);
}

.fpx-lightbox__next {
  right: clamp(-64px, -4vw, -12px);
  top: 50%;
  transform: translateY(-50%);
}

/* Below 720px there is no room beside the image, so the arrows drop under it
   and the close button sits inside the top-right corner of the frame. */
@media (max-width: 719px) {
  /* The controls are padded INTO the figure rather than hung outside it —
     negative offsets put them over the page behind the overlay, which read
     as though they belonged to the page rather than to the viewer. */
  .fpx-lightbox__figure {
    width: 100%;
    padding: 56px 0 68px;
  }

  .fpx-lightbox__img {
    max-height: 58vh;
  }

  .fpx-lightbox__close {
    top: 0;
    right: 0;
  }

  .fpx-lightbox__prev,
  .fpx-lightbox__next {
    top: auto;
    bottom: 6px;
    transform: none;
  }

  .fpx-lightbox__prev {
    left: calc(50% - 58px);
  }

  .fpx-lightbox__next {
    right: calc(50% - 58px);
  }
}

/* Body scroll lock. Both html and body: depending on the page's box model
   either one can be the scrolling element, and locking only body left the
   page scrollable behind the overlay. */
html.fpx-lightbox-open,
body.fpx-lightbox-open {
  overflow: hidden;
}

/* ── Icon cards ──────────────────────────────────────────────────────────── */

/* One per row on phones, two on tablets, 3 × 2 on desktop as on the live
   page. auto-fit was giving four columns at this container width, which broke
   the intended pairing and left an orphan row of two. */
.fpx-home .fpx-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(18px, 2vw, 26px);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 600px) {
  .fpx-home .fpx-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Three across only from 1180px. At 1000px it gave each card a 255px text
   box, which is 29 characters a line — below even the 35-char floor for
   phones, on a desktop screen. Two columns through that range read at ~47.
   Measured, not guessed: 29 chars at 1000px vs 40 at 1280px. */
@media (min-width: 1180px) {
  .fpx-home .fpx-cards {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* No border and no fill — the card is defined by a small shadow alone, so it
   sits on the white section rather than as a tinted block on it. */
.fpx-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(30px, 3.2vw, 42px) clamp(24px, 2.6vw, 32px);
  border: 0;
  border-radius: 18px;
  background: transparent;
  box-shadow: var(--lift-sm);
  text-align: center;
  transition: transform 0.22s ease, box-shadow 0.22s ease;
}

.fpx-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--lift);
}

/* Bare icon, no disc. Height is fixed so the titles still line up across the
   row whatever each glyph's own proportions are. */
.fpx-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 50px;
  /* Further from the title than the title is from its paragraph, so the two
     text elements group and the icon reads as a separate mark. They used to
     be an identical 20px optical gap either side. */
  margin-bottom: 24px;
  background: none;
  color: #148d9f; /* the brand gradient's deep stop */
}

/* Uppercased in CSS, not in the source strings, so the PHP keeps natural
   sentence case for translators and screen readers. Caps need positive
   tracking — the -0.01em that suits mixed case reads cramped here. */
/* 6px, which lands at ~14px optically once the half-leading of both line
   boxes is counted. A heading belongs to what follows it. */
.fpx-home .fpx-card__title {
  margin: 0 0 6px;
  max-width: 22ch;
  font-family: "Glory", sans-serif;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.26;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text);
  text-wrap: balance;
}

/* Leading scales inversely with measure. These lines run ~40 characters — a
   third shorter than the section ledes — so 1.72 left the block looking
   sparse and made the eye work to find the next line. Contrast on white is
   5.22:1, so do not lighten --muted here. */
.fpx-home .fpx-card__text {
  margin: 0;
  max-width: 40ch;
  font-size: 16px;
  line-height: 1.6;
  color: var(--muted);
}

/* ── Formula rows ────────────────────────────────────────────────────────
   Minimal, edge-bleed: photograph, heading, copy, button. The photo runs to
   the viewport edge and the copy stays on the page grid, so the asymmetry
   does the work that a panel or a plate would otherwise have to.

   The section is NOT inside .fpx-container (see products.php) — the row spans
   the full width and .fpx-formula__body supplies the page gutter itself. That
   gutter is expressed as PADDING, never width or margin, so it can never
   create horizontal overflow even though it is derived from 100vw (which
   includes the scrollbar).

   The only colour beyond body text is the small "What's in it?" label, which
   uses --accent, set per row in products.php from the label artwork. */

.fpx-formulas {
  display: grid;
  gap: clamp(56px, 7vw, 112px);
}

.fpx-formula {
  display: grid;
  gap: clamp(24px, 4vw, 64px);
  align-items: center;
}

/* Page gutter, matching .fpx-container: 1240px max with 20px padding, so the
   content column is 1200px and the gutter is whatever is left over. */
.fpx-formula__body {
  padding-inline: 20px;
}

@media (min-width: 880px) {
  .fpx-formula {
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  }

  /* `order`, not `direction`, so the DOM order — and therefore the reading
     order — stays image-then-copy on every row. */
  .fpx-formula--flip .fpx-formula__media {
    order: 2;
  }

  .fpx-formula__body {
    padding-left: 0;
    padding-right: max(20px, calc((100vw - 1200px) / 2));
  }

  .fpx-formula--flip .fpx-formula__body {
    padding-right: 0;
    padding-left: max(20px, calc((100vw - 1200px) / 2));
  }
}

/* A square render at half the viewport would be enormous on a wide screen, so
   the media is a fixed ratio. `contain` rather than `cover`: the renders are
   square and the box is landscape, so cover cropped the cap and base off the
   bottles.

   The background matches the renders' own backdrop so the space contain
   leaves at the sides is invisible. The renders originally shipped on a warm
   cream that fought the site's white/pale-blue palette; the backdrop was
   recoloured to this cool tint (see CLAUDE.md). Sampled edge values are
   #e1e7eb / #dae0e5 / #dde3e8 — this sits between them. */
.fpx-formula__media {
  aspect-ratio: 4 / 3;
}

@media (min-width: 880px) {
  .fpx-formula__media {
    aspect-ratio: 5 / 4;
  }
}

.fpx-formula__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

/* Rounded on the inner edge only — the outer edge is flush with the viewport,
   where a radius would look like a mistake. */
@media (min-width: 880px) {
  .fpx-formula:not(.fpx-formula--flip) .fpx-formula__img {
    border-radius: 0 16px 16px 0;
  }

  .fpx-formula--flip .fpx-formula__img {
    border-radius: 16px 0 0 16px;
  }
}

.fpx-home .fpx-formula__title {
  margin: 0 0 18px;
  font-family: "Glory", sans-serif;
  font-size: clamp(26px, 3.2vw, 38px);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--color-text);
}

/* Scales a little on very wide screens, where a flat 16px sat under a 38px
   title. The ceiling is low on purpose: these are the longest paragraphs on
   the page at ~61 characters a line, and 1.78 leading is already tuned to
   that measure — pushing the size further would change the measure too. */
.fpx-home .fpx-formula__text {
  margin: 0 0 18px;
  max-width: 58ch;
  font-size: clamp(16px, 1.02vw, 17.5px);
  line-height: 1.78;
  color: var(--muted);
}

/* A quiet aside, separated by a hairline rather than boxed or tinted. */
.fpx-formula__aside {
  margin: 28px 0 32px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
}

.fpx-home .fpx-formula__aside-title {
  margin: 0 0 10px;
  font-family: "Inter", sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Fallback matters: --accent is set by an inline style in products.php, and
     an undefined custom property makes the whole declaration invalid at
     computed-value time, which for `color` means inheriting. Any optimiser
     that strips inline styles would silently drop the label to body colour
     rather than leaving it visibly wrong. Measured on white: pX #1a3fa8
     8.68:1, Relax #d6135f 5.11:1, Immuno #1e7a34 5.40:1 — all pass AA. */
  color: var(--accent, #148d9f);
}

.fpx-home .fpx-formula__aside .fpx-formula__text:last-child {
  margin-bottom: 0;
}

@media (max-width: 879px) {
  /* Stacked: the photo still bleeds edge to edge, the copy keeps its gutter. */
  .fpx-formula__body {
    text-align: center;
  }

  .fpx-home .fpx-formula__text {
    margin-inline: auto;
  }

  .fpx-formula__aside {
    text-align: left;
  }
}

/* ── Testimonials ────────────────────────────────────────────────────────
   A slider, one quote at a time, no card or border. It does not autoplay —
   see testimonials.php for why — so there is no pause control to provide.

   The track is a flex row that translates; home.js sets --i to the active
   index and the CSS owns the transition, same mechanism as the hero. */

.fpx-quotes {
  position: relative;
  max-width: 860px;
  margin-inline: auto;
}

.fpx-quotes__viewport {
  overflow: hidden;
}

.fpx-quotes__track {
  display: flex;
  /* stretch, so every slide is as tall as the tallest and the section height
     does not jump between a long quote and a short one. */
  align-items: stretch;
  transform: translateX(calc(var(--i, 0) * -100%));
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.fpx-quote {
  flex: 0 0 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding-inline: clamp(0px, 3vw, 40px);
  text-align: center;
}

.fpx-quote__stars {
  display: inline-flex;
  gap: 3px;
  margin-bottom: 20px;
  color: #e0a325; /* amber, as on the live page */
}

.fpx-quote__stars svg {
  fill: currentColor;
}

.fpx-quote__text {
  margin: 0;
  padding: 0;
  border: 0;
  quotes: none;
}

/* The quote is the largest thing in the section — with no card around it, the
   type has to carry the weight on its own. */
.fpx-home .fpx-quote__text p {
  margin: 0;
  max-width: 52ch;
  font-family: "Glory", sans-serif;
  font-size: clamp(18px, 2.1vw, 24px);
  font-weight: 500;
  line-height: 1.58;
  letter-spacing: -0.01em;
  color: var(--color-text);
}

.fpx-quote__by {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 28px;
}

.fpx-quote__avatar {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  object-fit: cover;
  flex: none;
}

.fpx-quote__name {
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

/* Only one of the three reviewers has a location on the live page, so this is
   optional per quote rather than a required field. Set on its own line under
   the name, quieter, so a quote without one does not look like it is missing
   something. */
.fpx-quote__place {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  font-weight: 400;
  color: var(--muted);
}

/* Controls */

.fpx-quotes__arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: none; /* shown from 980px, where there is room beside the quote */
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  transform: translateY(-50%);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.fpx-quotes__arrow:hover {
  background: var(--surface);
  border-color: var(--brand-deep);
}

.fpx-quotes__arrow:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 2px;
}

@media (min-width: 980px) {
  .fpx-quotes__arrow {
    display: inline-flex;
  }

  .fpx-quotes__arrow--prev {
    left: -64px;
  }

  .fpx-quotes__arrow--next {
    right: -64px;
  }
}

.fpx-quotes__dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin-top: 26px;
}

/* 44px hit area around a small visual dot. */
.fpx-quotes__dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.fpx-quotes__dot-fill {
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: rgba(18, 52, 58, 0.24);
  transition: background-color 0.25s ease, width 0.25s ease;
}

.fpx-quotes__dot.is-active .fpx-quotes__dot-fill {
  width: 20px;
  background: var(--color-primary-dark);
}

.fpx-quotes__dot:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: -4px;
  border-radius: 999px;
}

/* ── Video wall ──────────────────────────────────────────────────────────
   Click-to-play facades: poster, play badge, title underneath. Nothing from
   youtube.com loads until a visitor activates one (see fpx_home_video()).

   The title sits below the poster rather than over it, so there is no scrim
   darkening every thumbnail and the title is plain dark text on the page. */

.fpx-home .fpx-videos {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(26px, 3vw, 40px) clamp(18px, 2vw, 28px);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 600px) {
  .fpx-home .fpx-videos {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1000px) {
  .fpx-home .fpx-videos {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.fpx-home .fpx-video {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}

/* The poster. hqdefault is 4:3 with letterbox bars baked in, so the frame is
   16:9 and the image covers it — that crops the bars off. */
.fpx-video__frame {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 12px;
  background: #0d2528;
}

.fpx-video__thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s ease, opacity 0.25s ease;
}

.fpx-video:hover .fpx-video__thumb {
  transform: scale(1.04);
  opacity: 0.9;
}

/* Play control: a small "▶ Watch" pill in the bottom-left corner, not a
   disc in the middle. Every centred version (frosted glass, teal disc,
   gradient disc) sat on someone's face in these interview posters; the
   corner keeps faces clear and still reads instantly as a video. White pill
   with a deep-teal glyph for contrast on any frame; fills with the brand
   gradient on hover. */
.fpx-video__play {
  position: absolute;
  left: 12px;
  bottom: 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px 0 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: #0e5763;
  font-family: "Inter", sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  box-shadow: 0 4px 14px rgba(8, 40, 46, 0.25);
  transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.fpx-video__play svg {
  width: 18px;
  height: 18px;
  margin-left: 2px;
}

.fpx-video:hover .fpx-video__play,
.fpx-video:focus-visible .fpx-video__play {
  background: var(--brand-grad, linear-gradient(340deg, #148d9f 30%, #56ccf2 100%));
  color: #fff;
  transform: translateY(-1px);
}

.fpx-home .fpx-video__title {
  display: block;
  margin-top: 14px;
  font-family: "Inter", sans-serif;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
  transition: color 0.2s ease;
}

.fpx-video:hover .fpx-video__title {
  color: var(--color-primary-dark);
}

.fpx-home .fpx-video:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 4px;
  border-radius: 14px;
}

/* The player home.js swaps in on click, keeping the title as its caption. */
.fpx-video-frame {
  margin: 0;
}

.fpx-video-frame__player {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 12px;
  overflow: hidden;
  background: #0d2528;
}

.fpx-video-frame__player iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ── Split rows (clinical + history) ─────────────────────────────────────── */

.fpx-split {
  display: grid;
  gap: clamp(24px, 3.5vw, 56px);
  align-items: center;
}

@media (min-width: 860px) {
  .fpx-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  /* Alternates against the clinical row above it. `order`, not `direction`,
     so the DOM order — and the reading order — stays copy-then-media. */
  .fpx-split--flip .fpx-split__media {
    order: -1;
  }
}

/* Ink, not teal: these two are left-aligned statement headings inside a row,
   like the formula titles. Teal is reserved for the centred section titles. */
.fpx-home .fpx-split__title {
  margin: 0 0 18px;
  font-family: "Glory", sans-serif;
  font-size: clamp(22px, 2.8vw, 34px);
  font-weight: 700;
  line-height: 1.22;
  letter-spacing: -0.015em;
  color: var(--color-text);
  text-wrap: balance;
}

.fpx-home .fpx-split__text {
  margin: 0 0 16px;
  max-width: 60ch;
  font-size: 16px;
  line-height: 1.72;
  color: var(--muted);
}

/* The closing statement. Separated by a hairline rather than boxed — a
   bordered white card here was the last remnant of the old panel language. */
.fpx-home .fpx-split__kicker {
  margin: 28px 0 0;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  font-family: "Glory", sans-serif;
  font-size: clamp(17px, 1.9vw, 21px);
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--color-text);
  max-width: 34ch;
}

/* The photo is shot on flat white, which read as a white card sitting on the
   tinted section. `multiply` drops that background into the section colour —
   the capsules stay dark, the bottle stays light, and the edges disappear.
   With no visible edge there is nothing for a radius to round. */
.fpx-split__img {
  display: block;
  width: 100%;
  height: auto;
  mix-blend-mode: multiply;
}

/* ── Contact strip ───────────────────────────────────────────────────────── */

/* Uses the same --brand-band as the promise band near the top, so the two
   bookend the page deliberately rather than being two different teals. Same
   accepted contrast caveat as that band — see the note on --brand-band. */
.fpx-contact {
  padding: clamp(40px, 4.5vw, 68px) 0;
  background-color: transparent;
  background-image: var(--brand-band);
}

.fpx-contact__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 22px;
  text-align: center;
}

@media (min-width: 860px) {
  .fpx-contact__inner {
    justify-content: space-between;
    text-align: left;
  }
}

.fpx-home .fpx-contact__text {
  margin: 0;
  max-width: 48ch;
  font-family: "Glory", sans-serif;
  font-size: clamp(19px, 2.2vw, 26px);
  line-height: 1.38;
  letter-spacing: -0.015em;
  font-weight: 600;
  color: #fff;
  text-wrap: balance;
}

/* Solid white on the band — the gradient button used everywhere else would
   disappear into it. No shadow, matching the rest of the minimal pass. */
.fpx-home .fpx-contact .fpx-btn--primary {
  background-color: #fff;
  background-image: none;
  color: var(--color-primary-dark);
  border-color: #fff;
  box-shadow: none;
}

.fpx-home .fpx-contact .fpx-btn--primary:hover {
  background-color: var(--color-primary-light);
  filter: none;
}

.fpx-home .fpx-contact .fpx-btn:focus-visible {
  outline-color: #fff;
}

/* ── Motion ──────────────────────────────────────────────────────────────
   Short, shallow reveals that acknowledge arrival.

   The hidden state is gated on `data-fpx-armed`, which ONLY home.js sets.
   Nothing on this page is hidden by CSS alone, because a page that hides its
   own content and relies on JavaScript to show it again has one failure mode
   too many: delay-JS-until-interaction, a bad combine pass or any earlier
   script error would leave whole sections permanently blank on a page that
   otherwise rendered fine. Unarmed means visible. See initReveals().

   Anything hidden here is also restored unconditionally under reduced
   motion, further down. */

.fpx-home [data-fpx-reveal][data-fpx-armed] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.55s ease, transform 0.55s ease;
}

.fpx-home [data-fpx-reveal][data-fpx-armed].is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .fpx-home *,
  /* animation-delay matters here as well as duration: the copy reveal uses
     `animation-fill-mode: both`, so a delay left in place would hold the copy
     at its hidden start state for a fifth of a second before popping in. */
  .fpx-hero * {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }

  .fpx-home [data-fpx-reveal],
  .fpx-home [data-fpx-reveal][data-fpx-armed] {
    opacity: 1;
    transform: none;
  }

  .fpx-video__thumb,
  .fpx-video:hover .fpx-video__thumb {
    transform: none;
  }
}
