@import url("premium-footer.css?v=20260906-cormorant-1");
@import url("legal-footer.css?v=20260930-reading-1");

:root {
  /* ------------------------------------------------------------------
     Zuvani brand palette, per the brand guideline. Declared here because
     this file loads first on all 14 pages. Page stylesheets restate the
     same values harmlessly. Before this, --taupe and --warm-stone were
     undefined on Discover, the four legal pages, walking-with-rhinos and
     404, so any rule using them silently failed on those pages.
     ------------------------------------------------------------------ */
  --warm-ivory: #f3ede0;
  --limestone: #e9e2d6;
  --taupe: #b8aa97;
  --warm-stone: #a7947e;
  --espresso: #231a10;
  --bronze: #8c6240;

  /* Easing vocabulary (28 September 2026). Every curve on the site is one
     of these, so an entrance, a hover and the menu all read as one hand:
       --ease-out     things arriving, leaving, or settling after a press:
                      a fast start and a long, soft landing (the footer's
                      reveal curve, now the site's).
       --ease-in-out  things already on screen changing place or state, and
                      the loader: eased at both ends.
     The built-in `ease` stays for colour and opacity hovers, and `linear`
     for constant motion and visibility switches, on purpose. Declared here
     because this file loads first on every page; --nav-overlay-ease and
     --pillar-ease survive only as aliases of --ease-out. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.76, 0, 0.24, 1);

  /* Button hierarchy: three levels, drawn only from the palette above.
     Taupe and Warm Stone are deliberately absent as fills. Ivory on Warm
     Stone measures 2.51:1 and on Taupe 1.95:1, both far under the 4.5:1
     floor for the small uppercase labels these buttons use.
       Tier 1 primary    bronze fill, ivory label          4.57:1  AA
       Tier 2 secondary  bronze outline, bronze label      4.57:1  AA
       Tier 3 quiet      espresso outline, limestone hover 14.67:1 AAA
     Every tier changes colour on hover, so the three stay distinguishable
     from one another in both their resting and their hover states. */
  /* Bronze is reserved for the Enquire button in the header, which stays
     constant on every page. No content button may be bronze, or it would
     collide with Enquire on its own page. Primary is therefore Espresso. */
  --btn-primary-ink: var(--warm-ivory);
  --btn-primary-fill: var(--espresso);
  --btn-primary-ink-hover: var(--warm-ivory);
  --btn-primary-fill-hover: var(--bronze);

  /* Tertiary exists only for pages that already show a primary and a
     secondary, so no two buttons on one page repeat a colour. Discover is
     the only such page today. Espresso on Taupe is 7.53:1. */
  --btn-tertiary-ink: var(--espresso);
  --btn-tertiary-fill: var(--taupe);
  --btn-tertiary-ink-hover: var(--warm-ivory);
  --btn-tertiary-fill-hover: var(--espresso);

  /* Lead: bronze became free when Enquire went unfilled. It exists because a
     colour may only repeat on a page when the destination repeats, and the
     Collective's two buttons post to different addresses. Ivory on bronze is
     4.57:1, and bronze separates from the ivory ground at the same ratio.
     Taupe was rejected here: against Warm Stone it reads as the same greige. */
  --btn-lead-ink: var(--warm-ivory);
  --btn-lead-fill: var(--bronze);

  /* Secondary reads espresso, not bronze, so the two tiers differ by colour
     at rest and not only by fill. Primary stays bronze per the guideline's
     "bronze for buttons"; espresso is the guideline's own primary ink, so
     the pair stays inside the palette. Revert by setting these two back to
     var(--bronze) and rgba(140, 98, 64, 0.68). */
  /* Every button is a pill, and every button carries a palette fill at rest;
     none rely on an outline alone. Tier 2 is Warm Stone: it separates from
     the ivory ground at 2.51:1 and holds espresso text at 5.85:1. Limestone
     was rejected at 1.10:1 against the ground, where the button would have
     read as a flat rectangle. Recolour tier 2 via --btn-secondary-fill. */
  --btn-radius: 999px;
  /* One label-to-arrow distance for the whole site: a single character width,
     so the arrow sits exactly one space after the last letter and scales with
     the label instead of sitting at a fixed pixel value. Pages previously used
     12, 14, 16, 18 and 20px, so the hover read differently on each one. */
  --btn-arrow-gap: 1ch;
  /* Label and arrow slide left together on hover, so the distance between
     them never changes while the button grows to the right. */
  --btn-arrow-shift: -4px;
  /* Photo indicators: the segmented lines under the Home and About pillar
     galleries and the Collective carousel. One value sizes every line's touch
     target; the visible 2px line is 8px narrower than this. Declared here so
     all three pages agree, whereas before Home and About stretched each line
     to 110px inside a 360px block and Collective ran 97px at desktop and 63px
     on a phone. Raise or lower this one number to resize them all. */
  --photo-indicator-hit: 56px;
  --btn-secondary-ink: var(--espresso);
  --btn-secondary-fill: var(--warm-stone);
  --btn-secondary-edge: var(--warm-stone);
  --btn-secondary-ink-hover: var(--warm-ivory);
  --btn-secondary-fill-hover: var(--espresso);

  --btn-quiet-ink: var(--espresso);
  --btn-quiet-edge: rgba(35, 26, 16, 0.28);
  --btn-quiet-fill-hover: var(--limestone);

  --space-section: clamp(72px, 9vw, 120px);
  --space-content: clamp(24px, 6vw, 96px);
  --space-card: clamp(26px, 4vw, 48px);
  --motion-premium: 760ms var(--ease-out);

  /* ------------------------------------------------------------------
     Zuvani typography system. One scale for the whole site; every page
     stylesheet maps its text roles onto these tokens. Everything is set in
     Cormorant Garamond (300/400/500/600 + italics); the functional sizes run
     ~12-15% larger than a sans would, to compensate for its small x-height.
     ------------------------------------------------------------------ */
  --font-display: "Cormorant Garamond", Georgia, serif;
  --font-body: "Cormorant Garamond", Georgia, serif;

  /* Serif display and heading scale */
  --text-display-xl: clamp(4.8rem, 7.4vw, 8.4rem);   /* page hero titles */
  --text-display-lg: clamp(3.8rem, 5.6vw, 6.6rem);   /* page statements, footer closing line */
  --text-heading-1: clamp(3.2rem, 4.6vw, 5.4rem);    /* main section headings */
  --text-heading-2: clamp(2.5rem, 3.6vw, 4.4rem);    /* secondary section and article headings */
  --text-heading-3: clamp(1.7rem, 2.2vw, 2.6rem);    /* card and component headings */
  --text-heading-4: clamp(1.15rem, 1.45vw, 1.45rem); /* small serif sub-headings and values */

  /* Copy scale */
  --text-lead: clamp(1.2rem, 1.55vw, 1.65rem);       /* serif lead statements */
  --text-body-lg: clamp(1.18rem, 1.36vw, 1.34rem);
  --text-body: clamp(1.12rem, 1.24vw, 1.2rem);
  --text-body-sm: clamp(1.04rem, 1.12vw, 1.12rem);

  /* Functional sans scale */
  --text-label: clamp(0.82rem, 0.84vw, 0.9rem);      /* eyebrows, buttons, nav */
  --text-caption: clamp(0.86rem, 0.9vw, 0.94rem);
  --text-legal: clamp(0.84rem, 0.88vw, 0.92rem);

  /* Leading */
  --leading-display: 0.94;
  --leading-heading: 1.02;
  --leading-heading-sm: 1.08;
  --leading-lead: 1.32;
  --leading-body: 1.5;
  --leading-body-sm: 1.45;
  --leading-label: 1;
  --leading-caption: 1.45;
  --leading-legal: 1.55;

  /* Tracking */
  --tracking-display: -0.02em;
  --tracking-heading: -0.015em;
  --tracking-heading-sm: -0.01em;
  --tracking-label: 0.18em;
  --tracking-button: 0.16em;
  --tracking-legal: 0.04em;

  /* Vertical rhythm inside a chapter: eyebrow to heading, heading to copy. */
  --space-eyebrow: 18px;
  --space-heading: clamp(22px, 2.6vh, 30px);

  /* Weights: small serif capitals need 600 to hold their line; the 500 and
     600 Cormorant faces are loaded so nothing is synthesised. */
  --weight-display: 300;
  --weight-heading: 400;
  --weight-body: 400;
  --weight-label: 600;

  /* Text colour on cream. Secondary copy never drops below .74 alpha. */
  --text-primary: var(--espresso, #231a10);
  --text-secondary: rgba(35, 26, 16, 0.74);
  --text-tertiary: rgba(35, 26, 16, 0.66);
  --text-accent: var(--bronze, #8c6240);
}

/* Phones: body copy holds at 16px and supporting copy at 15px. */
@media (max-width: 700px) {
  :root {
    --text-body: 1.14rem;
    --text-body-lg: 1.2rem;
    --text-body-sm: 1.06rem;
    --text-label: 0.86rem;
    --text-legal: 0.88rem;
    --text-caption: 0.9rem;
  }
}

/* Role classes. Components keep their own class names and map onto the
   tokens; these exist for new markup and for one-off text with no home. */
.zuvani-display-xl,
.zuvani-display-lg,
.zuvani-heading-1,
.zuvani-heading-2,
.zuvani-heading-3,
.zuvani-heading-4 {
  margin: 0;
  font-family: var(--font-display);
  text-wrap: balance;
}

.zuvani-display-xl { font-size: var(--text-display-xl); font-weight: var(--weight-display); line-height: 0.88; letter-spacing: var(--tracking-display); }
.zuvani-display-lg { font-size: var(--text-display-lg); font-weight: var(--weight-display); line-height: var(--leading-display); letter-spacing: var(--tracking-display); }
.zuvani-heading-1 { font-size: var(--text-heading-1); font-weight: var(--weight-display); line-height: 0.94; letter-spacing: var(--tracking-heading); }
.zuvani-heading-2 { font-size: var(--text-heading-2); font-weight: var(--weight-display); line-height: var(--leading-heading); letter-spacing: var(--tracking-heading-sm); }
.zuvani-heading-3 { font-size: var(--text-heading-3); font-weight: var(--weight-heading); line-height: 1.05; letter-spacing: var(--tracking-heading-sm); }
.zuvani-heading-4 { font-size: var(--text-heading-4); font-weight: var(--weight-heading); line-height: var(--leading-heading-sm); letter-spacing: 0; }

.zuvani-lead {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lead);
  font-weight: var(--weight-heading);
  line-height: var(--leading-lead);
  letter-spacing: -0.01em;
}

.zuvani-body-copy {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--weight-body);
  line-height: var(--leading-body);
  letter-spacing: 0;
}

.zuvani-copy p { margin: 0; }
.zuvani-copy p + p { margin-top: 1.15em; }

.zuvani-eyebrow {
  margin: 0;
  color: var(--text-accent);
  font-family: var(--font-body);
  font-size: var(--text-label);
  font-weight: var(--weight-label);
  line-height: 1.2;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.zuvani-button {
  font-family: var(--font-body);
  font-size: var(--text-label);
  font-weight: var(--weight-label);
  line-height: var(--leading-label);
  letter-spacing: var(--tracking-button);
  text-transform: uppercase;
}

.zuvani-caption {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: var(--weight-body);
  line-height: var(--leading-caption);
  color: var(--text-secondary);
}

.zuvani-legal {
  font-family: var(--font-body);
  font-size: var(--text-legal);
  font-weight: var(--weight-body);
  line-height: var(--leading-legal);
  letter-spacing: var(--tracking-legal);
  color: var(--text-secondary);
}

html {
  scroll-behavior: smooth;
}

body {
  opacity: 1;
}

html.zuvani-loading {
  overflow: hidden;
}

html.zuvani-loading body {
  height: 100vh;
  overflow: hidden;
}

html.zuvani-loading body > * {
  visibility: hidden;
}

html.zuvani-loading body::before,
html.zuvani-loading body::after {
  position: fixed;
  z-index: 10000;
  content: "";
  visibility: visible;
  pointer-events: auto;
}

html.zuvani-loading body::before {
  inset: 0;
  background: var(--warm-ivory, #f3ede0);
  opacity: 1;
}

html.zuvani-loading body::after {
  top: 50%;
  left: 50%;
  width: clamp(58px, 5vw, 78px);
  aspect-ratio: 1;
  background: url("../assets/images/icons/Shared_Zuvani_Sunmark-optimised.png") center / contain no-repeat;
  opacity: 0;
  transform: translate(-50%, calc(-50% + 8px)) scale(0.96);
  animation: zuvaniLoaderIconIn 420ms var(--ease-in-out) 60ms forwards;
}

html.zuvani-loader-leaving body::before {
  opacity: 0;
  transition: opacity 680ms var(--ease-in-out) 100ms;
}

html.zuvani-loader-leaving body::after {
  opacity: 0;
  transform: translate(-50%, calc(-50% - 4px)) scale(1);
  transition: opacity 380ms ease, transform 480ms var(--ease-in-out);
}

html.zuvani-loader-leaving body > * {
  visibility: visible;
}

@keyframes zuvaniLoaderIconIn {
  to {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

/* One typeface everywhere. Form controls do not inherit fonts by default
   (buttons fall back to Arial), and the embedded Flodesk forms ask for a
   "Cormorant" or "avenir" family that is not loaded here, so both are pinned
   to the site's Cormorant Garamond. */
button,
input,
select,
textarea {
  font-family: inherit;
}

[data-ff-el="root"],
[data-ff-el="root"] * {
  font-family: var(--font-body) !important;
}

/* The embedded forms take the background of whatever section they sit in
   rather than painting their own. Flodesk stores a colour per form, which
   showed as a slightly different block: limestone (#e9e2d6) on the ivory
   newsletter pages, and flat ivory over the enquiry panel's soft tint. */
[data-ff-el="root"],
[data-ff-el="root"] [class*="__container"] {
  background-color: transparent !important;
}

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--bronze, #8c6240);
  outline-offset: 5px;
}

img {
  max-width: 100%;
}

img[data-image-ready="false"] {
  background: #e9e2d6;
}

/* Photographs materialise (27 September 2026): a photo whose pixels arrive
   late is held invisible while it loads, then comes into focus - blur and a
   slight scale fall away as it fades in - rather than popping from the
   placeholder. filter and scale are independent properties, so this composes
   with any transform or reveal the photo's frame carries. Gated to motion
   allowed; with reduced motion the script never marks the image, so it shows
   the moment it loads. */
@media (prefers-reduced-motion: no-preference) {
  img[data-image-arriving] {
    opacity: 0;
  }
  img[data-image-materialise] {
    animation: image-materialise 640ms var(--ease-out) both;
  }
  /* A photo graded at rest keeps its filter untouched and settles by opacity
     and scale alone; see the note in public-refinements.js. */
  img[data-image-materialise][data-image-graded] {
    animation-name: image-materialise-graded;
  }
}
@keyframes image-materialise {
  from { opacity: 0; filter: blur(10px); scale: 1.03; }
  60% { filter: blur(0); }
  to { opacity: 1; filter: blur(0); scale: 1; }
}
@keyframes image-materialise-graded {
  from { opacity: 0; scale: 1.03; }
  to { opacity: 1; scale: 1; }
}

.site-footer-premium {
  color: var(--espresso, #231a10);
  background: var(--warm-ivory, #f3ede0);
}

.site-header {
  isolation: isolate;
}

.site-header .menu-toggle,
.site-header .inquire-link {
  min-height: 44px;
}

.zuvani-nav-link[aria-current="page"] .zuvani-nav-link-text {
  color: var(--bronze, #8c6240);
}

.zuvani-nav-link[aria-current="page"]::after {
  width: 24px;
  height: 1px;
  align-self: center;
  background: currentColor;
  content: "";
}

.public-form-status {
  min-height: 1.4em;
  margin-top: 14px;
  color: var(--text-accent);
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  letter-spacing: 0.05em;
}

.conversion-cta {
  min-height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--btn-arrow-gap, 16px);
  padding: 16px 28px;
  color: var(--btn-primary-ink, #f3ede0);
  background: var(--btn-primary-fill, #231a10);
  border: 1px solid var(--btn-primary-fill, #231a10);
  border-radius: var(--btn-radius, 999px);
  font: var(--weight-label) var(--text-label) / var(--leading-label) var(--font-body);
  letter-spacing: var(--tracking-button);
  text-decoration: none;
  text-transform: uppercase;
  box-shadow: 0 10px 24px rgba(35, 26, 16, 0.1);
  transition: transform 420ms var(--ease-out), box-shadow 420ms ease, background 300ms ease;
}

.conversion-cta::after {
  content: "→";
  transition: transform 320ms ease, opacity 180ms ease,
    max-width 220ms ease, margin-left 220ms ease;
}

/* Every CTA label shares one transition, so the label's left-shift and the
   arrow's arrival move as a single gesture rather than two loose ones. */
.conversion-cta span:first-child,
.outlined-cta span:first-child,
.impact-follow__button span:first-child,
.impact-gallery__cta span:first-child,
.zuvani-process__cta span:first-child,
.home-vision__cta span:first-child,
.experience-booking__button > span:first-child {
  transition: transform 200ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .conversion-cta:hover span:first-child,
  .conversion-cta:focus-visible span:first-child {
    transform: translateX(var(--btn-arrow-shift, -4px));
  }
}

/* ---------------------------------------------------------------------------
   Touch: the desktop animation, driven by the press instead of the pointer.

   Rest here is identical to the hover branch's rest state, so the label sits
   dead centre inside the padding box on a phone exactly as it does on a
   desktop. Pressed is identical to the hover branch's hover state, so the
   button grows to include the arrow and the glyph lands one character space
   after the last letter, the same --btn-arrow-gap on every page.

   An earlier attempt kept the arrow at zero width in BOTH states and let the
   glyph paint outside its own box, to stop the button growing under a finger.
   It failed outright: with max-width:0 there is no box to reveal, so raising
   opacity showed nothing and the arrow never appeared on any phone. Measured
   on the About page under touch emulation, the arrow's rect stayed 0px wide
   while its opacity animated. The arrow needs real width, which means the
   button really does have to grow.

   That growth is the known cost and it is accepted here: pressing grows the
   Impact Fund button 13.8px taller at 300-330px, shifts the document 10px at
   430px, and moves the Discover process button's left edge 11px.

   The pressed state answers to three triggers. :active covers Africa and the
   rhinos page, which have no press handler at all. is-pointer-pressed is
   already toggled on pointerdown by About, Discover and Impact Fund, and
   is-collective-pressed by Collective. Those classes land on pointer-DOWN,
   which iOS Safari applies far more reliably than :active. All three produce
   the same state, so a page matching two of them is harmless.

   The html prefix is load-bearing, not decoration: this file loads FIRST on
   all 14 pages, so a bare class selector here would lose the specificity tie
   to every page stylesheet that sets its own gap.
   --------------------------------------------------------------------------- */
@media (hover: none) {
  /* Zeroing the gap is what re-centres the label. A zero-width arrow still
     leaves the flex gap behind it, and that gap is half the offset. */
  html .conversion-cta,
  html .outlined-cta,
  html .zuvani-process__cta,
  html .home-vision__cta,
  html .impact-follow__button,
  html .impact-gallery__cta,
  html .experience-booking__button {
    gap: 0;
    /* Register the press immediately rather than after the legacy tap delay. */
    touch-action: manipulation;
  }

  /* The rhinos page sets this gap on a two-class selector, so match it. */
  html .experience-booking__actions .experience-booking__button {
    gap: 0;
  }

  html .conversion-cta::after,
  html .outlined-cta span:last-child,
  html .zuvani-process__cta-arrow,
  html .home-vision__cta span[aria-hidden="true"],
  html .impact-follow__button span:last-child,
  html .impact-gallery__cta span:last-child,
  html .experience-booking__button > span:last-child {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-width 160ms ease, margin-left 160ms ease,
      opacity 140ms ease, transform 160ms ease;
  }

  html .conversion-cta:active::after,
  html .conversion-cta.is-pointer-pressed::after,
  html .outlined-cta:active span:last-child,
  html .outlined-cta.is-collective-pressed span:last-child,
  html .zuvani-process__cta:active .zuvani-process__cta-arrow,
  html .zuvani-process__cta.is-pointer-pressed .zuvani-process__cta-arrow,
  html .home-vision__cta:active span[aria-hidden="true"],
  html .home-vision__cta.is-pointer-pressed span[aria-hidden="true"],
  html .impact-follow__button:active span:last-child,
  html .impact-follow__button.is-pointer-pressed span:last-child,
  html .impact-gallery__cta:active span:last-child,
  html .impact-gallery__cta.is-pointer-pressed span:last-child,
  html .experience-booking__button:active > span:last-child,
  html .experience-booking__button.is-pointer-pressed > span:last-child {
    max-width: 2.5em;
    margin-left: var(--btn-arrow-gap, 1ch);
    opacity: 1;
    transform: translateX(var(--btn-arrow-shift, -4px));
  }

  /* The label travels with the arrow, so the gap between them never changes. */
  html .conversion-cta:active span:first-child,
  html .conversion-cta.is-pointer-pressed span:first-child,
  html .outlined-cta:active span:first-child,
  html .outlined-cta.is-collective-pressed span:first-child,
  html .zuvani-process__cta:active span:first-child,
  html .zuvani-process__cta.is-pointer-pressed span:first-child,
  html .home-vision__cta:active span:first-child,
  html .home-vision__cta.is-pointer-pressed span:first-child,
  html .impact-follow__button:active span:first-child,
  html .impact-follow__button.is-pointer-pressed span:first-child,
  html .impact-gallery__cta:active span:first-child,
  html .impact-gallery__cta.is-pointer-pressed span:first-child,
  html .experience-booking__button:active > span:first-child,
  html .experience-booking__button.is-pointer-pressed > span:first-child {
    transform: translateX(var(--btn-arrow-shift, -4px));
  }
}

  to { opacity: 1; }
}

/* Press feedback for every tappable thing (27 September 2026). On pointer-down
   public-refinements.js adds .is-pressed; the control gives slightly under the
   finger and springs back on release, the way a physical control does. It uses
   the independent `scale` property, so it composes with any transform a card
   or arrow already carries rather than replacing it, and only opacity and
   scale move, so nothing re-lays out. Cards and rows give less than small
   controls: a whole tile scaling by 3% would read as flinching. Keyboard
   activation adds no class and so gets no motion. Reduced motion keeps the
   dim, which aids comprehension, and drops the movement. */
.is-pressed {
  --press-scale: 0.97;
  scale: var(--press-scale);
  transition: scale 120ms var(--ease-out), opacity 120ms ease;
}
:is(.article-card, .featured-story-card, .service-link, .zuvani-pillar-card__link,
  .footer-connect-link, .home-faq__question, .zuvani-nav-link,
  .designed-plan__toggle, .experience-rating, .back-link, .hero-link).is-pressed {
  --press-scale: 0.985;
}
.zuvani-nav-link.is-pressed,
.home-faq__question.is-pressed {
  transform-origin: left center;
}
/* The spring back on release: once .is-pressed is removed the element is in
   its resting state, and this class, added for 200ms by the script, carries
   the transition from the pressed scale back to 1 on the same curve. Putting
   the transition on a temporary class rather than on every link and button
   leaves their own hover and colour transitions exactly as they are. */
.is-released {
  transition: scale 200ms var(--ease-out), opacity 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .is-pressed { scale: none; opacity: 0.85; transition: opacity 80ms linear; }
  .is-released { transition: opacity 120ms linear; }
}

/* The arrow waits for the pointer, then fades and slides in. Gated to fine
   pointers because a touch device has no hover to reveal it, so there the
   arrow stays visible. The glyph keeps its space in both states, so the
   button never changes width and nothing around it reflows. */
@media (hover: hover) and (pointer: fine) {
  /* The flex gap is zeroed too. A zero-width arrow would still leave the
     18px gap behind it, which is the gap this is meant to remove. */
  .conversion-cta { gap: 0; }
  .conversion-cta::after {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
  }
  .conversion-cta:hover::after,
  .conversion-cta:focus-visible::after {
    max-width: 2.5em;
    margin-left: 18px;
    opacity: 1;
  }
}

/* The arrow carries the hover now, so the fill holds still. A slight dim plus
   the existing lift is the whole response. Swapping the fill colour on top of
   an animating arrow read as two competing signals at once. */
.conversion-cta:hover {
  opacity: .9;
  box-shadow: 0 18px 34px rgba(35, 26, 16, 0.16);
  transform: translateY(-5px);
}

.conversion-cta:hover::after {
  transform: translateX(4px);
}

.conversion-point {
  display: grid;
  justify-items: center;
  gap: 18px;
  padding: clamp(54px, 7vw, 88px) var(--space-content);
  text-align: center;
}

.conversion-point--compact {
  padding-block: clamp(34px, 4vw, 54px);
}

.conversion-point__eyebrow {
  margin: 0;
  color: var(--text-accent);
  font-family: var(--font-body);
  font-size: var(--text-label);
  font-weight: var(--weight-label);
  line-height: 1.2;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.conversion-point h2 {
  max-width: 760px;
  margin: 0;
  font: var(--weight-display) var(--text-heading-1) / 0.94 var(--font-display);
  letter-spacing: var(--tracking-heading);
  text-wrap: balance;
}

.conversion-point p:not(.conversion-point__eyebrow) {
  max-width: 590px;
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-body);
  line-height: var(--leading-body);
}

.footer-conversation {
  width: min(100%, 1440px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  margin: 0 auto clamp(48px, 7vh, 76px);
  padding-bottom: clamp(42px, 6vh, 64px);
  border-bottom: 1px solid var(--line, rgba(35, 26, 16, 0.14));
}

.footer-conversation p {
  max-width: 620px;
  margin: 0;
  font: var(--weight-display) var(--text-heading-2) / 0.98 var(--font-display);
  letter-spacing: var(--tracking-heading-sm);
}

.hero .conversion-cta {
  color: var(--espresso, #231a10);
  background: var(--warm-ivory, #f3ede0);
  border-color: var(--warm-ivory, #f3ede0);
}

/* The hero variant holds its ivory fill too; only the dim differs. */
.hero .conversion-cta:hover {
  opacity: .9;
}

.coming-soon-state {
  display: grid;
  justify-items: center;
  gap: 14px;
  max-width: 760px;
  margin: 0 auto;
  padding: var(--space-section) var(--space-content);
  text-align: center;
}

.coming-soon-state strong {
  color: var(--text-accent);
  font-size: var(--text-label);
  font-weight: var(--weight-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.reading-progress {
  position: fixed;
  inset: 0 0 auto;
  z-index: 500;
  height: 2px;
  pointer-events: none;
}

.reading-progress span {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--bronze, #8c6240);
  transform: scaleX(0);
  transform-origin: left;
}

/* Honour the OS preference without changing the normal motion experience. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  body {
    opacity: 1;
    transition: none;
  }

  html.zuvani-loading body::after {
    transform: translate(-50%, -50%);
    animation: zuvaniLoaderFadeIn 160ms ease forwards;
  }

  html.zuvani-loader-leaving body::before,
  html.zuvani-loader-leaving body::after {
    transform: translate(-50%, -50%);
    transition: opacity 180ms ease;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

/* Reduced motion, site-wide policy (27 September 2026). Reduced motion means
   gentler, not none: movement goes, but a state change still needs a moment
   to register or the interface reads as broken. Pages remove their slides,
   rises and parallax individually, often with !important; this one block,
   loaded on every page and placed after the blanket 0.01ms reset above so
   its durations win, restores a short opacity cross-fade for the four
   state changes that matter - the menu, the sign-up dialogs, sections and
   the footer arriving - on top of those removals. Only opacity ever moves
   here. The !important is deliberate: this is the policy the page-level
   resets defer to, and it must outrank them wherever they load. */
@media (prefers-reduced-motion: reduce) {
  /* Menu: a 200ms fade in place of the slide. The page-level "instant" rules
     that zero every transition on keyboard use are kept: keyboard activation
     stays instant, pointer use gets the fade. */
  html:not(.nav-motion-instant) .zuvani-nav-overlay {
    transform: none !important;
    transition: opacity 200ms ease, visibility 0s linear 200ms !important;
  }
  html:not(.nav-motion-instant) .zuvani-nav-overlay.is-visible {
    transition: opacity 200ms ease, visibility 0s linear 0s !important;
  }
  html:not(.nav-motion-instant) .zuvani-nav-backdrop {
    transition: opacity 200ms ease !important;
  }
  /* The blanket reset above leaves every element transitioning everything
     over 0.01ms, so as the panel turns visible each descendant runs a
     one-frame transition of the visibility it inherits, and for that frame
     it is still hidden: the close button refused focus (a discrete property
     holds its old value until the transition ends). Nothing inside the panel
     has anything left to transition under reduced motion. */
  html:not(.nav-motion-instant) .zuvani-nav-overlay * {
    transition-property: none !important;
  }

  /* Sign-up and confirmation dialogs: fade in over 200ms, no scale or rise. */
  html dialog[open]:is(.collective-signup-dialog, .impact-signup-dialog, .enquiry-confirmation,
    .collective-signup-confirmation, .impact-signup-confirmation, .blog-signup-confirmation) {
    transform: none !important;
    scale: none !important;
    animation: none !important;
    transition: opacity 200ms ease !important;
    @starting-style { opacity: 0; }
  }
  html dialog:is(.collective-signup-dialog, .impact-signup-dialog, .enquiry-confirmation,
    .collective-signup-confirmation, .impact-signup-confirmation, .blog-signup-confirmation)::backdrop {
    transition: opacity 200ms ease !important;
    @starting-style { opacity: 0; }
  }

  /* Sections and photographs arriving: the scripts skip the rise and show
     content at once under reduced motion. A 240ms fade on the reveal
     classes they still set turns that hard cut into an arrival. */
  html [data-reveal].is-revealed:not(.is-revealed-instant) > :not(dialog),
  html [data-reveal="self"].is-revealed:not(.is-revealed-instant),
  html .reveal-section.is-revealed:not(.is-revealed-instant) > :not(dialog),
  html .footer-legal-v2 .legal-footer__reveal.is-visible {
    animation: reduced-motion-arrive 240ms ease both !important;
    transform: none !important;
  }
}
@keyframes reduced-motion-arrive {
  from { opacity: 0; }
}

@keyframes zuvaniLoaderFadeIn {
  to { opacity: 1; }
}
