/* ---------------------------------------------------------------
   home-v2.css — homepage variant only.
   Loaded after tokens.css / base.css / components.css / pages.css.

   Revision 4 — reference-matching:
   - Pillar panels: no margin, no border-radius, full-bleed.
   - "Pillar 01/02/03" labels removed.
   - Pillar gradients lifted verbatim from _references/hero/Hero-Gradients.html
     (presets: reality / impact / aurora). Solid opaque base + alpha layers.
   - Pillar SVGs adapted from _references/experiments/TMRW-SVG.html
     (motifs: hologram-layers / radar-pulse / quantum-strings).
   - Reduced motion: all animations off, end states applied.
--------------------------------------------------------------- */

/* ============================================================
   Site header — homepage v2 behavior:
   1. At the top of the page: position: absolute over the hero,
      transparent background, white logo/nav. No cream bar, no border,
      no blur. The header sits inside the hero stacking context.
   2. While scrolling through the hero: the header scrolls away with
      the hero (because it's absolutely positioned at top of body, not
      fixed). It is not visible during the rest of the hero scroll.
   3. After the hero leaves the viewport: js/main.js adds
      `.is-past-hero` to the header. The header then becomes
      position: fixed at the top with a cream background, dark text,
      a 1px subtle bottom border, and slides in from translateY(-100%).
   ============================================================ */
body.is-home-v2 .site-header {
  position: absolute;
  top: 0; left: 0; right: 0;
  background: transparent;
  border-bottom: 1px solid transparent;
  color: var(--color-text-on-dark);
  z-index: 30;
}

/* Cream sticky state — only when JS has confirmed the hero is fully behind us. */
body.is-home-v2 .site-header.is-past-hero {
  position: fixed;
  top: 0;
  background: var(--color-surface-page);     /* opaque cream */
  border-bottom-color: var(--color-border-subtle);
  color: var(--color-text-primary);
  /* No explicit transform here. The heroHeaderSlideIn animation
     interpolates transform during its run; with animation-fill-mode
     default (none), transform reverts to its CSS-declared value
     (initial = none) after the animation completes. This matters
     for the mobile drawer: when transform is none after slide-in,
     the header is NOT a containing block for the drawer's
     position: fixed, so the drawer correctly resolves to viewport
     anchors in sticky state. It also means the animation property
     is identical between .is-past-hero and .is-past-hero.is-drawer-open,
     so toggling the drawer no longer cancels and re-triggers the
     slide-in (which was the "sticky bar slides from top on drawer
     close" bug). */
  animation: heroHeaderSlideIn var(--motion-emphasis) var(--ease-standard);
}
@keyframes heroHeaderSlideIn {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  body.is-home-v2 .site-header.is-past-hero { animation: none; }
}
body.is-home-v2 .site-header__logo {
  display: inline-flex;
  align-items: center;
  position: relative;
}
/* Hero overlay header — generous breathing room, larger logo. */
body.is-home-v2 .site-header .site-header__inner {
  min-height: 96px;
  padding-block: var(--space-4);
}
body.is-home-v2 .site-header__logo-image {
  display: block;
  height: 36px;
  width: auto;
  /* Mobile wordmark cap (single source of truth — sticky bar AND open
     drawer share this value). min(60vw, 180px):
        320 → 60vw = 192px → 180px wins  (fluid floor sits below cap)
        ≥333 → 180px ceiling
     Intrinsic 220x32 aspect-ratio (declared via img width/height
     attrs) means height drops proportionally when max-width clamps,
     so the closed sticky logo and the open drawer logo render at
     identical dimensions on every mobile viewport — no perceived
     size mismatch between states. Desktop unchanged. */
  max-width: min(60vw, 180px);
}
@media (min-width: 768px) {
  body.is-home-v2 .site-header__logo-image { height: 40px; max-width: none; }
}

/* Sticky cream header — compact bar with the icon-only brandmark.
   Asymmetric padding-block is intentional: the sticky bar has a 1px
   bottom border (added by .site-header.is-past-hero) so the visible
   bar is min-height + 1px. align-items: center (inherited from
   components.css) centres contents in the 72px content box, which
   sits 0.5px above the visible-bar centre. Adding 2px to the top
   padding pushes contents down by ~1px, aligning the logo icon and
   nav-link line-boxes with the visible centre of the cream bar. The
   total min-height stays 72px because the icon (32px) + new padding
   (10 + 8 = 18px) = 50px is well below the min-height floor, so the
   border-box's min-height continues to win — the bar height is
   unchanged. */
body.is-home-v2 .site-header.is-past-hero .site-header__inner {
  min-height: 72px;
  padding-block: calc(var(--space-2) + 2px) var(--space-2);
}

/* Logo swap: white full wordmark over the hero (default).
   Three real SVG assets are present in the markup — white wordmark,
   dark wordmark (Phase 2 addition), and dark brandmark icon. The
   hidden variants use `display: none` (not opacity) so they don't
   reserve layout space, which matters because the wordmarks are
   wide (220px) and the icon is narrow (~48px) — they shouldn't
   share an intrinsic-size column.

   Default display map (desktop and mobile, no scroll):
     --white  visible  (hero overlay state, white-on-video)
     --dark   hidden   (only used on the mobile cream sticky bar)
     --icon   hidden

   Sticky state (.is-past-hero):
     desktop  → --icon visible, wordmarks hidden  (existing behavior)
     mobile   → --dark wordmark visible, --icon hidden (Phase 2)

   Drawer-open state overrides further below to always show --white
   on the dark drawer surface. */
body.is-home-v2 .site-header__logo-image--icon {
  display: none;                          /* hidden in hero state */
}
body.is-home-v2 .site-header__logo-image--dark {
  display: none;                          /* hidden in hero state */
}
body.is-home-v2 .site-header.is-past-hero .site-header__logo-image--white {
  display: none;                          /* hidden in sticky state */
}
/* Sticky cream bar — show the full DARK wordmark at all widths
   (mobile + desktop), hide the icon entirely. Previously desktop
   sticky showed the icon-only brandmark while mobile sticky showed
   the wordmark — that mismatch is now unified so all sticky states
   read with the full wordmark. The drawer-open state further
   below still overrides this to the WHITE wordmark on the dark
   drawer.
   Width/height inherit from the base .site-header__logo-image
   rule: mobile = max-width: min(60vw, 180px) so the wordmark
   never overflows narrow viewports; desktop (≥768) = height: 40px
   with max-width: none. The intrinsic 220x32 aspect-ratio from
   the img's width/height attributes keeps both states crisp. */
body.is-home-v2 .site-header.is-past-hero .site-header__logo-image--icon {
  display: none;
}
body.is-home-v2 .site-header.is-past-hero .site-header__logo-image--dark {
  display: block;
}
/* Tablet + desktop sticky — slightly taller bar (88px) with a
   36px-tall full wordmark. Replaces the previous 72px-bar / 32px-
   logo combo, which read as too cramped at desktop. Balance:
     - 88px bar gives ~26px breathing room above and below the
       36px wordmark
     - 36px wordmark renders ~247px wide via the intrinsic 220x32
       aspect ratio (width: auto)
   Mobile sticky (≤767px) keeps its 72px bar + 36px wordmark
   (capped by max-width: min(60vw, 180px)) — completely unchanged.
   Hero-overlay state at desktop keeps its full 40px wordmark from
   the base rule. NOTE: js/main.js uses a fixed STICKY_BAR_HEIGHT
   = 72 for the mobile #founder-ip anchor math, which is correct
   for mobile (bar stays 72 on mobile). Desktop's anchor math uses
   the live header.offsetHeight so it auto-adjusts to 88. */
@media (min-width: 768px) {
  body.is-home-v2 .site-header.is-past-hero .site-header__inner {
    min-height: 88px;
  }
  body.is-home-v2 .site-header.is-past-hero .site-header__logo-image--dark {
    height: 36px;
    width: auto;
  }
}
/* Desktop sticky (≥1024px) — bump the dark wordmark up to 40px so
   it reads more prominently in the wider 88px bar. Tablet
   (768–1023) keeps the 36px size from the rule above. Width:auto
   preserves the intrinsic 220x32 aspect ratio — 40px tall renders
   ~275px wide. The 88px bar still leaves ~24px of breathing room
   above and below. */
@media (min-width: 1024px) {
  body.is-home-v2 .site-header.is-past-hero .site-header__logo-image--dark {
    height: 40px;
    width: auto;
  }
}

/* Nav text colour: white over hero; dark when scrolled */
body.is-home-v2 .site-header__nav-link {
  color: var(--color-text-on-dark);
  opacity: 0.82;
}
body.is-home-v2 .site-header__nav-link:hover,
body.is-home-v2 .site-header__nav-link[aria-current="page"] {
  color: var(--color-text-on-dark);
  opacity: 1;
}
body.is-home-v2 .site-header.is-past-hero .site-header__nav-link {
  color: var(--color-text-primary);
  opacity: 0.78;
}
body.is-home-v2 .site-header.is-past-hero .site-header__nav-link:hover,
body.is-home-v2 .site-header.is-past-hero .site-header__nav-link[aria-current="page"] {
  color: var(--color-action-primary);
  opacity: 1;
}
body.is-home-v2 .site-header__toggle { color: var(--color-text-on-dark); }
body.is-home-v2 .site-header.is-past-hero .site-header__toggle { color: var(--color-text-primary); }

/* Editorial drawer copy — hidden by default so it doesn't appear in
   the desktop horizontal nav bar where .site-header__nav becomes a
   flex row. The mobile drawer block further down re-enables these
   elements with display: block. */
.site-header__drawer-eyebrow,
.site-header__drawer-intro,
.site-header__drawer-note {
  display: none;
}

@media (max-width: 1023px) {
  /* ============================================================
     Branded mobile drawer — HOMEPAGE variant (dark) + animation
     ============================================================
     Scoped to body.is-home-v2 so subpages keep the components.css
     cream drawer. The drawer becomes a near-full-screen overlay
     in the dark footer tone, with the full white wordmark, large
     nav links separated by hairline light-on-dark dividers, and
     the Contact item rendered as a filled button.

     Animation strategy
     ------------------
     The drawer panel is ALWAYS rendered at mobile widths (overriding
     the components.css `display: none` default) and gated by
     opacity / visibility / transform / pointer-events. This lets
     CSS transitions animate both opening AND closing without a
     JS-managed .is-drawer-closing intermediate state: the
     `visibility 0s linear 360ms` transition on the base rule keeps
     the panel visible until the opacity + transform finish, then
     flips it to `hidden`. The `.is-drawer-open` rule overrides the
     visibility delay back to 0s so opening is instantaneous.

     Layout
     ------
     The drawer uses position: fixed (not the components.css
     absolute) so the closed-but-rendered state never extends body
     scroll area. `top` follows the header's settled height:
     96px in the hero-overlay state, 72px once .is-past-hero
     applies. `bottom: 0` anchors the panel to the viewport.

     Drawer chrome (background, padding, gap, overflow-y) is applied
     on the always-rendered selector — not gated by .is-drawer-open
     — so the panel doesn't visibly snap mid-close-fade. */

  /* Pin the header to the viewport whenever the drawer is open so the
     drawer always lands under the visible header. .is-past-hero is
     already position: fixed, so this is a no-op there. */
  body.is-home-v2 .site-header.is-drawer-open {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background: var(--color-surface-inverse);
    color: var(--color-text-on-dark);
    border-bottom-color: transparent;
  }

  /* (Previously had a .is-past-hero.is-drawer-open rule that set
     transform: none + animation: none to work around the
     containing-block bug. No longer needed — the .is-past-hero rule
     above now lets transform revert to none via animation-fill-mode
     default, so the containing-block bug is fixed structurally and
     the drawer toggle no longer re-triggers the slide-in animation.) */

  /* Drawer panel — always rendered. Hidden state (default).
     The base rule defines the closed-state layout so the closed
     panel doesn't extend body scroll area (position: fixed) and
     so the close animation has something to transition from. */
  body.is-home-v2 .site-header__nav {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 96px;            /* default: under hero-overlay header */
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 29;          /* below header (z:30), above hero content */
    background: var(--color-surface-inverse);
    color: var(--color-text-on-dark);
    border-bottom: none;
    padding: var(--space-8) var(--layout-gutter) var(--space-12);
    gap: var(--space-2);
    overflow-y: auto;
    /* Closed: invisible AND non-interactive. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    pointer-events: none;
    /* Visibility transition is delayed by the transform duration so
       the panel stays in the layout long enough to render the
       fade/slide-out before flipping to hidden. */
    transition:
      opacity 260ms var(--ease-standard),
      transform 360ms var(--ease-standard),
      visibility 0s linear 360ms;
  }
  /* Once the header settles to the sticky 72px height, drawer shifts
     up to match so there's no visible gap between header bottom and
     drawer top. */
  body.is-home-v2 .site-header.is-past-hero .site-header__nav {
    top: 72px;
  }
  /* Open state — fully explicit / belt-and-suspenders rule.
     The previous version only set the toggleable properties
     (opacity, visibility, transform, pointer-events) and relied on
     specificity ordering to keep position / top / left / right /
     bottom / z-index / background / color / overflow-y from the
     base rule. That cascade was correct on paper, but the
     `visibility 0s linear 0s` transition is browser-quirky at zero
     duration AND zero delay (some engines elide the change entirely),
     and any sibling components.css rule with the same specificity
     could win for a property the open rule didn't repeat. This rule
     restates every visible-state property so the open drawer is
     guaranteed to render. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 96px;                              /* flush below hero-overlay header */
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 29;
    height: auto;
    min-height: 0;
    max-height: none;
    background: var(--color-surface-inverse);
    color: var(--color-text-on-dark);
    border-bottom: none;
    padding: var(--space-8) var(--layout-gutter) var(--space-12);
    gap: var(--space-2);
    overflow-y: auto;
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    pointer-events: auto;
    transition:
      opacity 260ms var(--ease-standard),
      transform 360ms var(--ease-standard),
      visibility 0s linear 0s;
  }
  /* Sticky-state override — drawer flush below the 72px sticky bar. */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav {
    top: 72px;
  }
  /* Generous spacing between large nav links. Each link is a row
     with its own bottom divider, mirroring the reference. Tap
     targets exceed 44px via padding-block alone. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-list {
    gap: 0;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link {
    display: block;
    padding: var(--space-5) 0;
    /* Drawer typography system — shared by nav-links AND the Contact
       CTA below. Exact 18px (no clamp) per brief. Tap target stays
       ≥44px via the --space-5 (20px) padding-block on each row. */
    font-size: 18px;
    line-height: 1.25;
    color: var(--color-text-on-dark);
    opacity: 1;
    border-bottom: 1px solid var(--color-border-on-dark);
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link:hover,
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link:focus-visible {
    color: var(--color-text-on-dark);
    opacity: 0.85;
  }
  /* Contact rendered as the same .button.button--primary system used
     for the footer "Contact us" CTA — signature blue background, white
     text, 4px radius, --space-3/--space-6 padding, --touch-target-min
     min-height, --color-action-primary-hover on hover. The list-item
     override removes the divider rule and adds breathing room above
     so the button sits as a distinct CTA, not another nav row. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-item--cta {
    margin-top: var(--space-8);
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link--cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--touch-target-min);
    /* Vertical padding dropped from --space-3 (12px) to --space-2
       (8px) so the larger drawer-typography font (clamp 20-24) +
       line-height 1.25 doesn't bloat the button: box renders ~44-46px
       across the clamp range instead of 49-54. Horizontal padding
       and 4px radius match the .button system used by the footer CTA. */
    padding: var(--space-2) var(--space-6);
    border: 1px solid transparent;
    border-radius: 4px;
    background: var(--color-action-primary);
    color: var(--color-text-on-dark);
    /* Same scale as the nav-links above so both read as one type
       system. Exact 18px per brief (no clamp). */
    font-size: 18px;
    line-height: 1.25;
    text-decoration: none;
    transition: background-color var(--motion-quick) var(--ease-standard);
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link--cta:hover,
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav-link--cta:focus-visible {
    background: var(--color-action-primary-hover);
    color: var(--color-text-on-dark);
    opacity: 1;
  }
  /* Logo swap inside the drawer — full WHITE wordmark on the dark
     drawer surface. Width cap inherited from the base
     .site-header__logo-image rule (min(60vw, 180px)), shared with
     the closed sticky bar so both states render at the same
     dimensions. Hides --dark + --icon explicitly so neither slips
     through when .is-past-hero and .is-drawer-open are active
     together. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__logo-image--white {
    display: block;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__logo-image--dark {
    display: none;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__logo-image--icon {
    display: none;
  }
  /* Close (X) icon — white, inheriting from color above. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__toggle {
    color: var(--color-text-on-dark);
  }

  /* Editorial drawer copy — eyebrow + supporting line above the nav
     list, and a small brand signature at the bottom of the panel.
     These elements are flex children of .site-header__nav (a column
     flex container), so margin-top: auto on the bottom note anchors
     it at the foot of the drawer regardless of nav-list length. The
     drawer's own overflow-y: auto handles content that exceeds the
     viewport height. Kept deliberately subtle — muted colour, smaller
     scale than nav-links — so the panel reads as a navigation menu
     with a brand touch rather than a second footer. */
  body.is-home-v2 .site-header.is-drawer-open .site-header__drawer-eyebrow {
    display: block;
    margin: 0;
    font-size: var(--type-eyebrow);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--color-text-on-dark-muted);
    line-height: 1.2;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__drawer-intro {
    display: block;
    margin: 0 0 var(--space-4);
    font-size: 16px;
    line-height: 1.4;
    color: var(--color-text-on-dark);
    opacity: 0.78;
    max-width: 30ch;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__drawer-note {
    display: block;
    /* margin-top: auto anchors the statement to the foot of the
       drawer's column flex. padding-top gives breathing room above
       so the statement doesn't crash into the Contact CTA. */
    margin-top: auto;
    padding-top: var(--space-10);
    /* Mobile-poster scale — expressive but not dominating. Previous
       clamp(56, 16vw, 96) was desktop-display range and overpowered
       the drawer.
         320 → 12vw = 38.4 → 40px floor
         390 → 46.8px
         430 → 51.6px
         ≥467 → 56px ceiling
       9ch max-width keeps long words from extending too far while
       letting short words stack into a poster feel. Color set per
       drawer state below. */
    font-size: clamp(40px, 12vw, 56px);
    line-height: 1;
    letter-spacing: -0.04em;
    max-width: 9ch;
    color: var(--color-text-on-dark);
    opacity: 1;
  }

  /* ============================================================
     Sticky/light drawer state — when .is-past-hero AND .is-drawer-open
     are both active. Drawer surface becomes cream so it visually
     belongs to the cream sticky bar.
     ------------------------------------------------------------
     All cream overrides use the .is-past-hero.is-drawer-open
     compound selector (specificity 0,5,0–0,5,1), so they win over
     the dark-drawer rules above (which max out at 0,4,1) without
     needing source-order gymnastics.

     IMPORTANT: this block deliberately does NOT touch `transform`
     or `animation` on the header. The .is-past-hero rule (top of
     this file) uses animation-fill-mode: none so transform reverts
     to none post-slide-in — that's what fixes BOTH the drawer
     containing-block bug AND the "sticky bar slides in again on
     drawer close" bug. Adding any transform/animation declarations
     here would re-introduce one or both of those bugs.
     ============================================================ */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open {
    background: var(--color-surface-page);
    color: var(--color-text-primary);
    border-bottom-color: transparent;
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav {
    background: var(--color-surface-page);
    color: var(--color-text-primary);
    border-bottom: none;
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link {
    color: var(--color-text-primary);
    border-bottom-color: var(--color-border-subtle);
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link:hover,
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link:focus-visible {
    color: var(--color-action-primary);
    opacity: 1;
  }
  /* Contact CTA preserves blue background + white text in cream
     drawer. Must come AFTER the nav-link rules above so the CTA's
     color and border cascade resolve correctly. */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link--cta {
    color: var(--color-text-on-dark);
    border-color: transparent;
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link--cta:hover,
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__nav-link--cta:focus-visible {
    background: var(--color-action-primary-hover);
    color: var(--color-text-on-dark);
    opacity: 1;
  }
  /* Editorial drawer copy — dark variants on cream. */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__drawer-eyebrow {
    color: var(--color-text-secondary);
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__drawer-intro {
    color: var(--color-text-primary);
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__drawer-note {
    color: var(--color-text-primary);
  }
  /* Toggle / X icon — dark on cream. */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__toggle {
    color: var(--color-text-primary);
  }
  /* Logo swap — show DARK wordmark, hide white wordmark + icon. */
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__logo-image--white {
    display: none;
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__logo-image--dark {
    display: block;
  }
  body.is-home-v2 .site-header.is-past-hero.is-drawer-open .site-header__logo-image--icon {
    display: none;
  }

  /* Burger ↔ X morph
     ---------------
     The toggle icon's three lines (the middle is .toggle-icon
     itself; the top/bottom are its ::before/::after, set in
     components.css with top: -7px / +7px) animate into an X.
     Same button, same position — the icon morphs in place.

     Closed → Open:
       middle  background-color → transparent  (200ms)
       top     top -7 → 0 + transform → rotate(45deg)   (280ms)
       bottom  top +7 → 0 + transform → rotate(-45deg)  (280ms)

     Open → Closed reverses each step at the same durations. */
  body.is-home-v2 .site-header__toggle-icon,
  body.is-home-v2 .site-header__toggle-icon::before,
  body.is-home-v2 .site-header__toggle-icon::after {
    transition:
      transform 280ms var(--ease-standard),
      top 280ms var(--ease-standard),
      background-color 200ms linear;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__toggle-icon {
    background: transparent;
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__toggle-icon::before {
    top: 0;
    transform: rotate(45deg);
  }
  body.is-home-v2 .site-header.is-drawer-open .site-header__toggle-icon::after {
    top: 0;
    transform: rotate(-45deg);
  }
}

/* Reduced motion — disable drawer panel + burger-to-X transitions.
   State changes still happen instantly via the opacity / visibility
   / transform / background swap; only the animation curves are
   suppressed. */
@media (prefers-reduced-motion: reduce) {
  body.is-home-v2 .site-header__nav,
  body.is-home-v2 .site-header.is-drawer-open .site-header__nav,
  body.is-home-v2 .site-header__toggle-icon,
  body.is-home-v2 .site-header__toggle-icon::before,
  body.is-home-v2 .site-header__toggle-icon::after {
    transition: none;
  }
}
/* The previous prefers-reduced-motion override targeted the
   opacity-cross-fade rules that have been removed (replaced by a
   display swap, which has no transition to disable). */

/* ============================================================
   Hero v2 — full-viewport jellyfish, no visible controls
   ============================================================ */
.hero-v2 {
  position: relative;
  isolation: isolate;
  contain: layout style paint;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* Editorial lower-left composition: the copy block anchors toward the
     bottom of the hero (justify-content: flex-end), but the generous
     bottom padding keeps it off the floor so it reads as an intentional
     ~70%-down baseline rather than a footer-anchored block. */
  justify-content: flex-end;
  padding-block: clamp(var(--space-16), 6vw, var(--space-20))
                 clamp(var(--space-16), 9vw, var(--space-24));
  color: var(--color-text-on-dark);
  background: #000;
  overflow: hidden;
}
.hero-v2__media,
.hero-v2__poster {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* object-position kept at center per the brief — the right-shift is
     handled entirely by transform: translateX below, mirroring the
     Main-Heroes.html reference's single-lever technique. */
  object-position: center;
  /* Composition shift: dialled back to 22% — clearly to the right of
     the headline column, but short of the 25% reference value (which
     exposed too much dark area on the left). Applied to both <video>
     and <img> poster so the reduced-motion fallback matches the
     live-video composition. */
  transform: translateX(22%);
  z-index: 0;
}
/* Mobile + tablet (≤1023px) — neutralise the desktop right-shift so
   the background video centres cleanly on every sub-desktop width.
   The 22% translateX was a desktop-landscape composition lever; on
   portrait/tablet viewports it exposes too much of the left edge,
   where the scrim's diagonal left-fade can't fully cover the gap
   and a visible black bar appears. `transform: none` + explicit
   `object-position: center center` keeps the video cover-cropped
   to the hero container with the centre of the frame anchored.
   Desktop (≥1024) inherits the base translateX(22%) from above.
   .is-home-v1-ab (index.html / TMRW-Foundation.mp4) is neutral via
   the (0,2,1) override below and unaffected. v3 is already centred
   (transform: none in home-v3.css) and unaffected. */
@media (max-width: 1023px) {
  .hero-v2__media,
  .hero-v2__poster {
    transform: none;
    object-position: center center;
  }
}
/* Mobile (≤767px), index-v2.html ONLY — jellyfish composition tweak.
   Scoped to body.is-home-v2-jellyfish, a class added only to
   index-v2.html (not to index.html, not to index-v3.html). Uses
   object-position to crop the cover-fitted video — NOT transform.
   The element still fills inset: 0 so there's no empty/black gap
   on the left; only the cropping origin moves.

   Why 30% (left-biased) puts the jellyfish on the RIGHT:
   With object-fit: cover on a landscape video inside a portrait
   container, the video is scaled up so the shorter dimension
   matches and the longer dimension overflows. object-position
   chooses which slice of the over-sized frame is visible.
     - 50% center: visible window centred on video centre → subject
       (centred in source) lands in the centre of the hero.
     - 30% center: visible window biased toward the LEFT of the
       source frame → the subject (still at video's 50% point)
       lands toward the RIGHT of the hero. This is the desired
       composition (mirror of the desktop translateX(22%) intent,
       but using crop instead of element shift).
     - 70% would do the opposite (subject toward the LEFT of hero). */
@media (max-width: 767px) {
  body.is-home-v2-jellyfish .hero-v2__media,
  body.is-home-v2-jellyfish .hero-v2__poster {
    transform: none;
    object-fit: cover;
    object-position: 30% center;
  }
}
/* Tablet (768–1023px), index-v2.html ONLY — jellyfish composition.
   At tablet portrait/landscape the hero is wider than phone so a
   stronger left bias reads cleanly without overcropping the
   subject. object-position: left center anchors the cover-cropped
   video frame to the LEFT edge of the element (= 0% center). The
   element still fills inset: 0, so no empty gap appears on either
   side. Scoped to body.is-home-v2-jellyfish (only on index-v2.html)
   so index.html and index-v3.html are unaffected. */
@media (min-width: 768px) and (max-width: 1023px) {
  body.is-home-v2-jellyfish .hero-v2__media,
  body.is-home-v2-jellyfish .hero-v2__poster {
    transform: none;
    object-fit: cover;
    object-position: left center;
  }
}
/* v1 A/B variant (index.html) — TMRW-Foundation.mp4 doesn't share the
   jellyfish video's extended fade area, so the 22% right-shift crops
   the frame. We reset positioning to neutral for the .is-home-v1-ab
   body class only. Specificity (0,2,1) beats the base rules above and
   overrides the mobile media queries as well (media queries don't add
   specificity). v2 jellyfish offset and v3 stay untouched. */
body.is-home-v1-ab .hero-v2__media,
body.is-home-v1-ab .hero-v2__poster {
  object-position: center center;
  transform: none;
}
.hero-v2__poster { display: none; }
.hero-v2__scrim {
  position: absolute; inset: 0;
  z-index: 1; pointer-events: none;
  /* Vertical scrim unchanged (top + bottom darkening for legibility).
     Horizontal scrim restored to a heavier left-fade — required because
     translateX(25%) again exposes ~25% of the hero on the left. The
     diagonal 100deg gradient (0.60 → 0.35 → 0) is lifted directly from
     the Main-Heroes.html reference (`linear-gradient(100deg,
     rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.35) 35%, rgba(0,0,0,0) 65%)`) so
     the seam between the dark left area and the video reads as part of
     the composition rather than a hard edge. */
  background:
    linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.75) 100%),
    linear-gradient(100deg, rgba(0,0,0,0.60) 0%, rgba(0,0,0,0.35) 35%, rgba(0,0,0,0) 65%);
}
.hero-v2__content {
  position: relative;
  z-index: 2;
  display: grid;
  gap: var(--space-8);
  /* Allow the headline to use more horizontal space; the clamp below still
     controls the font size, but the column itself can stretch wider so we
     don't force a 5-line block when 3-4 lines would read better. */
  max-width: min(96ch, 1100px);
}
.hero-v2__eyebrow {
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: rgba(255,255,255,0.78);
  line-height: 1.2;
}
.hero-v2__h1 {
  font-size: clamp(48px, 6.2vw, 104px);
  line-height: 1.02;
  letter-spacing: -0.035em;
  text-wrap: balance;
  margin: 0;
  color: var(--color-text-on-dark);
  max-width: 20ch;
}
/* Phase 1 (important, mobile, 320px): the clamp floor is 48px, which
   combined with the &nbsp;-joined pairs "Blue Ocean" / "Deep Tech."
   leaves no slack on a 280px content column. Drop the floor to 40px
   below 360px so the headline always wraps comfortably. Markup stays
   untouched; A/B differences intact. */
@media (max-width: 360px) {
  .hero-v2__h1 { font-size: 40px; }
}
.hero-v2__tagline {
  font-size: clamp(20px, 2.2vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: rgba(255,255,255,0.92);
  max-width: 48ch;
  margin: 0;
}
.hero-v2__lead {
  font-size: var(--type-body-lg);
  line-height: 1.55;
  color: rgba(255,255,255,0.78);
  max-width: 60ch;
  margin: 0;
}
/* Subtle hero scroll CTA — sits as a grid child of .hero-v2__content,
   so the parent's `gap: var(--space-8)` controls spacing from the lead.
   No boxed button: a quiet white text link with a hairline underline
   and a soft downward-nudge on the arrow on hover/focus. Anchor-only;
   scroll-behavior + reduced-motion reset live in base.css. */
.hero-v2__scroll-cta {
  margin: 0;
}
.hero-v2__scroll-cta-link {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5em;
  font-size: var(--type-body);
  line-height: 1.4;
  color: rgba(255,255,255,0.88);
  text-decoration: none;
  border-bottom: 1px solid rgba(255,255,255,0.4);
  /* Phase 1 (important, mobile): grow the tap target to the 44px
     minimum without moving the visual underline. padding-top expands
     the box upward; padding-bottom is unchanged so the hairline border
     stays exactly 2px below the text baseline. min-height locks the
     final hit area on phones where line-height alone wouldn't reach
     44px. Desktop behaviour is visually identical. */
  padding: var(--space-3) 0 2px;
  min-height: 44px;
  transition: color 160ms ease, border-color 160ms ease;
}
.hero-v2__scroll-cta-link:hover,
.hero-v2__scroll-cta-link:focus-visible {
  color: #fff;
  border-bottom-color: rgba(255,255,255,0.92);
}
.hero-v2__scroll-cta-link:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 4px;
  border-radius: 1px;
}
.hero-v2__scroll-cta-arrow {
  display: inline-block;
  transition: transform 220ms ease;
}
.hero-v2__scroll-cta-link:hover .hero-v2__scroll-cta-arrow,
.hero-v2__scroll-cta-link:focus-visible .hero-v2__scroll-cta-arrow {
  transform: translateY(2px);
}
@media (prefers-reduced-motion: reduce) {
  .hero-v2__scroll-cta-link,
  .hero-v2__scroll-cta-arrow { transition: none; }
  .hero-v2__scroll-cta-link:hover .hero-v2__scroll-cta-arrow,
  .hero-v2__scroll-cta-link:focus-visible .hero-v2__scroll-cta-arrow { transform: none; }
}
@media (max-width: 1023px) {
  .hero-v2 { padding-block: var(--space-20) var(--space-12); min-height: 90svh; }
}
/* Phone-only override (≤767px) — restore full viewport-height hero so
   the cream Founder & IP section isn't visible at the bottom of the
   initial load. Tablet portrait (768–1023) keeps the 90svh value from
   the rule above. 100vh declared first as a fallback for engines that
   don't support 100svh; 100svh wins on modern mobile browsers and
   correctly accounts for the dynamic browser chrome. */
/* Mobile + tablet (≤1023px) — hero fills the full viewport so the
   next section isn't visible at the bottom on initial load. Wins
   over the earlier @media (max-width: 1023px) rule that set
   min-height: 90svh (same specificity, later in source). Desktop
   (≥1024) keeps the base hero min-height: 100vh / 100svh. */
@media (max-width: 1023px) {
  .hero-v2 {
    min-height: 100vh;
    min-height: 100svh;
  }
}
@media (prefers-reduced-motion: reduce) {
  .hero-v2__media { display: none; }
  .hero-v2__poster { display: block; }
}

/* ============================================================
   Invisible scroll anchor — used by the hero CTA to land at the
   start of a section's visible content without painting a focus
   outline on the next sibling (the visible eyebrow). position:
   relative keeps the element in normal flow so its
   getBoundingClientRect().top reflects exactly where the next
   piece of copy starts. main.js focuses this element with
   preventScroll: true after the anchor scroll lands, and the
   :focus rule below ensures the temporary focus doesn't render
   a visible ring. No global .sr-only is reused because that
   utility uses position: absolute + negative margin, which would
   detach the anchor from flow and make its bounding rect
   unreliable as a scroll target.
   ============================================================ */
.section-scroll-anchor {
  position: relative;
  display: block;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
.section-scroll-anchor:focus {
  outline: none;
}

/* ============================================================
   KPI v2 — cream surface, no extra divider line.
   Layout: eyebrow / heading / intro / whitespace / KPI row (each item has its own top rule).
   ============================================================ */
.kpi-v2-section {
  background: var(--color-surface-page);
  color: var(--color-text-primary);
  padding-block: clamp(var(--space-20), 10vw, var(--space-32));
}
.kpi-v2-section__intro {
  display: grid;
  gap: var(--space-6);
  max-width: 70ch;
  margin-bottom: clamp(var(--space-16), 8vw, var(--space-24));
  /* No border-bottom — the KPI items below carry their own top rules. */
}
.kpi-v2-section__title {
  font-size: clamp(40px, 5vw, 72px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.kpi-v2-section__intro .lead {
  color: var(--color-text-secondary);
  max-width: 62ch;
}
.kpi-v2 {
  display: grid;
  gap: clamp(var(--space-12), 5vw, var(--space-20));
}
@media (min-width: 768px) {
  .kpi-v2 { grid-template-columns: repeat(3, 1fr); }
}
.kpi-v2__item {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border-subtle);
}
/* KPI value — single typographic scale for both numeric (600+, $5B+)
   and text values (AI & 3D). The previous base was too large compared
   with the AI & 3D text modifier; the row now reads as one calmer,
   consistent system at this lower scale. */
.kpi-v2__value {
  font-size: clamp(40px, 5vw, 80px);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--color-text-primary);
}
/* Text-based KPI ("AI & 3D") — same size as the numeric base now,
   with marginally looser tracking since text characters don't need
   the same negative spacing as tightly-packed digits. */
.kpi-v2__value--text {
  font-size: clamp(40px, 5vw, 80px);
  letter-spacing: -0.025em;
}
.kpi-v2__label {
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.kpi-v2__note {
  /* Bumped from caption 13px → 15px → 16px so it reads as comfortable
     editorial prose in the KPI row (matches the homepage news teaser
     body size). Line-height 1.5 retained. */
  font-size: 16px;
  line-height: 1.5;
  color: var(--color-text-secondary);
  max-width: 38ch;
  margin: 0;
}
.kpi-v2-section__cta { margin-top: clamp(var(--space-16), 8vw, var(--space-20)); }

/* ============================================================
   PILLAR STACK — full-bleed sticky panels, no margin, no radius.
   Each panel uses the exact gradient recipe from Hero-Gradients.html
   (preset names referenced inline below).
   ============================================================ */
.pillar-stack {
  position: relative;
  isolation: isolate;
  background: var(--color-surface-page);
}
.pillar-stack__panel {
  position: sticky;
  /* True viewport-height chapters. Panel height = 100svh, top
     offset = sticky-navbar height (88px on tablet/desktop, 72px
     on mobile via the override below). The panel's bottom edge
     overshoots the visible viewport by `top` pixels, which is
     intentional: sticky behavior keeps the panel visible at
     viewport.top = top-offset until its natural bottom reaches
     viewport.top = 0, so each chapter occupies the full viewport
     before the next overlaps. Negative sibling margins were
     removed — sticky alone produces a natural chapter-by-chapter
     scroll, and the +1px subpixel buffer is no longer needed
     because there's no `calc(100svh - X)` mismatch to align. */
  top: 88px;
  margin: 0;
  border-radius: 0;
  height: 100vh;
  height: 100svh;
  min-height: 100svh;
  width: 100%;
  box-sizing: border-box;
  overflow: hidden;
  isolation: isolate;
  /* Single-cell grid + place-items: center centres the inner content
     reliably regardless of its size. */
  display: grid;
  place-items: center;
}
/* Mobile (≤767px) — sticky navbar is 72px tall instead of 88px, so
   only the sticky top offset changes; panel height stays a full
   100svh to preserve the full-chapter feel. */
@media (max-width: 767px) {
  .pillar-stack__panel {
    top: 72px;
    height: 100vh;
    height: 100svh;
    min-height: 100svh;
  }
}
/* Short viewports (landscape phones, small laptops in dev tools):
   trim headline + visual + padding so the chapter still fits one
   screen. Height/min-height now come from the width-scoped rules
   above; only the typography + spacing trims live here. */
@media (max-height: 760px) {
  .pillar-panel__h2 { font-size: clamp(32px, 4vw, 56px); }
  .pillar-panel__inner { padding-block: var(--space-8); gap: var(--space-6); }
  .pillar-panel__visual { min-height: 160px; }
  .pillar-svg { width: clamp(200px, 22vw, 320px); }
}
/* Very small viewports — keep full-viewport chapters even on short
   landscape phones / dev-tools resizes. The previous `height: auto`
   escape hatch made panels shrink to content, breaking the chapter
   feel. Forcing 100svh keeps each chapter immersive; on extreme
   heights internal `overflow: hidden` clips any content overflow. */
@media (max-height: 560px) {
  .pillar-stack__panel {
    height: 100vh;
    height: 100svh;
    min-height: 100svh;
  }
}
/* Adjacent panel spacing — zero. The previous negative margin
   forced each new panel to start before its predecessor's natural
   bottom, which made the next chapter enter too early. With margin
   0, the next panel begins exactly at the previous panel's natural
   bottom — sticky alone produces the chapter-by-chapter overlap as
   the next panel scrolls over the previous one's sticky-locked
   position. The overlap effect is preserved without forcing it. */
.pillar-stack__panel + .pillar-stack__panel {
  margin-top: 0;
}

.pillar-panel__base {
  /* Solid opaque base colour — guarantees the panel is never transparent. */
  position: absolute;
  inset: 0;
  z-index: 0;
}
.pillar-panel__gradient {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.pillar-panel__motion {
  /* Optional animated layer matching the Hero-Gradients preset motion. */
  position: absolute;
  inset: -18%;
  z-index: 2;
  pointer-events: none;
}
.pillar-panel__scrim {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.pillar-panel__inner {
  position: relative;
  z-index: 5;
  display: grid;
  gap: clamp(var(--space-8), 4vw, var(--space-12));
  width: 100%;
  height: 100%;
  max-width: var(--layout-max);
  margin: 0 auto;
  box-sizing: border-box;
  padding: clamp(var(--space-10), 4vw, var(--space-16))
           clamp(var(--space-6), 4vw, var(--space-16));
  /* align-items centres each cell vertically within its row track.
     align-content centres the row tracks themselves within the
     100%-height grid container. Both are needed: without
     align-content the auto-sized row collects at the top and the
     content reads as "stuck near the top" rather than centred. */
  align-items: center;
  align-content: center;
}
@media (min-width: 1024px) {
  .pillar-panel__inner {
    /* Copy column gets ~62% of width; visual ~38%, right-aligned. */
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
  }
}
.pillar-panel__copy { display: grid; gap: var(--space-6); }
.pillar-panel__visual {
  display: flex;
  align-items: center;
  /* SVG flush with the right edge of the content grid — same column
     line as the nav / contact alignment in the header above. */
  justify-content: flex-end;
  justify-self: end;
  width: 100%;
  min-height: 220px;
}
@media (max-width: 1023px) {
  /* Mobile / tablet — pillar SVG as ambient bottom-right signature.
     ---------------------------------------------------------------
     Positioning anchor: the FULL panel (.pillar-stack__panel), not
     the inner content wrapper. Previous attempts kept inner as
     position: relative, which trapped the visual's position:
     absolute inside the inner — and because the inner's height did
     not consistently fill the panel under `display: grid;
     place-items: center`, the visual landed mid-panel rather than
     at the panel's bottom edge.

     Approach: drop inner to position: static at this breakpoint.
     The visual's `position: absolute` then walks up the ancestor
     chain past the (now-non-positioned) inner and resolves to
     .pillar-stack__panel (which is position: sticky — a valid
     containing block). bottom/right offsets are measured from the
     panel's outer box, which is fixed at 100svh.

     Stacking trade-off: with inner no longer positioned, its
     `z-index: 5` (from the base rule) no longer applies. By
     default, that means the panel's four positioned background
     layers (base/gradient/motion/scrim, originally z: 0–3) would
     paint above the non-positioned inner content. To preserve
     the visual layering, those four layers get negative z-indexes
     here so they paint BEHIND the in-flow inner content. The
     visual itself gets z: -1 — same negative range as the
     backgrounds but later in source order than scrim (-2), so it
     paints above all four backgrounds and still below the inner
     content above it.

     Desktop (≥1024px) is unaffected — none of these overrides
     apply, so inner stays position: relative; z-index: 5 and the
     four background layers keep their original z: 0, 1, 2, 3. */
  .pillar-panel__inner {
    position: static;
  }
  .pillar-panel__base     { z-index: -5; }
  .pillar-panel__gradient { z-index: -4; }
  .pillar-panel__motion   { z-index: -3; }
  .pillar-panel__scrim    { z-index: -2; }
  .pillar-panel__visual {
    position: absolute;
    right: clamp(16px, 5vw, 32px);
    /* Lifted from clamp(24, 7vw, 56) to clamp(72, 12vw, 120) so the
       SVG stays visible a bit longer before the next sticky pillar
       panel slides up to cover it. The sticky overlap behavior
       itself is unchanged — only the SVG's resting height inside
       its panel is nudged upward. Still well below the CTA. */
    bottom: clamp(72px, 12vw, 120px);
    left: auto;
    top: auto;
    z-index: -1;
    width: auto;
    min-height: 0;
    margin: 0;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    pointer-events: none;
    opacity: 0.45;
    transform: none;
  }
  .pillar-svg {
    width: clamp(150px, 38vw, 240px);
    max-width: none;
  }
}
/* Tablet (768–1023px) — pillar layout rebalanced to a 60/40 grid.
   On tablet we have enough horizontal room that the previous
   absolute-positioned SVG read as small and loose. Switching to a
   relative two-column grid pulls the SVG into its own intentional
   side column and tightens the composition.

   Layout:
     - inner gets grid-template-columns: 3fr / 2fr (≈60/40)
     - column-gap clamp gives breathing room that scales with width
     - visual flips back to position: relative (sitting in the
       right grid track) and explicitly resets top/right/bottom/
       transform/z-index that the mobile rule above set for the
       bottom-right absolute placement
     - copy max-width is removed so the column can fully expand
   Mobile (≤767) keeps the bottom-right SVG rule above untouched.
   Desktop (≥1024) keeps the 1.25fr/0.75fr split via the base. */
@media (min-width: 768px) and (max-width: 1023px) {
  .pillar-panel__inner {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(220px, 2fr);
    align-items: center;
    column-gap: clamp(var(--space-8), 6vw, var(--space-14));
  }
  .pillar-panel__copy {
    max-width: none;
  }
  .pillar-panel__visual {
    position: relative;
    top: auto;
    right: auto;
    bottom: auto;
    transform: none;
    width: 100%;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    opacity: 0.65;
    z-index: auto;
    margin: 0;
  }
  .pillar-svg {
    width: clamp(220px, 28vw, 360px);
    max-width: 100%;
  }
}
.pillar-panel__eyebrow {
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  opacity: 0.78;
}
/* Pillar headline — strong chapter title, NOT another hero. */
.pillar-panel__h2 {
  /* Aligned with .kpi-v2__value type scale so pillar headlines and
     KPI numbers read at the same weight family across breakpoints.
     Was clamp(36px, 4.8vw, 80px). */
  font-size: clamp(40px, 5vw, 80px);
  line-height: 1.03;
  letter-spacing: -0.025em;
  text-wrap: balance;
  margin: 0;
  max-width: 17ch;
}
/* .pillar-panel__statement has been removed — the headline now lives in
   .pillar-panel__h2 and the statement role no longer exists. */
.pillar-panel__lead {
  font-size: clamp(17px, 1.4vw, 22px);
  line-height: 1.5;
  max-width: 56ch;
  margin: 0;
}
.pillar-panel__cta { margin-top: var(--space-2); }
/* Editorial CTA link — shared by pillar CTAs and any other section
   that wants the same calm editorial treatment (KPI section "Explore
   the IP portfolio" etc.). The pillar BEM class is kept as a co-
   selector so the existing markup keeps working without changes; the
   neutral `.editorial-cta-link` is the preferred class for use
   outside of pillars. Underline reads as VISIBLE-by-default and
   retracts on hover (the inverse of the global `.link-editorial`
   pattern, which is intentionally a different interaction). */
.editorial-cta-link,
.pillar-panel__cta-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-body);
  padding: var(--space-3) 0;
  position: relative;
  color: inherit;
}
.editorial-cta-link::after,
.pillar-panel__cta-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 4px;
  height: 1px;
  background: currentColor;
  transform: scaleX(1);
  transform-origin: left;
  transition: transform var(--motion-emphasis) var(--ease-standard);
}
.editorial-cta-link:hover::after,
.pillar-panel__cta-link:hover::after {
  transform: scaleX(0);
  transform-origin: right;
}
.pillar-panel__cta--disabled {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  font-size: var(--type-caption);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  border-radius: 999px;
  cursor: default;
}

/* ---------- Panel — TMRW Reality ----------
   Source: Hero-Gradients.html preset "reality".
   :host vars (preset): --red #F2071B, --crimson #D91136, --magenta #BF1B57,
                        --purple #5F328C, --blue #056CF2, --white #FFFFFF.
   .hero background recipe (verbatim):
     linear-gradient(334deg, var(--red) 0%, var(--crimson) 20%, var(--magenta) 40%,
                              var(--purple) 58%, var(--blue) 78%, var(--magenta) 100%)
   Motion recipe (verbatim from .gradient-motion):
     - background-size: 190% 190%
     - keyframes reality-reality-motion: 0% pos 0%50% rot 0deg; 24% pos 100%50%;
       42% rot 10deg scale 1.06; 68% pos 0%50% rot 10deg; 86% rot 0deg; 100% rot 0deg scale 1
     - 11s ease-in-out infinite
   .gradient-motion-2 (atmospheric layer, secondary palette):
     linear-gradient(120deg, rgba(242,7,27,0.90) 0%, rgba(217,17,54,0.72) 24%,
                              rgba(191,27,87,0.56) 46%, rgba(95,50,140,0.48) 64%,
                              rgba(5,108,242,0.82) 82%, rgba(242,7,27,0.78) 100%)
   ---------- */
.pillar-panel--reality { color: var(--color-text-on-dark); }
.pillar-panel--reality .pillar-panel__base {
  background: #BF1B57; /* opaque magenta base — guarantees opacity */
}
.pillar-panel--reality .pillar-panel__gradient {
  /* Expanded beyond viewport so the rotate(10deg) + scale(1.06) keyframes
     never expose a hard corner edge. Panel still has overflow: hidden. */
  inset: -25%;
  background: linear-gradient(
    334deg,
    #F2071B 0%,
    #D91136 20%,
    #BF1B57 40%,
    #5F328C 58%,
    #056CF2 78%,
    #BF1B57 100%
  );
  background-size: 220% 220%;
  background-position: 0% 50%;
  animation: pillarRealityMotion 11s ease-in-out infinite;
  transform-origin: center center;
}
.pillar-panel--reality .pillar-panel__motion {
  /* matches .gradient-motion-2 in the experiment; also expanded to hide edges */
  inset: -30%;
  background: linear-gradient(
    120deg,
    rgba(242, 7, 27, 0.90) 0%,
    rgba(217, 17, 54, 0.72) 24%,
    rgba(191, 27, 87, 0.56) 46%,
    rgba(95, 50, 140, 0.48) 64%,
    rgba(5, 108, 242, 0.82) 82%,
    rgba(242, 7, 27, 0.78) 100%
  );
  background-size: 240% 240%;
  background-position: 100% 50%;
  opacity: 0.30;
  filter: blur(36px);
  animation: pillarRealityMotion2 14s ease-in-out infinite;
}
.pillar-panel--reality .pillar-panel__scrim {
  background: radial-gradient(ellipse at center,
    rgba(255,255,255,0.035) 0%,
    rgba(255,255,255,0.012) 44%,
    rgba(255,255,255,0) 76%);
}
@keyframes pillarRealityMotion {
  0%   { background-position: 0% 50%;   transform: rotate(0deg)  scale(1); }
  24%  { background-position: 100% 50%; transform: rotate(0deg)  scale(1.02); }
  42%  { background-position: 100% 50%; transform: rotate(10deg) scale(1.06); }
  68%  { background-position: 0% 50%;   transform: rotate(10deg) scale(1.03); }
  86%  { background-position: 0% 50%;   transform: rotate(0deg)  scale(1.01); }
  100% { background-position: 0% 50%;   transform: rotate(0deg)  scale(1); }
}
@keyframes pillarRealityMotion2 {
  0%   { background-position: 100% 50%; transform: rotate(0deg) scale(1); }
  24%  { background-position: 30% 60%;  transform: rotate(0deg) scale(1.03); }
  42%  { background-position: 30% 60%;  transform: rotate(-8deg) scale(1.08); }
  68%  { background-position: 100% 40%; transform: rotate(-8deg) scale(1.04); }
  86%  { background-position: 100% 40%; transform: rotate(0deg) scale(1.02); }
  100% { background-position: 100% 50%; transform: rotate(0deg) scale(1); }
}

/* ---------- Panel — TMRW Impact ----------
   Source: Hero-Gradients.html preset "impact".
   :host vars (preset): --impact-light #EFF5EB, --impact-soft #DCE9DF,
                        --impact-sage #9FB4A2, --impact-muted #3F5E46,
                        --impact-forest #274D36, --impact-dark #033722,
                        --text #033722.
   .tmrw-gradient background recipe (verbatim):
     radial-gradient(circle at 18% 22%, rgba(239,245,235,0.92) 0%, rgba(239,245,235,0) 38%),
     radial-gradient(circle at 78% 72%, rgba(63, 94, 70, 0.24) 0%, rgba(63, 94, 70, 0) 42%),
     linear-gradient(135deg, var(--impact-light) 0%, var(--impact-soft) 38%,
                              rgba(159,180,162,0.56) 62%, rgba(39,77,54,0.26) 100%)
   The :host carries a solid background of var(--impact-light) under the gradient,
   which is why the recipe contains alpha stops without going transparent.
   Motion: the preset uses 6 blurred radial blobs drifting; we recreate two
   composite blob layers (CSS only, no JS), matching colour stops.
   ---------- */
.pillar-panel--impact { color: #033722; }
.pillar-panel--impact .pillar-panel__base {
  background: #EFF5EB; /* opaque cream base = var(--impact-light) from preset */
}
.pillar-panel--impact .pillar-panel__gradient {
  background:
    radial-gradient(circle at 18% 22%, rgba(239, 245, 235, 0.92) 0%, rgba(239, 245, 235, 0) 38%),
    radial-gradient(circle at 78% 72%, rgba(63, 94, 70, 0.24) 0%, rgba(63, 94, 70, 0) 42%),
    linear-gradient(135deg, #EFF5EB 0%, #DCE9DF 38%, rgba(159, 180, 162, 0.56) 62%, rgba(39, 77, 54, 0.26) 100%);
}
.pillar-panel--impact .pillar-panel__motion {
  /* Two composite blob fields (from preset .blob-2 and .blob-3 colour stops),
     drifting via translate keyframes. mix-blend-mode: screen matches the experiment. */
  background:
    radial-gradient(circle at 80% 18%, rgba(220, 233, 223, 0.78) 0%, rgba(220, 233, 223, 0) 60%),
    radial-gradient(circle at 16% 80%, rgba(239, 245, 235, 0.92) 0%, rgba(239, 245, 235, 0) 64%),
    radial-gradient(circle at 50% 60%, rgba(159, 180, 162, 0.42) 0%, rgba(159, 180, 162, 0) 60%);
  mix-blend-mode: screen;
  filter: blur(60px);
  opacity: 0.85;
  animation: pillarImpactDrift 12s ease-in-out infinite alternate;
}
.pillar-panel--impact .pillar-panel__scrim {
  background: radial-gradient(ellipse at center,
    rgba(255,255,255,0.20) 0%,
    rgba(255,255,255,0.06) 42%,
    rgba(255,255,255,0) 72%);
}
@keyframes pillarImpactDrift {
  from { transform: translate3d(-1vw, -1vh, 0) scale(1); }
  to   { transform: translate3d(2vw, 2vh, 0)   scale(1.06); }
}

/* ---------- Panel — TMRW Aurora ----------
   Source: Hero-Gradients.html preset "aurora".
   :host vars (preset): --deep-blue #0D2052, --ocean-blue #1D4B73,
                        --signal-blue #3088BF, --sky-blue #B2E0F7,
                        --photon-cyan #88E8F2, --mid-blue #4EB1D9.
   .hero background recipe (verbatim):
     radial-gradient(circle at 16% 28%, rgba(13,32,82,0.95) 0%, transparent 42%),
     radial-gradient(circle at 82% 72%, rgba(13,32,82,0.82) 0%, transparent 44%),
     linear-gradient(135deg, var(--deep-blue) 0%, var(--deep-blue) 28%,
                              var(--ocean-blue) 54%, var(--signal-blue) 78%,
                              var(--mid-blue) 100%)
   .hero::before — conic field (verbatim):
     conic-gradient(from 90deg at 50% 50%,
       rgba(13,32,82,1) 0%, rgba(13,32,82,1) 24%,
       rgba(29,75,115,0.96) 45%, rgba(48,136,191,0.68) 62%,
       rgba(78,177,217,0.42) 76%, rgba(136,232,242,0.22) 88%,
       rgba(13,32,82,1) 100%)
     filter: blur(92px); opacity: 0.96; animation: aurora-aurora-rotate-field 13s linear infinite
   ---------- */
/* Aurora — text colours lifted directly from Hero-Gradients.html preset "aurora":
     :host { color: var(--white); }                              →  #FFFFFF
     .eyebrow { color: rgba(136, 232, 242, 0.82); }              →  photon-cyan @ 0.82
     h1       { color: var(--photon-cyan); }                     →  #88E8F2
   Applied below to eyebrow, h2 (title), statement, paragraph, Coming-soon chip,
   and the SVG stroke colour (currentColor). */
/* Aurora — text in the photon-cyan family rather than pure white.
   Headline = solid photon-cyan; body = photon-cyan @ 0.78 so the
   paragraph reads as part of the same blue/cyan family as the title. */
.pillar-panel--aurora { color: rgba(136, 232, 242, 0.78); }
.pillar-panel--aurora .pillar-panel__eyebrow { color: rgba(136, 232, 242, 0.82); }
.pillar-panel--aurora .pillar-panel__h2      { color: #88E8F2; }      /* photon-cyan */
.pillar-panel--aurora .pillar-panel__lead    { color: rgba(136, 232, 242, 0.78); }
.pillar-panel--aurora .pillar-panel__base {
  background: #0D2052; /* opaque deep-blue base */
}
.pillar-panel--aurora .pillar-panel__gradient {
  background:
    radial-gradient(circle at 16% 28%, rgba(13, 32, 82, 0.95) 0%, transparent 42%),
    radial-gradient(circle at 82% 72%, rgba(13, 32, 82, 0.82) 0%, transparent 44%),
    linear-gradient(135deg, #0D2052 0%, #0D2052 28%, #1D4B73 54%, #3088BF 78%, #4EB1D9 100%);
}
.pillar-panel--aurora .pillar-panel__motion {
  /* Expanded inset so the rotate keyframes never expose a corner edge */
  inset: -35%;
  background: conic-gradient(
    from 90deg at 50% 50%,
    rgba(13, 32, 82, 1) 0%,
    rgba(13, 32, 82, 1) 24%,
    rgba(29, 75, 115, 0.96) 45%,
    rgba(48, 136, 191, 0.68) 62%,
    rgba(78, 177, 217, 0.42) 76%,
    rgba(136, 232, 242, 0.22) 88%,
    rgba(13, 32, 82, 1) 100%
  );
  filter: blur(92px);
  opacity: 0.96;
  animation: pillarAuroraRotateField 13s linear infinite;
  transform-origin: center;
}
.pillar-panel--aurora .pillar-panel__scrim {
  background:
    radial-gradient(ellipse at 20% 30%, rgba(0,0,0,0.20) 0%, rgba(0,0,0,0) 60%),
    linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.25) 100%);
}
.pillar-panel--aurora .pillar-panel__cta--disabled {
  /* Photon-cyan border + text, matching the Aurora preset palette */
  color: rgba(136, 232, 242, 0.86);
  border: 1px solid rgba(136, 232, 242, 0.38);
}
@keyframes pillarAuroraRotateField {
  from { transform: rotate(0deg)   scale(1.04); }
  to   { transform: rotate(360deg) scale(1.04); }
}

/* ============================================================
   Pillar SVG visuals — adapted from
   /_references/experiments/TMRW-SVG.html.
   The experiment uses CSS class ".line-1px" with optional ".dim" /
   ".very-dim" modifiers. Reproduced here as ".line-1px",
   ".line-1px-dim", ".line-1px-very-dim".
   Reduced motion = no animation, static end states.
   ============================================================ */
.pillar-svg {
  /* Smaller still — an accent, not a competing element. */
  width: clamp(220px, 22vw, 380px);
  height: auto;
  aspect-ratio: 1 / 1;
  overflow: visible;
}
.pillar-svg .line-1px,
.pillar-svg .line-1px-dim,
.pillar-svg .line-1px-very-dim {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.pillar-svg .line-1px-dim       { opacity: 0.55; }
.pillar-svg .line-1px-very-dim  { opacity: 0.30; }

/* ---------- Reality — TMRW-SVG.html SVG #1 (hologram-layers) ----------
   15 ellipses, rx=70 ry=25, with paired <animate> on cy (40→-40→40, 6s)
   and opacity (0→0.5→0, 6s), staggered begin="-${i*0.2}s".
   Reproduced as 15 static <ellipse> with CSS keyframes (translateY +
   opacity) and :nth-of-type(n) animation-delays mirroring the JS offsets. */
.pillar-svg--reality { color: rgba(255, 255, 255, 0.85); }
.pillar-svg--reality .hologram-ellipse {
  transform-box: fill-box;
  transform-origin: center;
  animation: pillarHologramOscillate 6s linear infinite;
}
@keyframes pillarHologramOscillate {
  0%   { transform: translateY(40px);  opacity: 0; }
  25%  { transform: translateY(0);     opacity: 0.5; }
  50%  { transform: translateY(-40px); opacity: 0; }
  75%  { transform: translateY(0);     opacity: 0.5; }
  100% { transform: translateY(40px);  opacity: 0; }
}
.pillar-svg--reality .hologram-ellipse:nth-of-type(1)  { animation-delay:  0s;   }
.pillar-svg--reality .hologram-ellipse:nth-of-type(2)  { animation-delay: -0.2s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(3)  { animation-delay: -0.4s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(4)  { animation-delay: -0.6s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(5)  { animation-delay: -0.8s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(6)  { animation-delay: -1.0s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(7)  { animation-delay: -1.2s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(8)  { animation-delay: -1.4s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(9)  { animation-delay: -1.6s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(10) { animation-delay: -1.8s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(11) { animation-delay: -2.0s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(12) { animation-delay: -2.2s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(13) { animation-delay: -2.4s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(14) { animation-delay: -2.6s; }
.pillar-svg--reality .hologram-ellipse:nth-of-type(15) { animation-delay: -2.8s; }

/* ---------- Impact — TMRW-SVG.html SVG #2 (radar / pulse rings) ----------
   The experiment uses SMIL <animate attributeName="r" values="0;90">.
   The same SMIL <animate> elements are now embedded directly in the
   markup (see index-v2.html). No CSS animation needed here — SMIL drives
   the r and opacity attributes natively, keeping the 1px stroke clean
   (CSS transform: scale would have thickened the stroke).
   Static crosshair lines use .line-1px-dim, matching the reference's
   "line-1px dim" pair. */
.pillar-svg--impact { color: rgba(3, 55, 34, 0.72); }

/* ---------- Aurora — TMRW-SVG.html SVG #3 (quantum-strings) ----------
   The experiment: bordered square (rect 40,40 → 160,160, "line-1px very-dim"),
   horizontal+vertical crosshair lines, three anchor circles at (40,100),
   (100,100), (160,100), and 8 sine-wave <path> elements driven by a JS
   requestAnimationFrame loop computing y = sin(x*freq+time+i) * 30 *
   exp(-((x-100)/40)^2) with opacity = 0.1 + |sin(time + i*0.5)| * 0.4.
   Reproduced here without JS: each path is a quadratic curve with a built-in
   Gaussian-envelope wave shape, and SMIL <animate attributeName="d">
   morphs between two phase-shifted snapshots of the experiment's wave
   (giving a continuous flowing-string effect). Opacity SMIL <animate>
   matches the experiment's 0.1 → 0.5 → 0.1 envelope. The 8 paths are
   staggered by 0.4s, matching the experiment's i*0.5s phase factor. */
/* SVG stroke uses photon-cyan with alpha — same hue as the title, slightly
   dimmer so the strings read as background fabric rather than primary text. */
.pillar-svg--aurora { color: rgba(136, 232, 242, 0.72); }
.pillar-svg--aurora .anchor-dot {
  fill: currentColor;
  stroke: none;
}

/* ---------- Reduced motion: freeze SMIL + CSS animations ----------
   Hides SMIL <animate> via display:none (treated as "not running" by the
   SVG spec — the parent element retains its initial attribute values), and
   zeroes CSS animations on the Reality hologram. */
@media (prefers-reduced-motion: reduce) {
  .pillar-panel__gradient,
  .pillar-panel__motion,
  .pillar-svg *,
  .pillar-svg *::before,
  .pillar-svg *::after {
    animation: none !important;
    transition: none !important;
  }
  .pillar-svg animate { display: none; }
  .pillar-svg--reality .hologram-ellipse {
    opacity: 0.4;
    transform: translateY(0);
  }
}

/* ============================================================
   News v2 — cream editorial grid (matches KPI / Milestones tone)
   ============================================================ */
.news-v2-section {
  background: var(--color-surface-page);
  color: var(--color-text-primary);
  /* Lower-section rhythm: symmetric padding-block (~56-96px) so this
     section reads as part of a calm vertical cadence with Milestones
     and the press strip below. */
  padding-block: clamp(56px, 7vw, 96px);
}
/* NOTE — the previous .news-v2-section > .container border-top has been
   removed: there should be no separator line directly between the last
   pillar and News. The transition is now last-pillar → cream whitespace
   → News heading, with no horizontal rule at that boundary. */
.news-v2-grid {
  display: grid;
  /* r47 — gap matched to .kpi-v2 (clamp(var(--space-12), 5vw,
     var(--space-20))) so the news columns share the same rhythm
     as the KPI columns underneath. On wide desktops this widens
     the inter-column breathing room from a fixed 48px to up to
     80px; on mobile the grid collapses to one column anyway so
     the clamp's smaller end (48px) doesn't matter visually. */
  gap: clamp(var(--space-12), 5vw, var(--space-20));
}
@media (min-width: 768px) {
  .news-v2-grid { grid-template-columns: repeat(3, 1fr); }
}
.news-v2-entry {
  /* v2 (A/B variant): text-only editorial teasers. The earlier
     hairline above each item has been removed — the page had too
     many horizontal rules. Cards now read as clean text blocks
     separated only by the surrounding .news-v2-grid gap.
     v3 remains the image-based variant for comparison. */
  display: grid;
  gap: var(--space-3);
}
.news-v2-entry__meta {
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.news-v2-entry__title {
  font-size: clamp(22px, 2.2vw, 32px);
  line-height: 1.18;
  letter-spacing: -0.01em;
}
/* v2 (A/B variant) — text-only news teasers, no image markup, so
   the .news-v2-entry__media / .news-v2-entry__image rules from the
   image variant have been removed. They remain in home-v3.css where
   v3 still renders the image cards. */
/* Homepage news titles intentionally render without the global
   .link-editorial underline-on-hover treatment — they read as a
   calm editorial item, not a list link. The arrow glyph has also
   been dropped from the visible text. The link itself remains
   clickable; only the trailing-1px underline pseudo is suppressed. */
.news-v2-entry__title .link-editorial::after {
  content: none;
}
/* News teaser body paragraph — currently rendered with class="caption"
   (a global 13px utility). Override to 16px / 1.5 inside the homepage
   news entry so the teaser body reads as comfortable editorial prose
   rather than a tight caption. Scoped via the parent so the global
   .caption rule is unaffected elsewhere. */
.news-v2-entry .caption {
  font-size: 16px;
  line-height: 1.5;
}

/* ============================================================
   Milestones — cream surface
   ============================================================ */
.milestones-section {
  background: var(--color-surface-page);
  color: var(--color-text-primary);
  /* r46 — asymmetric padding-block.
     Top: keeps the original 56-96 rhythm with the News section
       above; the hairline border-top sits in that breathing space.
     Bottom: tightened to 24-48 so the transition to the press-strip
       section below feels balanced (press-strip's own padding-top
       is 56-96, which now reads as the dominant gap rather than
       being doubled). */
  padding-top: clamp(56px, 7vw, 96px);
  padding-bottom: clamp(24px, 3vw, 48px);
  /* Border lives on the section itself (not the inner container) so it
     sits exactly at the section boundary, with section padding-block
     above and below it providing balanced breathing room. The cream
     surface extends edge-to-edge, like the navbar's full-width border. */
  border-top: 1px solid var(--color-border-subtle);
}
/* The earlier `.milestones-section > .container { border-top ... }`
   rule has been removed — see border on the section itself above. */
.milestones-section .timeline { border-left: 1px solid var(--color-border-subtle); }
.milestones-section .timeline__year { color: var(--color-text-secondary); }

/* ============================================================
   Horizontal homepage timeline (.timeline-h)
   Alternating journey layout — one continuous horizontal hairline
   runs through the vertical centre of the track; items live as
   absolutely-positioned columns ABOVE the line (odd) or BELOW it
   (even). A marker dot on each column sits on the line; a short
   vertical connector links the dot to that item's content block
   (image + date + title). Lives inside .milestones-section.
   Homepage v2/v3 only — global vertical .timeline component on
   subpages is untouched. No JS.
   ============================================================ */
.timeline-h__hint {
  display: inline-block;
  margin: 0;
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-secondary);
  opacity: 0.78;
}
@media (min-width: 1280px) {
  /* The fade-edges already signal scrollability on wide desktops, so
     the verbal hint is suppressed where it would feel redundant. */
  .timeline-h__hint { display: none; }
}

.timeline-h {
  /* Tokens for the circular-node journey.
       --card-w        narrow item column — circle + label only
       --circle-d      diameter of the circular image
       --line-y        vertical position of the central axis
       --marker-d      diameter of the node dot
       --marker-half   half of the node dot
       --connector-h   short vertical stem from node to cluster
       --gap-from-line marker-half + connector + small buffer */
  /* r39 — corrected density toward the reference: narrower clusters
     so more nodes fit horizontally, tighter inter-item gap below. */
  --card-w: clamp(170px, 16vw, 220px);
  /* r46 — circle reverted to the pre-r42 size (104-128px). The r42
     bump made the thumbnails feel teaser-heavy; this returns to the
     diagrammatic / compact reading. */
  --circle-d: clamp(104px, 8vw, 128px);
  --line-y: 50%;
  /* r50 — smaller marker dot (was 14px) to match the lighter dashed
     axis treatment. */
  --marker-d: 10px;
  --marker-half: 5px;
  /* Connector + gap-from-line tightened further so each cluster sits
     close to the axis and the journey reads as continuous. */
  --connector-h: 12px;
  --gap-from-line: 20px;

  position: relative;
  /* r45 — full-bleed scroll viewport on BOTH sides. The scroller
     extends from viewport-left to viewport-right, so when items are
     scrolled they can pan all the way to either viewport edge
     without being clipped at the .container boundaries.
     The container-aligned INITIAL start position is provided by the
     track's own `padding-inline-start` (see .timeline-h__track
     below), not by limiting the scroller's width. */
  width: 100vw;
  max-width: none;
  margin-inline: calc(50% - 50vw);
  overflow-x: auto;
  overflow-y: hidden;
  /* r46 vertical rhythm — bottom trimmed further so the gap to the
     press-strip section below feels balanced, not heavy. Top buffer
     unchanged. */
  padding-block: clamp(8px, 1.5vw, 20px) clamp(8px, 1.5vw, 20px);
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* Align scroll-snap to the container content edge — same formula
     as the track's padding-inline-start. Items snap so their start
     edge sits at the .container left edge (not the viewport left
     edge), matching the initial layout's visual rhythm. */
  scroll-padding-inline-start:
    max(var(--layout-gutter),
        calc((100vw - var(--layout-max)) / 2 + var(--layout-gutter)));
  -webkit-overflow-scrolling: touch;
  /* r41 — fade-edge mask removed from the timeline scroller. The
     press-strip marquee fade is unaffected (different selector). */
  /* r42 — visually hide the scrollbar while keeping the scroller
     fully native and interactive. Trackpad / mouse-wheel / touch /
     swipe / keyboard arrow scrolling all still work because we're
     not setting `overflow: hidden`. */
  scrollbar-width: none;             /* Firefox */
  -ms-overflow-style: none;          /* Legacy Edge / IE */
}
.timeline-h::-webkit-scrollbar { display: none; }  /* WebKit / Blink */
.timeline-h:focus-visible {
  outline: 1px solid var(--color-text-primary);
  outline-offset: 4px;
}

.timeline-h__track {
  display: flex;
  /* r49 inter-item gap — slight bump from r42 (was 32-56). +8px on
     both ends gives items a touch more horizontal air without going
     back to the earlier sparse spacing. Multiple nodes still
     visible at 1440px desktop. */
  gap: clamp(40px, 3vw, 64px);
  padding-block: 0;
  /* r45 — track inset matches .container's outer offset on both
     sides, so the first item INITIALLY aligns with the section
     heading (left) and the last item can scroll to the same inset
     on the right. The scroller itself is full-bleed (above), so
     items pan all the way to the viewport edges when scrolled.
     Math: max(gutter, (100vw - layout-max)/2 + gutter) always
     equals the distance from viewport-edge to .container-content-
     edge at any viewport width. */
  padding-inline:
    max(var(--layout-gutter),
        calc((100vw - var(--layout-max)) / 2 + var(--layout-gutter)));
  min-width: max-content;
  /* r46 — track height eased down (was 360-440) now that the
     circles are smaller again; still enough for top circle + axis +
     bottom copy without clipping. */
  min-height: clamp(340px, 36vw, 420px);
  position: relative;
}
.timeline-h__track::before {
  /* r51 — horizontal axis: dashed, slightly stronger than r50 (was
     0.15 alpha → now 0.20). Reads more clearly without becoming a
     section-divider-style hairline; the dashed pattern at the same
     alpha as solid --color-border-subtle still recedes naturally
     compared to the solid section dividers because of the gaps in
     the stroke. */
  content: "";
  position: absolute;
  top: var(--line-y);
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dashed rgba(31, 39, 27, 0.20);
  background: none;
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 0;
}

/* r40 — split-side layout. Each item is a 3-row grid:
     row 1: top zone   (image OR copy, depending on modifier)
     row 2: marker     (the dot, sitting on the central axis)
     row 3: bottom zone (the other one)
   The 1fr/auto/1fr template centres the marker row at the item's
   vertical midpoint, which coincides with the track's --line-y (50%).
   `align-self: end` on row 1 / `start` on row 3 pulls each block
   toward the centre so the cluster reads as one milestone hanging
   off a single node. */
.timeline-h__item {
  flex: 0 0 var(--card-w);
  scroll-snap-align: start;
  align-self: stretch;          /* fill the track's content-box height */
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
  position: relative;
  z-index: 1;
}

.timeline-h__marker {
  grid-row: 2;
  width: var(--marker-d);
  height: var(--marker-d);
  border-radius: 50%;
  background: var(--color-text-primary);
  /* Cream halo isolates the node from the line so it reads as a
     deliberate stop on the journey, not a stray dot. */
  box-shadow: 0 0 0 4px var(--color-surface-page);
  position: relative;
  z-index: 2;
}
/* Short vertical stems extending up and down from the marker toward
   the image/copy blocks. Drawn via pseudo-elements so no extra
   markup is needed. r50 — dashed + lighter, matching the horizontal
   axis. r51 — bumped slightly (was 0.15) but kept 4 percentage-
   points BELOW the axis alpha so the hierarchy reads:
       connectors (0.16) ≤ axis (0.20) < section dividers (solid 0.20).
   Still dashed; same border-left + width: 0 technique so the dash
   pattern renders crisply on a thin vertical line. */
.timeline-h__marker::before,
.timeline-h__marker::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: var(--connector-h);
  border-left: 1px dashed rgba(31, 39, 27, 0.16);
  background: none;
  z-index: 1;
}
.timeline-h__marker::before { bottom: calc(100% + 2px); }
.timeline-h__marker::after  { top:    calc(100% + 2px); }

/* Image-on-top variant: media sits in row 1 (above the axis),
   copy sits in row 3 (below the axis). Both hug the marker via
   align-self. */
.timeline-h__item--image-top .timeline-h__media {
  grid-row: 1;
  align-self: end;
  margin-bottom: var(--gap-from-line);
}
.timeline-h__item--image-top .timeline-h__copy {
  grid-row: 3;
  align-self: start;
  margin-top: var(--gap-from-line);
}
/* Copy-on-top variant: mirror. */
.timeline-h__item--copy-top .timeline-h__copy {
  grid-row: 1;
  align-self: end;
  margin-bottom: var(--gap-from-line);
}
.timeline-h__item--copy-top .timeline-h__media {
  grid-row: 3;
  align-self: start;
  margin-top: var(--gap-from-line);
}

.timeline-h__copy {
  display: grid;
  /* Tight 4px row gap binds date → title closely together. */
  gap: var(--space-1);
  text-align: center;
  width: 100%;
  max-width: var(--card-w);
}

.timeline-h__media {
  /* Circular image — fixed diameter (rather than aspect-ratio +
     width:100%) keeps every node visually identical regardless of
     cluster width. r40: no margin shorthand — the variant rules
     above apply `margin-bottom` (image-top) or `margin-top`
     (copy-top) via `var(--gap-from-line)` so the circle hugs the
     marker on the correct side. */
  display: block;
  width: var(--circle-d);
  height: var(--circle-d);
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 50%;
  background: var(--color-border-subtle);
  margin: 0;
  /* Subtle 1px ring — elegant punctuation, not a card border. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.timeline-h__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.timeline-h__date {
  margin: 0;
  font-size: var(--type-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.timeline-h__title {
  margin: 0;
  font-size: 16px;
  line-height: 1.35;
  font-weight: 400;
  color: var(--color-text-primary);
  text-wrap: pretty;
  /* r39 — title max-width pulled in to 210px so the text block stays
     a compact caption sitting beneath / above the circle, matching
     the dense reference. Titles wrap naturally; no truncation. */
  max-width: 210px;
}

/* ----------------------------------------------------------------
   Mobile — smaller circles, line stays near the top of the track,
   all clusters live below it (no alternating) so circles don't
   collide on a narrow viewport. */
@media (max-width: 767px) {
  /* r42 mobile — bigger circles to match desktop bump; tighter
     padding-block since the scrollbar is hidden. */
  .timeline-h {
    --card-w: clamp(180px, 60vw, 220px);
    /* r46 mobile — circle reverted to pre-r42 size. */
    --circle-d: clamp(96px, 32vw, 112px);
    --connector-h: 10px;
    --gap-from-line: 14px;
    padding-block: 8px 8px;
  }
  .timeline-h__track {
    min-height: 300px;
    padding-block: 0;
    /* Mobile: at viewport < --layout-max the calc collapses to just
       var(--layout-gutter), which matches the .container's inset. */
    padding-inline:
      max(var(--layout-gutter),
          calc((100vw - var(--layout-max)) / 2 + var(--layout-gutter)));
    gap: var(--space-6);
  }
  .timeline-h__title { font-size: 14px; }
}

/* Reduced motion — kill smooth scroll and snap so user-initiated
   scrolling is instant. The hairline, dots, and connectors are
   static visuals and remain unchanged. */
@media (prefers-reduced-motion: reduce) {
  .timeline-h {
    scroll-behavior: auto;
    scroll-snap-type: none;
  }
}

/* ============================================================
   Press logo marquee.
   No visible label, edge-to-edge cream band with a soft linear-gradient
   mask fading content in/out on both sides. The track contains two
   identical <ul> sets side-by-side; the animation slides the track by
   exactly 50% of its width per cycle, returning it to the same visual
   position seamlessly. Each list carries a trailing inline padding
   equal to the internal logo gap so the seam between list 1's last
   logo and list 2's first logo matches the rest of the row.
   No JS, no libraries, no marquee plugin.
   Source markup hooks: .press-strip, .press-strip__viewport,
   .press-strip__track, .press-strip__logos, .press-strip__item,
   .press-strip__logo (+ per-logo modifiers).
   ============================================================ */
.press-strip {
  background: #FBF9F0;                 /* cream surface, per brief */
  color: var(--color-text-primary);
  /* Symmetric vertical rhythm matching News and Milestones. Section
     padding-block provides equal breathing room above the border (from
     the Milestones content end) and below the border (before the
     marquee viewport begins). */
  padding-block: clamp(56px, 7vw, 96px);
  /* The hairline sits on the SECTION (unmasked), not on the inner
     viewport (which carries the left/right fade mask). That keeps the
     line a normal solid subtle rule — it does NOT fade with the
     logos. */
  border-top: 1px solid var(--color-border-subtle);
}
.press-strip__viewport {
  position: relative;
  /* r52 — reverted r48's content-width formula. The wider previous
     sizing reads better visually, so we go back to OUTER-width
     matching .container (max-width + padding-inline). The fade
     mask spans the outer box; it overlaps slightly with what would
     be other sections' padding-inline area, but that wider band
     was preferred over the strictly content-aligned version. */
  width: 100%;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding-inline: var(--layout-gutter);
  box-sizing: border-box;
  overflow: hidden;
  /* The previous border-top + padding-top has moved to .press-strip
     (the unmasked parent). This element is responsible only for the
     fade mask and for clipping the wider track — nothing else. */
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0%, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(90deg,
    transparent 0%, #000 12%, #000 88%, transparent 100%);
}
.press-strip__track {
  display: flex;
  width: max-content;
  /* Calm, premium speed. translateX 0 → -50% over 36s = one full cycle
     every 36s. Linear so the motion never accelerates or decelerates
     (any easing would betray the seam at the loop point). */
  animation: pressMarqueeSlide 36s linear infinite;
  will-change: transform;
}
@keyframes pressMarqueeSlide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.press-strip__logos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  /* Internal logo spacing. */
  gap: clamp(var(--space-12), 6vw, var(--space-20));
  /* Trailing padding equals the internal gap so the spacing between
     the last logo of list 1 and the first logo of list 2 reads as one
     normal gap — making the translateX(-50%) loop visually seamless. */
  padding-inline-end: clamp(var(--space-12), 6vw, var(--space-20));
}
.press-strip__item {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;                      /* never shrink under flex pressure */
  /* No card, no shadow, no border. */
}
.press-strip__logo {
  display: block;
  /* Per the brief: every logo renders at 100px tall by default; width
     stays auto so each mark keeps its native aspect ratio. No per-logo
     optical-balance override — Al Arabiya's stacked mark is allowed to
     read taller within its natural bounding box at the shared height. */
  height: 100px;
  width: auto;
  object-fit: contain;
  /* Marks are already dark / black PNGs from the Webflow export; render
     them at a slightly muted opacity so the row reads as supportive
     proof rather than competing with adjacent sections. */
  opacity: 0.82;
}

@media (max-width: 767px) {
  /* Reduce section padding so the strip is less tall vertically.
     Was clamp(56, 7vw, 96); now clamp(36, 10vw, 56). */
  .press-strip {
    padding-block: clamp(36px, 10vw, 56px);
  }
  /* Wider horizontal spacing between logos — was clamp(40, 9vw, 64),
     now clamp(40, 14vw, 64) for a noticeably calmer rhythm. The
     padding-inline-end matches the gap value so the marquee loop
     seam (translateX(-50%) between list 1 and list 2) reads as one
     normal gap. */
  .press-strip__logos {
    gap: clamp(40px, 14vw, 64px);
    padding-inline-end: clamp(40px, 14vw, 64px);
  }
  /* Moderate mobile logo size — readable / premium, only ~16-28%
     smaller than the base 100px. Previous clamp(24, 7vw, 34) was
     far too aggressive. height drives the size; max-height: none
     unsets any inherited cap; max-width is a sane ceiling so very
     wide marks don't dominate. object-fit: contain (from the base)
     preserves aspect ratio within these bounds. */
  .press-strip__logo {
    height: clamp(72px, 18vw, 84px);
    max-height: none;
    max-width: clamp(140px, 42vw, 190px);
  }
  /* Marquee speed unchanged. */
  .press-strip__track { animation-duration: 28s; }
}

@media (prefers-reduced-motion: reduce) {
  /* Reduced motion: stop the marquee but keep a static, readable row.
     The duplicate list is hidden, the remaining list is centred, and
     the fade is softened so the first / last logos in the static row
     remain readable. */
  .press-strip__track {
    animation: none;
    transform: none;
    justify-content: center;
    width: 100%;
  }
  .press-strip__logos {
    flex-wrap: wrap;
    justify-content: center;
    padding-inline-end: 0;
  }
  .press-strip__logos[aria-hidden="true"] { display: none; }
  .press-strip__viewport {
    -webkit-mask-image: linear-gradient(90deg,
      transparent 0%, #000 4%, #000 96%, transparent 100%);
            mask-image: linear-gradient(90deg,
      transparent 0%, #000 4%, #000 96%, transparent 100%);
  }
}

/* ============================================================
   Footer social icons — homepage v2 / v3 override.
   Global components.css renders the social links as 36×36 circular
   outlined buttons (border + border-radius: 50%). On the homepage we
   want plain stand-alone icons instead — no circle, no border, no
   hover background. Scoped via body.is-home-v2 so subpages keep the
   global circular treatment.
   ============================================================ */
body.is-home-v2 .site-footer__social-link {
  /* Strip the button chrome from the global rule. */
  width: auto;
  height: auto;
  padding: var(--space-1);            /* small hit-target padding, no visible chrome */
  border: 0;
  border-radius: 0;
  background: transparent;
  /* Replace background-colour transition with a calm opacity transition. */
  color: var(--color-text-on-dark);
  opacity: 0.78;
  transition: opacity var(--motion-quick) var(--ease-standard);
}
body.is-home-v2 .site-footer__social-link:hover,
body.is-home-v2 .site-footer__social-link:focus-visible {
  /* No background fill, no border on hover — just a subtle opacity lift. */
  background: transparent;
  opacity: 1;
}
body.is-home-v2 .site-footer__social-link:focus-visible {
  /* Replace the implicit circular focus with a calm rectangular ring
     that doesn't re-introduce the circle. */
  outline: 1px solid currentColor;
  outline-offset: 4px;
}
body.is-home-v2 .site-footer__social-link svg {
  /* Slight bump from the global 16px so the icons read clearly without
     the surrounding 36×36 button to host them. */
  width: 20px;
  height: 20px;
  fill: currentColor;
}
@media (prefers-reduced-motion: reduce) {
  body.is-home-v2 .site-footer__social-link { transition: none; }
}

/* ============================================================
   CTA closer (kept dark)
   ============================================================ */
/* The previous .cta-closer__inner / __content / __signet wrappers
   that hosted the large brand signet have been removed (markup +
   CSS). The CTA-closer is back to a clean single-column layout —
   eyebrow, headline, lead, and the Contact us button — sized by
   their own BEM rules (.cta-closer__h2, .cta-closer__lead) further
   down in this file. */


.cta-closer {
  /* Top padding unchanged from the previous symmetric value — the
     transition INTO the CTA still wants plenty of breathing room. */
  padding-top: clamp(var(--space-20), 10vw, var(--space-32));
  /* Bottom padding roughly halved — the CTA content was previously
     followed by 80-128px of dark space before the footer's own top
     padding kicked in, so the two stacked paddings produced a gap
     that read as a dead zone. ~50% reduction restores rhythm without
     cramming the button against the footer. */
  padding-bottom: clamp(var(--space-10), 5vw, var(--space-16));
}
.cta-closer__h2 {
  font-size: clamp(48px, 6.2vw, 104px);
  line-height: 0.96;
  letter-spacing: -0.04em;
  text-wrap: balance;
  margin: 0 0 var(--space-8);
  max-width: 22ch;
}
.cta-closer__lead {
  font-size: var(--type-body-lg);
  line-height: 1.55;
  color: var(--color-text-on-dark-muted);
  max-width: 60ch;
  margin: 0 0 var(--space-12);
}
