/* ============================================================
   ZOXA.AI — SITE STYLES · Ember v3.0
   Every value comes from tokens.css. No raw hex below this line
   (SVG data-URIs excepted). If a component needs a colour that
   is not a token, it is the wrong colour.
   ============================================================ */

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body.zx {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

/* Resets use :where() so they carry ZERO specificity — a component class
   like .zx-hero-sub must always win over the reset. Without this, every
   margin token on a p/h element is silently discarded. */
.zx :where(h1, h2, h3, h4) {
  font-family: var(--font-display);
  margin: 0;
  text-wrap: balance;
}
.zx h1 { font-size: var(--fs-h1); font-weight: var(--fw-heavy); line-height: var(--lh-display); letter-spacing: var(--ls-display); }
.zx h2 { font-size: var(--fs-h2); font-weight: var(--fw-bold); line-height: var(--lh-heading); letter-spacing: var(--ls-heading); }
.zx h3 { font-size: var(--fs-h3); font-weight: var(--fw-bold); line-height: var(--lh-sub); letter-spacing: var(--ls-heading); }
.zx h4 { font-size: var(--fs-h4); font-weight: var(--fw-bold); line-height: var(--lh-sub); letter-spacing: var(--ls-heading); }
.zx :where(p) { margin: 0; }
.zx :where(img, svg) { display: block; max-width: 100%; }
.zx :where(a) { color: inherit; }
.zx :where(button) { font-family: inherit; }

/* Focus system — brand ember ring, keyboard only, never browser default */
.zx :focus { outline: none; }
.zx :focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
.zx .zx-btn:focus-visible,
.zx .zx-voice-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: var(--r-button);
  box-shadow: 0 0 0 5px var(--primary-soft);
}

::selection { background: var(--primary-soft); color: var(--text); }

/* Theme boundaries re-anchor inherited colour — without this, elements inside
   a theme island inherit the parent theme's already-resolved colour. */
.zx [data-band] {
  color: var(--text);
}

/* ---- section bands ----
   Each section paints its own canvas from its own tokens. A band's
   light/dark identity is fixed in the markup, so nothing depends on
   scroll position or runtime state. */
.zx [data-band] {
  background-color: var(--bg);
  position: relative;
}

/* ---- scroll-linked theme surface ----
   One fixed sheet behind the whole page. assets/js/theme-scroll.js owns
   its colour and blends it as you cross between a light section and a
   dark one, so the page reads as turning over rather than as blocks of
   colour sliding past each other.

   The fade is HERE, not in the script, and that is deliberate. Tying
   the colour to scroll position parks you in the mid-greys if you stop
   mid-crossing, and in that band neither bone type nor muted ink
   reaches 4.5:1 — measured worst case 3.20:1. A timed transition
   passes through the same midpoint but always resolves out of it, so
   every state you can actually rest in is a correct pairing. The full
   reasoning is in assets/js/theme-scroll.js. */
.zx-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-color: var(--bg);
  pointer-events: none;
  /* --ease-in-out, not --ease-out. Its soft ends are what make a long
     fade feel like settling rather than sliding, and its quick middle
     spends less time in the mid-greys where neither text colour reaches
     4.5:1. --ease-out front-loads the change, which is the opposite of
     both of those. */
  transition: background-color var(--dur-theme, 760ms) var(--ease-in-out);
}

/* Type turns over with the surface. Same duration and same curve, so
   the words and the background land together instead of the text
   arriving after the page has already changed underneath it. */
.zx-tscroll .zx [data-band] {
  transition: color var(--dur-theme, 760ms) var(--ease-in-out);
}

@media (prefers-reduced-motion: reduce) {
  .zx-backdrop,
  .zx-tscroll .zx [data-band] { transition: none; }
}

/* The handover. .zx-tscroll is added by the script, so this only takes
   effect once the script is running — with JS off every section keeps
   painting itself and the page looks exactly as it did before.

   The bands still carry their light/dark TOKENS, which is what keeps
   their text, borders and cards correct. Only the canvas is given up. */
.zx-tscroll .zx [data-band] { background-color: transparent; }

/* Islands opt back in: a marked element nested inside another one is a
   card or a panel, not a page band, and must keep its own fill. */
.zx-tscroll .zx [data-band] [data-band] { background-color: var(--bg); }

/* .zx-strip re-declares its own background further down this file at
   the same specificity, so it wins on order and keeps painting. It is
   light and currently sits between light sections, which is why that
   goes unnoticed — but it would show as a hard white band the moment a
   dark section moved next to it. Opt it in explicitly. */
.zx-tscroll .zx [data-band].zx-strip { background-color: transparent; }

/* ---------- utilities ---------- */
.zx-container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.zx-container--wide { max-width: var(--container-wide); }
.zx-container--narrow { max-width: var(--container-narrow); }

.zx-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}
.zx-eyebrow::before {
  content: "";
  width: var(--s2);
  height: var(--s2);
  border-radius: var(--r-2xs);
  background: var(--primary);
  flex: none;
}

.zx-section { padding-block: var(--section-y); }
.zx-section--sm { padding-block: var(--section-y-sm); }

.zx-section-head { max-width: 640px; margin-bottom: var(--s7); }
.zx-section-head--center { margin-inline: auto; text-align: center; }
.zx-section-head--center .zx-eyebrow { justify-content: center; }
.zx-section-head .zx-eyebrow { margin-bottom: var(--s4); }
.zx-section-head p {
  margin-top: var(--s4);
  color: var(--muted);
  font-size: var(--fs-lead);
  line-height: var(--lh-small);
}

.zx-mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-variant-numeric: tabular-nums;
}


/* ---------- buttons ---------- */
.zx-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: var(--s3) var(--s5);
  min-height: 48px;
  border-radius: var(--r-button);
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: var(--fs-button);
  font-weight: var(--fw-medium);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
.zx-btn[disabled], .zx-btn[aria-disabled="true"] {
  opacity: .5;
  pointer-events: none;
}

.zx-btn--primary {
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow-sm);
}
.zx-btn--primary:hover { background: var(--primary-hover); box-shadow: var(--shadow-md); }

.zx-btn--secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--border-strong);
}
.zx-btn--secondary:hover { border-color: var(--text); background: var(--primary-soft); }

.zx-btn--ghost {
  background: transparent;
  color: var(--text);
  padding-inline: var(--s4);
}
.zx-btn--ghost:hover { color: var(--primary); }

.zx-btn--lg { padding: var(--s4) var(--s6); min-height: 56px; font-size: var(--fs-lead); }

.zx-btn .zx-btn-icon { width: 18px; height: 18px; flex: none; }

/* ---------- radial hover fill ----------
   One behaviour, reused anywhere. The disc grows from wherever the pointer
   entered (--mx / --my, set in main.js) and falls back to the centre for
   keyboard focus. --invert is the orange-at-rest, white-on-hover pairing.
   ---------------------------------------------------------------- */
.zx-fillfx { position: relative; isolation: isolate; overflow: hidden; }
.zx-fillfx > .zx-fill {
  position: absolute;
  z-index: -1;
  left: var(--mx, 50%);
  top: var(--my, 50%);
  width: 280%;
  aspect-ratio: 1;
  border-radius: var(--r-full);
  background: var(--bone);
  transform: translate(-50%, -50%) scale(0);
  transition: transform var(--dur-fill) var(--ease-fill);
  pointer-events: none;
}
.zx-fillfx:hover > .zx-fill,
.zx-fillfx:focus-visible > .zx-fill,
.zx-card:hover .zx-fillfx > .zx-fill,
.zx-card:focus-within .zx-fillfx > .zx-fill { transform: translate(-50%, -50%) scale(1); }

.zx-fillfx--invert {
  background: linear-gradient(180deg, var(--ember-500), var(--ember-700));
  color: var(--bone);
  /* border must be 0, not transparent: overflow:hidden clips the fill at the
     padding edge, so a 1px border leaves a ring of the orange underneath */
  border: 0;
  transition: color var(--dur-base) var(--ease-out);
}
.zx-fillfx--invert:hover,
.zx-fillfx--invert:focus-visible,
.zx-card:hover .zx-fillfx--invert,
.zx-card:focus-within .zx-fillfx--invert { color: var(--ink); background: linear-gradient(180deg, var(--ember-500), var(--ember-700)); }

@media (prefers-reduced-motion: reduce) {
  .zx-fillfx > .zx-fill { transition: none; }
}

/* ---------- CTA button ----------
   Rest: bone pill, ink label, ember circle badge holding a → arrow.
   Hover: an ember disc expands from wherever the pointer entered,
   the badge dissolves into it, and the arrow swings up to ↗.
   Pointer position arrives as --mx / --my from main.js. */
.zx-btn--fx {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  gap: var(--s5);
  padding: var(--s2) var(--s2) var(--s2) var(--s6);
  min-height: 60px;
  border-radius: var(--r-button);
  background: var(--bone);
  color: var(--ink);
  font-weight: var(--fw-medium);
}

.zx-btn-fill {
  position: absolute;
  z-index: -1;
  right: 22px; /* Center of the badge */
  top: 50%;
  left: auto;
  width: 250%;
  aspect-ratio: 1;
  border-radius: var(--r-full);
  background: var(--ember-500);
  transform: translate(50%, -50%) scale(0);
  transition: transform var(--dur-fill) var(--ease-fill);
  pointer-events: none;
}
.zx-btn--fx:hover .zx-btn-fill,
.zx-btn--fx:focus-visible .zx-btn-fill { transform: translate(50%, -50%) scale(1); }

.zx-btn-label { position: relative; white-space: nowrap; }

.zx-btn-badge {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-icon);
  background: var(--ember-500);
  color: var(--bone);
  flex: none;
  transition: background-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.zx-btn-badge svg {
  width: 18px;
  height: 18px;
  transition: transform var(--dur-slow) var(--ease-spring);
}
/* badge dissolves into the incoming fill, arrow swings to ↗ */
.zx-btn--fx:hover .zx-btn-badge { background: transparent; color: var(--bone); }
.zx-btn--fx:hover .zx-btn-badge svg { transform: rotate(-45deg); }
.zx-btn--fx:hover .zx-btn-label { color: var(--bone); }

.zx-btn--fx.zx-btn--lg { min-height: 64px; font-size: var(--fs-lead); }
.zx-btn--fx.zx-btn--sm {
  min-height: 44px;
  padding: 4px 4px 4px var(--s5);
  gap: var(--s4);
  font-size: var(--fs-button);
}
.zx-btn--fx.zx-btn--sm .zx-btn-badge { width: 36px; height: 36px; }
.zx-btn--fx.zx-btn--sm .zx-btn-badge svg { width: 15px; height: 15px; }

/* This block used to read:

     [data-band="invert"] .zx-btn--fx,
     @media (prefers-reduced-motion: reduce) { ... }

   — a selector list left hanging on a comma, with the @media directly
   after it. CSS parses that as ONE invalid selector and throws away the
   block that follows, so the reduced-motion rules below were dead and
   the invert rule never existed at all. Both are restored here. */

@media (prefers-reduced-motion: reduce) {
  /* Snap to the filled state instead of growing into it. Centre the disc
     so it covers the pill regardless of where the pointer entered. */
  .zx-btn-fill { transition: none; }
  .zx-btn--fx:hover .zx-btn-fill,
  .zx-btn--fx:focus-visible .zx-btn-fill { transform: translate(50%, -50%) scale(1); }
  .zx-btn-badge svg { transition: none; }
}

/* ---------- navbar ---------- */
.zx-nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-nav);
  height: var(--nav-height);
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
  border-bottom: 1px solid transparent;
}
/* ---- Frosted glass bar ----
   A constant light frost that never re-tints with the band underneath.
   Heavy blur + saturation lifts whatever is behind it into a silver sheen,
   and the contents stay ink so contrast is fixed rather than conditional. */
.zx-nav .zx-nav-in {
  /* relative, not absolute — relative does not create a containing block for
     fixed descendants, so the drawer inside still measures off the viewport */
  position: relative;
  max-width: var(--container-nav);
  width: calc(100% - var(--gutter) * 2);
  margin-inline: auto;
  padding: 0 var(--s5) 0 var(--s6);
  height: 100%;
  margin-top: var(--s3);
  border-radius: var(--r-nav);
  display: flex;
  align-items: center;
  gap: var(--s4);
  color: var(--ink);
  border: 1px solid rgba(255,255,255,.6);
  box-shadow:
    0 10px 34px rgba(15,10,8,.16),
    0 1px 0 rgba(15,10,8,.06),
    inset 0 1px 0 rgba(255,255,255,.85);
  transition: box-shadow var(--dur-base) var(--ease-out);
  /* Stacking context so the frost below can sit at z-index -1 without
     falling behind the page. isolation is safe here: it creates a stacking
     context but NOT a containing block, which is the whole point. */
  isolation: isolate;
}

/* ---- the frost lives on a pseudo-element, deliberately ----
   backdrop-filter on .zx-nav-in itself made it the containing block for its
   position:fixed descendants, which broke the mobile drawer nested inside it
   (see the note in components/header.njk). A pseudo-element has no element
   children, so the filter can live here and trap nothing.

   Do not move these two declarations back up to .zx-nav-in. */
/* ---- ONE sheet of glass, shared by the pill and the mega panel ----
   These are two separate elements that have to read as a single surface
   when the menu is open, so the surface is defined once here and both
   consume it. Do not give either its own background or blur.

   FLAT, not a gradient. Each element ran its own 180deg gradient across
   its own height, so the pill ended on its darkest stop exactly where the
   panel restarted on its lightest — measured, that was a 64-level jump in
   two pixels right across the join. Two boxes of different heights can
   never share a per-element gradient seamlessly. A flat fill is identical
   at any height, which is the whole point.

   The lit top edge is still there: it comes from the inset box-shadow on
   .zx-nav-in, which sits on the pill's own edge rather than being a tone
   ramp that has to line up with anything. */
.zx-nav {
  /* ACTUAL GLASS: 55% fill, so 45% of whatever is behind comes through and
     tints it. Over the dark hero the bar goes grey; over a light band it
     goes white. That colour shift IS the effect — a 94% fill, which is what
     this was, is a cream panel with a blur behind it and reads as neither.

     It could only go this transparent once the pane became a single
     element. While the nav row and the dropdown had a backdrop-filter each,
     their mismatch showed through at (1 - alpha), so every step toward
     transparency made the join worse. One filter decoupled the two.

     The floor is text contrast, not taste. Nav links are solid ink over
     this, which measures 6.9:1 on the darkest backdrop on the site. At .48
     it is 5.8, at .42 it is 5.0 — still passing, but with the darkest
     possible hero frame there is no margin left. .55 keeps one. */
  --glass: rgba(252, 250, 247, .55);
  --glass-blur: blur(36px) saturate(180%);
  --glass-edge: rgba(255, 255, 255, .5);
}
.zx-nav-in::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
          backdrop-filter: var(--glass-blur);
}
.zx-nav.is-scrolled .zx-nav-in { box-shadow: 0 10px 40px rgba(15,10,8,.2), inset 0 1px 0 rgba(255,255,255,.55); }

/* contents are always ink on the frost — never theme-dependent.
   Solid ink, not 72%. Drawing the links translucent composited them
   against the plate and threw away contrast at exactly the moment the
   plate went transparent — it cost about two steps of glass. Solid ink
   is what pays for the 55% fill above. */
.zx-nav .zx-nav-links > li > a:not(.zx-btn) { color: var(--ink); }
.zx-nav .zx-nav-links > li > a:not(.zx-btn):hover { color: var(--ink); background: rgba(15,10,8,.06); }
.zx-nav .zx-btn--ghost { color: var(--ink); }
.zx-nav .zx-btn--ghost:hover { color: var(--ember-600); }
.zx-nav .zx-nav-toggle span { background: var(--ink); }

/* ---- nav over a dark band ----
   The pill is fixed, so once the page turns over behind it, ink text on
   55%-transparent glass drops to roughly 1.5:1 — unreadable. The script
   puts .zx-on-dark on <body> based on the colour behind the NAV
   specifically, not the colour at the reading line, so the flip happens
   as the dark section reaches the top of the screen rather than while
   it is still halfway down.

   Colours only. Nothing here changes size or position, so the pill does
   not shift as the state flips. */
.zx.zx-on-dark .zx-nav {
  --glass: rgba(24, 17, 14, .55);
  --glass-edge: rgba(255, 255, 255, .16);
}
.zx.zx-on-dark .zx-nav .zx-nav-in {
  color: var(--bone);
  border-color: rgba(255, 255, 255, .16);
  box-shadow:
    0 10px 34px rgba(0, 0, 0, .34),
    0 1px 0 rgba(255, 255, 255, .04),
    inset 0 1px 0 rgba(255, 255, 255, .14);
}
.zx.zx-on-dark .zx-nav .zx-nav-links > li > a:not(.zx-btn) { color: var(--bone); }
.zx.zx-on-dark .zx-nav .zx-nav-links > li > a:not(.zx-btn):hover {
  color: var(--bone);
  background: rgba(255, 255, 255, .10);
}
.zx.zx-on-dark .zx-nav .zx-btn--ghost { color: var(--bone); }
.zx.zx-on-dark .zx-nav .zx-btn--ghost:hover { color: var(--ember-300); }
.zx.zx-on-dark .zx-nav .zx-btn--secondary { color: var(--bone); border-color: rgba(255,255,255,.34); }
.zx.zx-on-dark .zx-nav .zx-nav-toggle span { background: var(--bone); }
.zx.zx-on-dark .zx-nav .zx-nav-trigger .zx-caret { color: var(--bone); }

/* Both logo files ship in the same grid cell (see .zx-nav-logo) and are
   swapped with opacity. Changing one <img>'s src instead would blank the
   logo for a frame on every flip while the new file decodes. */
.zx-logo--dark { opacity: 0; }
.zx.zx-on-dark .zx-logo--light { opacity: 0; }
.zx.zx-on-dark .zx-logo--dark { opacity: 1; }

.zx-nav-logo {
  display: grid;
  align-items: center;
  flex: none;
}
.zx-logo { height: 30px; width: auto; display: block; }
.zx-nav-logo .zx-logo { grid-area: 1 / 1; }

.zx-nav-links {
  display: flex;
  align-items: center;
  gap: var(--s1);
  list-style: none;
  margin: 0 0 0 auto;
  padding: 0;
}
.zx-nav-cta .zx-btn--ghost { padding-inline: var(--s3); }
.zx-nav-links > li > a:not(.zx-btn) {
  display: inline-block;
  white-space: nowrap;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-chip);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  color: var(--muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.zx-nav-links > li > a:not(.zx-btn):hover { color: var(--text); background: var(--primary-soft); }

.zx-nav-cta { display: flex; align-items: center; gap: var(--s3); flex: none; }
.zx-nav-links-cta { display: none; } /* CTAs live in .zx-nav-cta on desktop, in menu on mobile */
.zx-nav-cta .zx-btn { min-height: 40px; padding-block: var(--s2); }

.zx-nav-toggle {
  display: none;
  margin-left: auto;
  width: 44px;
  height: 44px;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: var(--r-sm);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
.zx-nav-toggle span {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: var(--r-full);
  background: var(--text);
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
.zx-nav-toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.zx-nav-toggle.is-open span:nth-child(2) { opacity: 0; }
.zx-nav-toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- nav mega-menu (glass, connected) ---------- */
.zx-nav-trigger .zx-caret {
  display: inline-block;   /* the global svg reset is display:block — keep the caret beside the text */
  width: 13px; height: 13px;
  margin-left: 4px;
  vertical-align: -2px;
  transition: transform var(--dur-fast) var(--ease-out);
}
.zx-has-mega:hover .zx-nav-trigger .zx-caret,
.zx-has-mega:focus-within .zx-nav-trigger .zx-caret { transform: rotate(180deg); }

/* the panel is centred under the whole nav pill and resolves against
   .zx-nav-in (position:relative). the li stays static on purpose. */
.zx-mega {
  position: absolute;
  /* Butt the panel against the pill's bottom instead of tucking it 17px
     underneath. Both surfaces are translucent, so any overlap stacks two
     of them and reads denser than either alone — that was a 14-level step
     along the pill's bottom edge. Tiling them means the join carries one
     thickness of glass the whole way down.
     No hover bridge is needed at 0 gap: the pointer leaves the trigger and
     is already inside the panel. */
  /* -1px, not a flush 0. Sub-pixel rounding at fractional zoom can leave
     the panel's top edge a fraction below the pill's bottom, and any gap
     there shows the backdrop straight through. One pixel of overlap closes
     it. Only one: the previous 17px overlap stacked two translucent
     surfaces and read denser than either, which was its own visible seam. */
  top: calc(100% - 1px);
  left: 50%;
  width: 100%;                       /* match the nav pill's full width */
  padding-top: 0;
  transform: translateX(-50%);
  visibility: hidden;
  pointer-events: none;
  /* Negative, so the glass sheet this carries paints BEHIND the nav row's
     logo and links while still sitting above .zx-nav-in::before. The cards
     inside are entirely below the nav row, so nothing of the menu is
     obscured by being back here. */
  z-index: -1;
  /* No transition, on purpose, in either direction. Every timed version of
     this — fade, slide, staggered content, fast-close-slow-open — still
     read as "taking a moment" to whoever's actual intent was to leave.
     Show/hide now tracks the pointer 1:1: the instant :hover or
     :focus-within stops matching, visibility and pointer-events flip on
     the same frame. */
}
.zx-has-mega:hover .zx-mega,
.zx-has-mega:focus-within .zx-mega {
  visibility: visible;
  pointer-events: auto;
}

/* Cards and glass appear together now — no separate entrance fade. The
   opacity split used to exist so the glass (a sibling ::before on
   .zx-mega__panel, not an ancestor) never inherited a fade meant for the
   cards; that reasoning still holds, it's just that neither one animates
   any more, so it's a plain on/off rather than two synced fades. */
.zx-mega__panel > .zx-mega__grid,
.zx-mega__panel > .zx-mega__foot {
  opacity: 0;
}
.zx-has-mega:hover .zx-mega__panel > .zx-mega__grid,
.zx-has-mega:hover .zx-mega__panel > .zx-mega__foot,
.zx-has-mega:focus-within .zx-mega__panel > .zx-mega__grid,
.zx-has-mega:focus-within .zx-mega__panel > .zx-mega__foot {
  opacity: 1;
}

/* frosted glass, same family as the nav bar */
/* ════════════════════════════════════════════════════════════
   ONE SHEET OF GLASS

   The panel itself is now just a content box — no background, no blur, no
   border, no shadow. All of that lives on a SINGLE pseudo-element below,
   which starts one nav-height above the panel and therefore spans the nav
   row and the dropdown as one continuous pane.

   This is structural, not cosmetic. Two elements means two
   backdrop-filters: each blurs only what sits behind its own box, and each
   clamps its sampling at its own edges. Where those two clamped edges meet
   you get a discontinuity that no amount of colour matching removes —
   matching the fills got the step down from 64 levels to ~5, and that
   residue was the two filters, not the fills. One element, one filter, no
   join to hide.

   Do NOT give .zx-mega__panel a background or a backdrop-filter again.
   ════════════════════════════════════════════════════════════ */
.zx-mega__panel {
  position: relative;
  padding: var(--s4);
  color: var(--ink);
}
.zx-mega__panel::before {
  content: "";
  position: absolute;
  /* up over the nav row, and out by a pixel each side to swallow the
     pill's border ring rather than leaving it cutting across the pane */
  top: calc(-1 * var(--nav-height) - 1px);
  left: -1px;
  right: -1px;
  bottom: 0;
  z-index: -1;
  border-radius: var(--r-nav);
  border: 1px solid var(--glass-edge);
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
          backdrop-filter: var(--glass-blur);
  /* Negative spread is load-bearing: a plain `0 28px 60px` spreads blur/2
     in EVERY direction, so it casts back up over the nav row and darkens
     it. Pulling the box in keeps the shadow's top edge below the pane. */
  box-shadow: 0 28px 60px -14px rgba(15, 10, 8, .30);
}

/* While the menu is open the pill and panel are one rounded sheet, so the
   pill drops its bottom corners and its bottom edge. Without this the
   pill's rounded corners curve away underneath the panel's square top
   corners and leave two small notches at the join. */
/* While the menu is open the pane above provides the glass for the nav row
   too, so the pill hides its own. Two layers here would read as a denser
   band across exactly the width of the nav row.

   The pill's border also goes fully transparent rather than zero-width:
   with the pane sitting behind it, a transparent ring reveals GLASS, not
   the backdrop. (When the pane is absent, transparent here would show the
   ember hero straight through as an orange line — that is why this rule is
   tied to the menu being open.) */
.zx-nav-in:has(.zx-has-mega:hover)::before,
.zx-nav-in:has(.zx-has-mega:focus-within)::before { opacity: 0; }

.zx-nav-in:has(.zx-has-mega:hover),
.zx-nav-in:has(.zx-has-mega:focus-within) {
  border-color: transparent;
  box-shadow: none;
}
.zx-mega__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--s3); }

.zx-mega-card {
  display: grid;
  gap: var(--s2);
  padding: var(--s4);
  border-radius: var(--r-lg);
  text-decoration: none;
  color: var(--ink);
  /* .74, up from .55. These cards sit ON the glass, so when the pane went
     from 94% to 55% their own backdrop got darker underneath and the
     descriptions — which are --muted, not ink — dropped toward 4.4:1.
     Raising the card plate puts them back in the clear. */
  background: rgba(255, 255, 255, .74);
  border: 1px solid rgba(15, 10, 8, .06);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
/* whole card flips to the orange + black shade on hover — no motion */
.zx-mega-card:hover {
  color: var(--bone);
  border-color: transparent;
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(239, 90, 25, .6), transparent 58%),
    linear-gradient(120deg, var(--ink) 0%, var(--ember-800) 100%);
  box-shadow: 0 14px 30px rgba(148, 48, 19, .3);
}
.zx-mega-card__icon {
  display: grid; place-items: center;
  width: 40px; height: 40px;
  border-radius: var(--r-sm);
  background: var(--primary-soft);
  color: var(--primary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.zx-mega-card:hover .zx-mega-card__icon { background: rgba(251, 250, 246, .16); color: var(--bone); }
.zx-mega-card__icon svg { width: 20px; height: 20px; }
.zx-mega-card__title {
  display: flex; align-items: center; gap: var(--s2);
  font-family: var(--font-body);
  font-weight: var(--fw-bold);
  font-size: var(--fs-body);
  color: var(--ink);
  transition: color var(--dur-fast) var(--ease-out);
}
.zx-mega-card:hover .zx-mega-card__title { color: var(--white); }
.zx-mega-card__desc { color: var(--muted); font-size: var(--fs-small); line-height: var(--lh-small); transition: color var(--dur-fast) var(--ease-out); }
.zx-mega-card:hover .zx-mega-card__desc { color: rgba(251, 250, 246, .82); }
.zx-mega-card__arrow { width: 16px; height: 16px; margin-left: auto; transition: transform var(--dur-base) var(--ease-spring); }
/* Colour, not a nudge — the arrow used to translate(3px,-3px) on hover,
   which moves an element from its position. */
.zx-mega-card:hover .zx-mega-card__arrow { color: var(--primary); opacity: 1; }

.zx-mega__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  margin-top: var(--s3);
  padding: var(--s3) var(--s2) 0;
  border-top: 1px solid rgba(15, 10, 8, .08);
}
.zx-mega__note { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--fs-small); color: var(--muted); }
.zx-mega__dot { width: 7px; height: 7px; border-radius: var(--r-full); background: var(--ember-500); flex: none; }
/* min-height for WCAG 2.5.8 — the text box alone measured 22px, 2px under
   the 24px minimum target size. align-items keeps the label where it was. */
.zx-mega__all { display: inline-flex; align-items: center; min-height: 24px; gap: var(--s2); font-size: var(--fs-small); font-weight: var(--fw-medium); color: var(--primary); text-decoration: none; }
.zx-mega__all svg { width: 16px; height: 16px; }
.zx-mega__all:hover { color: var(--primary-hover); }

/* ---------- hero — full-bleed ember panel ---------- */
.zx-hero {
  position: relative;
  background: var(--bg);
  color: var(--text);
  padding: calc(var(--nav-height) + var(--s4)) var(--gutter) var(--s7);
}

/* ---- Grainient rays — static, no animation ----
   Vertical light columns of uneven width bleeding into a warm vertical
   ramp, then heavy grain over the top. Every stop from the Ember ramp.
   The bright bloom sits mid-panel where no copy lands; the top and
   bottom stay deep so the headline and proof row keep their contrast. */
.zx-mesh { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.zx-liquid { display: none; }   /* animated renderer retired for the static treatment */

.zx-rays {
  position: absolute;
  inset: 0;
  /* Plain vertical gradient — black floor rising through the dark end of
     the Ember ramp into light orange at the top. No animation, no image.
     Light orange is held to the top ~14% so the headline below it lands on
     mid-ramp orange, where bone type still reads. */
  background: linear-gradient(180deg,
    var(--ember-100) 0%,
    var(--ember-300) 8%,
    var(--ember-400) 16%,
    var(--ember-500) 27%,
    var(--ember-600) 40%,
    var(--ember-700) 54%,
    var(--ember-800) 68%,
    #2A0E05 82%,
    var(--ink) 100%);
}

/* vignette: kills banding, keeps copy legible over any mesh state */
.zx-mesh-grain { position: absolute; inset: 0; }
/* film grain — the texture that makes a gradient read as artwork rather
   than a CSS blur, and hides any residual banding */
.zx-mesh-grain::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: .10;
  mix-blend-mode: overlay;
  pointer-events: none;
}

.zx-hero-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--bone);
  opacity: .9;
  margin-bottom: var(--s6);
}
.zx-hero h1 {
  font-size: var(--fs-display);
  color: var(--bone);
  margin-bottom: var(--s7);
  max-width: 16ch;
}
.zx-hero h1 em { font-style: normal; color: var(--bone); opacity: .72; }

.zx-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  justify-content: center;
  margin-bottom: var(--s9);
}

/* frosted secondary — legible on any part of the mesh */
.zx-hero .zx-btn--primary {
  background: var(--bone);
  color: var(--ink);
  border-color: transparent;
}
.zx-hero .zx-btn--primary:hover { background: #FFFFFF; box-shadow: var(--shadow-lg); }

.zx-btn--glass {
  background: rgba(var(--bone-rgb, 251, 250, 246), .1);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-color: rgba(var(--bone-rgb, 251, 250, 246), .3);
  color: var(--bone);
}
.zx-btn--glass:hover { background: rgba(var(--bone-rgb, 251, 250, 246), .18); border-color: var(--bone); }

/* bottom row: proof left, live-demo card right */
.zx-hero-foot {
  margin-top: auto;
  width: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s6);
  text-align: left;
}
.zx-hero-blurb { max-width: 42ch; }
.zx-hero-blurb p { color: var(--bone); opacity: .9; font-size: var(--fs-small); line-height: var(--lh-small); }

/* ---------- marquee (models / stack strip) ---------- */
.zx [data-band].zx-strip { background-color: var(--bg); }
.zx-strip { position: relative; }
.zx-strip {
  background: var(--surface);
  padding-block: var(--s5);
  overflow: hidden;
}
.zx-strip-label {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s4);
}
.zx-marquee {
  display: flex;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, black 12%, black 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, black 12%, black 88%, transparent);
}
.zx-marquee-track {
  display: flex;
  align-items: center;
  gap: var(--s8);
  padding-inline: var(--s6);
  flex: none;
  animation: zx-marquee 36s linear infinite;
}
@keyframes zx-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}
.zx-marquee-item {
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-heading);
  color: var(--muted); /* readable credibility, not disabled-grey */
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-out);
}
.zx-marquee-item:hover { color: var(--text); }

/* ---------- how it works — editorial, oversized numerals ---------- */
.zx-steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s6);
}
.zx-step {
  position: relative;
  padding-top: var(--s2);
}
.zx-step-num {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--ls-display);
  line-height: 1;
  color: var(--surface-2);
  -webkit-text-stroke: 1px var(--border);
  margin-bottom: var(--s5);
  transition: color var(--dur-slow) var(--ease-out);
}
.zx-step:hover .zx-step-num { color: var(--primary); -webkit-text-stroke: 0; }
.zx-step h3 { margin-bottom: var(--s3); }
.zx-step p { color: var(--muted); font-size: var(--fs-small); line-height: var(--lh-small); max-width: 36ch; }

/* ---------- use cases ---------- */
.zx-cases {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s5);
}
.zx-case-list {
  list-style: none;
  margin: 0 0 var(--s5);
  padding: 0;
  display: grid;
  gap: var(--s2);
}
.zx-case-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  font-size: var(--fs-small);
  color: var(--text);
}
.zx-case-list li::before {
  content: "";
  flex: none;
  width: var(--s4);
  height: var(--s4);
  margin-top: 2px;
  border-radius: var(--r-full);
  background-color: var(--primary-soft);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 8.5l2.5 2.5 4.5-5' fill='none' stroke='%23B93F17' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-size: contain;
}
.zx-case-cta {
  margin-top: auto;
  align-self: flex-start;
  min-height: 24px;
  padding-block: var(--s1);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  color: var(--primary);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  transition: gap var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.zx-case-cta:hover { gap: var(--s2); color: var(--primary-hover); }

/* ---------- features — asymmetric bento ---------- */
.zx-features {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s5);
}
.zx-feature--lead {
  grid-column: span 2;
  grid-row: span 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100%;
}
.zx-feature--lead h3 { font-size: var(--fs-h2); letter-spacing: var(--ls-heading); margin-bottom: var(--s3); }
.zx-feature--lead p { font-size: var(--fs-body); max-width: 46ch; color: var(--muted); }
.zx-feature-metric {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--ls-display);
  line-height: 1;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--s5);
}
.zx-feature-metric small {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-top: var(--s3);
}

/* ============================================================
   FEATURES — dark-ember hover
   ============================================================ */

/* The ember corner wash that used to sit here (a ::before pinned to the
   section's top-right, bleeding to the viewport edge) has been removed.

   position/isolation/overflow stay: the hover fill on the cards below
   relies on this being a positioned stacking context, and overflow-x
   still clips anything that reaches past the section edge. Removing
   them would break the card hover, not just the wash. */
#features { position: relative; isolation: isolate; overflow-x: clip; }

/* The lead card no longer echoes a corner wash that is not there — it
   keeps only the border tint, so it still reads as the lead card
   without carrying a gradient that now points at nothing. */
#features .zx-feature--lead {
  border-color: rgba(239, 90, 25, .30);
}

/* ---- hover: the card fills with dark ember ----
   The fill is a pseudo-element faded by opacity, not an animated
   background-image. Gradients only interpolate when both sides share an
   identical stop structure; at rest these cards have no gradient at all, so
   transitioning background-image would snap rather than fade.

   Colour only — nothing moves, per the sitewide rule.

   Every text colour below is checked against #B93F17, the lightest stop of
   the fill and therefore the worst case. Solid values, not alpha: a
   translucent colour composites against whichever stop sits behind it, so
   its contrast would drift across the card. */
#features .zx-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(130% 115% at 100% 0%,
    var(--ember-600) 0%, var(--ember-700) 36%, var(--ember-800) 70%, #4D140A 100%);
  transition: opacity var(--dur-base) var(--ease-out);
}
#features .zx-card:hover::before,
#features .zx-card:focus-within::before { opacity: 1; }

/* Cards are flex containers, so their children are flex items and take
   z-index without needing position — the fill would otherwise cover them. */
#features .zx-card > * { z-index: 1; }

#features .zx-card h3,
#features .zx-card .zx-card-body > p,
#features .zx-card .zx-feature-metric,
#features .zx-card .zx-feature-metric small,
#features .zx-card .zx-card-tab {
  transition: color var(--dur-base) var(--ease-out);
}
#features .zx-card .zx-card-icon {
  transition: background-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
#features .zx-tag {
  transition: opacity var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}

#features .zx-card:hover,
#features .zx-card:focus-within {
  border-color: var(--ember-800);
  box-shadow: 0 18px 44px rgba(112, 33, 13, .26);
}
#features .zx-card:hover h3,
#features .zx-card:focus-within h3,
#features .zx-card:hover .zx-feature-metric,
#features .zx-card:focus-within .zx-feature-metric { color: var(--bone); }

/* #F6EBE4 — 4.73:1 on the lightest stop, so secondary copy still clears AA */
#features .zx-card:hover .zx-card-body > p,
#features .zx-card:focus-within .zx-card-body > p,
#features .zx-card:hover .zx-feature-metric small,
#features .zx-card:focus-within .zx-feature-metric small,
#features .zx-card:hover .zx-card-tab,
#features .zx-card:focus-within .zx-card-tab { color: #F6EBE4; }

/* Chips darken rather than lighten. A white film would raise the plate
   toward the text and cost contrast; sinking it gains contrast instead. */
#features .zx-card:hover .zx-card-icon,
#features .zx-card:focus-within .zx-card-icon {
  background: rgba(15, 10, 8, .24);
  color: var(--bone);
}
#features .zx-card:hover .zx-tag,
#features .zx-card:focus-within .zx-tag {
  background: rgba(15, 10, 8, .24);
  border-color: rgba(251, 250, 246, .26);
  color: #F6EBE4;
}
/* The notch tab strip is opaque --surface; on a filled card it would sit
   there as a pale block, so it goes transparent and lets the fill through. */
#features .zx-card:hover .zx-card-notch,
#features .zx-card:focus-within .zx-card-notch { background: transparent; }

/* ---- mini chart, used in place of a flat icon on the analytics card ----
   Sits where .zx-card-icon would, so the card keeps the family's rhythm
   while actually showing what it is describing. */
.zx-mini {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s3) var(--s4) var(--s4);
  margin-bottom: var(--s5);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.zx-mini-head {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  transition: color var(--dur-base) var(--ease-out);
}
.zx-mini-dot {
  width: var(--s2);
  height: var(--s2);
  border-radius: var(--r-2xs);
  background: var(--ember-500);
  flex: none;
}
.zx-mini-bars { display: flex; align-items: flex-end; gap: 5px; height: 52px; }
.zx-mini-bars i {
  flex: 1;
  height: var(--h, 50%);
  border-radius: 3px 3px 0 0;
  background: rgba(15, 10, 8, .14);
  transition: background-color var(--dur-base) var(--ease-out);
}
.zx-mini-bars i.is-hot { background: linear-gradient(180deg, var(--ember-400), var(--ember-600)); }

/* The feature cards fill with dark ember on hover, so a light plate here
   would sit on top of that as a pale slab. Invert it with the card. */
#features .zx-card:hover .zx-mini,
#features .zx-card:focus-within .zx-mini {
  background: rgba(15, 10, 8, .24);
  border-color: rgba(251, 250, 246, .18);
}
#features .zx-card:hover .zx-mini-head,
#features .zx-card:focus-within .zx-mini-head { color: #F6EBE4; }
#features .zx-card:hover .zx-mini-bars i,
#features .zx-card:focus-within .zx-mini-bars i { background: rgba(251, 250, 246, .22); }
#features .zx-card:hover .zx-mini-bars i.is-hot,
#features .zx-card:focus-within .zx-mini-bars i.is-hot { background: var(--bone); }
#features .zx-card:hover .zx-mini-dot,
#features .zx-card:focus-within .zx-mini-dot { background: var(--bone); }

.zx-feature {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s6);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.zx-feature:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}
.zx-feature-icon {
  width: var(--s7);
  height: var(--s7);
  border-radius: var(--r-md);
  background: var(--primary-soft);
  color: var(--primary);
  display: grid;
  place-items: center;
  margin-bottom: var(--s5);
}
.zx-feature-icon svg { width: var(--s5); height: var(--s5); }
.zx-feature h3 { font-size: var(--fs-h4); margin-bottom: var(--s2); }
.zx-feature p { color: var(--muted); font-size: var(--fs-small); line-height: var(--lh-small); }

/* ---------- stats band (dark) ---------- */
.zx-stats-band {
  background: var(--bg);
  color: var(--text);
}
.zx-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s6);
}
.zx-stat { text-align: center; }
.zx-stat .n {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--ls-display);
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.zx-stat .n em { font-style: normal; color: var(--primary); }
.zx-stat .l {
  display: block;
  margin-top: var(--s3);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- integrations ---------- */
.zx-integrations {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s5);
}
.zx-int-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s6);
}
.zx-int-group-legacy h3 {
  font-size: var(--fs-h4);
  margin-bottom: var(--s4);
}
.zx-int-badges { display: flex; flex-wrap: wrap; gap: var(--s2); }
.zx-badge {
  display: inline-block;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-chip);
  border: 1px solid var(--border);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--muted);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.zx-badge:hover { color: var(--text); border-color: var(--border-strong); }
.zx-int-api {
  margin-top: var(--s6);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s5) var(--s6);
}
.zx-int-api p { color: var(--muted); }
.zx-int-api code {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--primary);
  background: var(--primary-soft);
  padding: var(--s1) var(--s2);
  border-radius: var(--r-sm);
}

/* ---------- testimonials ---------- */
.zx-quotes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s5);
}
/* Everything below is scoped to #testimonials on purpose. cards.css loads
   AFTER site.css, so a bare `.zx-quote:hover` (0,2,0) ties with
   `.zx-card:hover` (0,2,0) and loses on source order — the plate and the
   glow would silently revert to the generic card hover. The id makes these
   (1,2,0) and settles it regardless of link order. */
#testimonials .zx-quote {
  display: flex;
  flex-direction: column;
  /* Bare at rest: no plate, no outline, just the words on the band. The
     card assembles itself around copy that never moves.

     border-color goes transparent rather than the border being removed.
     A card with no border that grows one on hover shifts every child
     inside it by a pixel, and nothing may move on hover. */
  background-color: transparent;
  background-image: none;
  border-color: transparent;
  box-shadow: none;
  transition: background-color var(--dur-slow) var(--ease-out),
              border-color var(--dur-slow) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out);
}

/* With no plate behind it the notch has nothing to be a tab against, and its
   opaque --surface block would float over the bare band. It becomes a plain
   label, and the concave joins that shape the tab go with it. */
#testimonials .zx-quote .zx-card-notch { background: transparent; }
#testimonials .zx-quote .zx-card-notch::before,
#testimonials .zx-quote .zx-card-notch::after { display: none; }

/* The notch is absolutely positioned and --notch-h (46px) tall measured from
   the card's BORDER, while the body starts at the card's 32px padding — so
   the quote mark was landing on top of the CUSTOMER STORY label. The opaque
   notch used to paint over it and hide the collision; with the notch now
   transparent the body has to actually clear it. */
#testimonials .zx-quote .zx-card-body { padding-top: var(--s6); }

/* ---- quote card: ember bloom from the bottom-right on hover ----
   The card sits on an inverted band, so this has to read as light entering
   the corner, not as a coloured shape laid on top of it. Four layers:
     · a tight specular core — the actual shine
     · the body of the bloom
     · a wide low-alpha falloff so it fades instead of banding
     · a slack diagonal sheen across the plate

   Built on the brand ramp — ember-400 centre, ember-500 core, ember-600
   falloff — NOT on the pale end. An earlier pass used ember-100 (#FFC3A1)
   for the core: on black that desaturates to a milky peach and stops
   reading as the brand colour at all. Saturation is what makes orange
   survive a dark backdrop; lightness alone just makes it look washed.

   The core is deliberately TIGHT. Brightness and area trade against each
   other here: spreading this much luminance across the whole card would
   wash the quote text below AA. Keeping it small buys the shine without
   ever reaching the copy — measured, the brightest pixel under the
   blockquote is a fraction as bright as the corner itself.

   Colour and opacity only — the card does not move, per the sitewide rule. */
#testimonials .zx-quote::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background:
    /* top-edge sheen. It lives in here rather than on the card because
       background-image does not interpolate — put on the card it would snap
       in while everything else faded. */
    linear-gradient(180deg, rgba(255, 255, 255, .05) 0%, rgba(255, 255, 255, 0) 46%),
    /* hot centre — ember-400, the shine */
    radial-gradient(26% 30% at 100% 100%, rgba(255, 122, 62, .82) 0%, rgba(255, 122, 62, 0) 100%),
    /* the colour the card actually reads as: ember-500, the brand anchor */
    radial-gradient(54% 58% at 100% 100%, rgba(239, 90, 25, .78) 0%, rgba(239, 90, 25, .26) 56%, rgba(239, 90, 25, 0) 100%),
    /* deep falloff through ember-600 so it dies into the black rather than
       stopping on an edge */
    radial-gradient(130% 142% at 100% 100%, rgba(185, 63, 23, .34) 0%, rgba(185, 63, 23, 0) 78%);
  transition: opacity var(--dur-slow) var(--ease-out);
}
#testimonials .zx-quote:hover::after,
#testimonials .zx-quote:focus-within::after { opacity: 1; }

/* Flex items take z-index without needing position, so the copy clears the
   bloom without any of it being repositioned. */
#testimonials .zx-quote > * { z-index: 1; }

#testimonials .zx-quote:hover,
#testimonials .zx-quote:focus-within {
  background-color: var(--surface);
  border-color: rgba(239, 90, 25, .58);
  /* No outer glow. The bloom is contained entirely inside the card — the
     only box-shadow here is the inset 1px top highlight, which sits on the
     card's own edge rather than spilling behind it. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

#testimonials .zx-quote .zx-quote-mark,
#testimonials .zx-quote blockquote,
#testimonials .zx-quote .zx-quote-avatar,
#testimonials .zx-quote .zx-card-tab {
  transition: color var(--dur-slow) var(--ease-out),
              background-color var(--dur-slow) var(--ease-out);
}
/* the mark and the avatar catch the light too, so the whole card warms
   together rather than one corner lighting up on its own */
#testimonials .zx-quote:hover .zx-quote-mark,
#testimonials .zx-quote:focus-within .zx-quote-mark { color: var(--ember-300); }
#testimonials .zx-quote:hover .zx-quote-avatar,
#testimonials .zx-quote:focus-within .zx-quote-avatar {
  background: rgba(255, 158, 113, .20);
  color: var(--ember-100);
}
.zx-quote-mark {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-h1);
  line-height: .5;
  color: var(--primary);
  margin-bottom: var(--s4);
}
.zx-quote blockquote {
  margin: 0 0 var(--s5);
  font-size: var(--fs-lead);
  line-height: var(--lh-small);
  letter-spacing: -.01em;
}
.zx-quote-author { margin-top: auto; display: flex; align-items: center; gap: var(--s3); }
.zx-quote-author > div strong { display: block; font-size: var(--fs-small); }
.zx-quote-author > div span { display: block; font-size: var(--fs-mono); font-family: var(--font-mono); color: var(--muted); }
.zx-quote-avatar {
  width: var(--s7);
  height: var(--s7);
  border-radius: var(--r-full);
  background: var(--primary-soft);
  color: var(--primary);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  line-height: 1;
  flex: none;
}

/* ---------- faq ---------- */
/* Two columns, each an independent stack.
   The columns are separate elements on purpose. A single grid of six items
   shares row heights between its columns, so opening an answer on the left
   grew that row and left an identical hole under the closed item on the
   right. Only decoupling the stacks fixes it: now a column grows on its own
   and nothing beside it moves. */
.zx-faq {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: var(--s3);
}
.zx-faq-col {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.zx-faq-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-out);
}
.zx-faq-item[open] { border-color: var(--border-strong); }
.zx-faq-item summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s5) var(--s5);
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-heading);
}
.zx-faq-item summary::-webkit-details-marker { display: none; }
.zx-faq-item summary:hover { color: var(--primary); }
.zx-faq-x {
  position: relative;
  width: var(--s4);
  height: var(--s4);
  flex: none;
  /* the whole mark spins as the vertical stroke retracts, so the plus
     becomes a minus in one gesture rather than one stroke twitching */
  transition: transform var(--dur-slow) var(--ease-out);
}
.zx-faq-item[open] .zx-faq-x { transform: rotate(180deg); }
.zx-faq-x::before, .zx-faq-x::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: var(--primary);
  border-radius: var(--r-full);
  transition: transform var(--dur-slow) var(--ease-out);
}
.zx-faq-x::before { width: 100%; height: 2px; }
.zx-faq-x::after { width: 2px; height: 100%; }
.zx-faq-item[open] .zx-faq-x::after { transform: scaleY(0); }

.zx-faq-item .zx-faq-a {
  padding: 0 var(--s5) var(--s5);
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  max-width: 70ch;
  /* Required for the height animation in main.js. Everything is box-sizing:
     border-box sitewide, so height includes the padding — animating height
     to 0 collapses the padding with it, and this clips the text while it
     travels. Without it the answer spills out of the collapsing panel. */
  overflow: hidden;
}

/* ---------- footer — CTA card + light logo bar ---------- */
.zx-foot {
  background: var(--bg);
  color: var(--text);
  padding-block: clamp(var(--s8), 9vw, var(--s9)) var(--s6);
}
/* footer runs wider than the site container so the card fills the row */
.zx-foot .zx-container--wide { max-width: 1460px; }

/* big dark card: black left → orange-glow right ("linear shadow") */
.zx-foot-card {
  /* The card's padding lives in a variable because .zx-foot-visual has to
     cancel exactly this value to sit flush on the bottom edge. Written
     twice, the two drifted apart the moment the mobile breakpoint changed
     the padding — the artwork overhung the card by 16px at 390px. One
     declaration, referenced twice, cannot drift. */
  --foot-card-pad: clamp(var(--s7), 5vw, var(--s9));
  position: relative;
  /* NOT overflow: hidden. That is what sliced the top off the artwork.
     The clip is not needed for the corners either — the card paints no
     background of its own any more; ::before below carries both the
     colour and the radius and clips itself. Leaving the card visible
     lets the image rise out of the top edge. */
  isolation: isolate;
  border-radius: var(--r-2xl);
  padding: var(--foot-card-pad);
  display: grid;
  /* The visual column is the wider of the two. The artwork is sized by
     height but bounded by the width of its column, so a narrow column
     caps the height indirectly and leaves dead space above the image —
     widening the column is what lets it grow tall enough to fill the
     card. The copy is short enough to take the smaller share. */
  grid-template-columns: minmax(0, .74fr) minmax(0, 1.26fr);
  gap: clamp(var(--s6), 4vw, var(--s8));
  align-items: center;
  color: var(--bone);
  /* No background here. It moved to ::before so the card itself can stay
     unclipped; a background on this box would need the radius AND would
     paint over nothing useful. */
}

/* The card's actual surface: flat black, plus rounded corners, on a layer
   of its own so the card no longer has to clip its own children to keep
   its shape.

   Flat on purpose. The colour used to be a gradient washing across the
   whole card from the right, which lit the card itself. The glow now
   belongs to the ARTWORK — see ::after — so the card behind it has to be
   plain, or the two tints stack and the halo has nothing to read
   against. */
.zx-foot-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--ink);
}

/* The halo behind the artwork.

   Anchored to where the artwork sits — right-hand side, low — rather than
   centred on the card, so it reads as light coming off the image instead
   of a panel tint. It is a separate layer from the surface above so it
   can sit BETWEEN the black card and the image: z-index -1 puts it behind
   the content but the surface is painted first, so the order is
   black card → halo → artwork.

   `closest-side` is avoided deliberately: anchored near an edge it
   collapses the radius toward zero and the gradient silently paints
   nothing. Both stops carry explicit percentage sizes. */
.zx-foot-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  /* Three stacked blooms, all centred on the artwork, brightest core out
     to widest falloff. Matches the reference build: one light source sat
     behind the product shot, dying to flat black before it reaches the
     card edges — not a tint washed across the panel.

     Core is ember-100/300 rather than ember-500. A mid-orange core reads
     as a coloured patch; the pale end reads as light, which is the whole
     illusion. Saturation comes from the mid layer underneath it. */
  /* Ellipses are TALL and NARROW, not round. That is the difference
     between this and the reference: a circular bloom reads as a spotlight
     aimed at the panel, while a tall column of light reads as glow coming
     off the product itself. Every layer is roughly twice as tall as it is
     wide and runs past the card top and bottom, so vertically it never
     resolves into a visible disc edge — it just fades.

     Horizontal falloff stays tight so the copy side keeps its flat black.
     Core is ember-100 (pale) rather than ember-500: the pale end reads as
     light, a mid-orange core reads as a coloured patch. Saturation comes
     from the layers beneath it. */
  /* A ROUND halo, sat low so the card's bottom edge slices it off.

     Two things make that work:

     1. The percentage pairs look lopsided but resolve to circles. Each
        axis is a percentage of a DIFFERENT side, and the card runs about
        1340x500 — so `20% 54%` is 268px wide by 270px tall, round. An
        even-looking pair like `24% 24%` would paint a flat wide lens.
        Re-derive these if the card's proportions change.

     2. The centres sit at ~82% down, and the radii reach past 100%, so
        the lower arc falls outside the card. This layer is inset:0 with
        the card's radius, so that part is simply never painted — the
        glow ends flat on the bottom line instead of fading under it.
        Centres any higher and the halo has already dimmed by the time it
        reaches the edge, which reads as a fade rather than a cut. */
  background:
    radial-gradient(circle 400px at 70% 80%, rgba(255, 214, 182, .54) 0%, rgba(255, 195, 161, .32) 42%, rgba(255, 158, 113, 0) 74%),
    radial-gradient(circle 600px at 70% 82%, rgba(255, 122, 62, .36) 0%, rgba(239, 90, 25, .16) 48%, rgba(239, 90, 25, 0) 78%),
    radial-gradient(circle 850px at 70% 84%, rgba(185, 63, 23, .28) 0%, rgba(148, 48, 19, .08) 56%, rgba(112, 33, 13, 0) 84%);
}

.zx-foot-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: clamp(2.25rem, 1.5rem + 2.8vw, 3.5rem);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  color: var(--white);
}
.zx-foot-card__sub {
  margin: var(--s5) 0 var(--s6);
  max-width: 42ch;
  color: rgba(251, 250, 246, .82);
  font-size: var(--fs-lead);
  line-height: var(--lh-sub);
}
.zx-foot-card__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s5); }
/* Call action — an icon tile, squared to the button radius rather than a
   circle, so it reads as a sibling of the Book a demo pill beside it.

   A fixed square, centred against the taller pill, rather than stretched to
   match it. Two things ruled stretching out: aspect-ratio resolved the width
   from min-height BEFORE the stretch set the real height, so the tile came
   out 56x64; and below 768px .zx-foot-card__actions switches to a column
   with align-items:stretch, which blew the tile out to 296px wide.
   align-self:center opts out of both. */
.zx-foot-call {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: 56px;
  height: 56px;
  flex: none;
  border-radius: var(--r-button);
  border: 1px solid rgba(251, 250, 246, .28);
  background: rgba(251, 250, 246, .08);
  color: var(--bone);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.zx-foot-call svg { width: 22px; height: 22px; }
.zx-foot-call:hover {
  background: var(--bone);
  border-color: var(--bone);
  color: var(--ember-600);
}

/* right visual — supplied artwork.

   The drawn live-call panel that used to live here (card, waveform,
   metric bars, floating chips) is gone along with its markup — see the
   note in _includes/components/footer.njk. Its rules went with it
   rather than being left behind as dead weight: .zx-callstage,
   .zx-callcard*, .zx-metric*, .zx-live and .zx-foot-fchip* had no
   other callers anywhere in the project. */
/* The artwork sits ON the card's bottom edge rather than floating in the
   middle of the row.

   Three things are needed and all three are load-bearing:
     · align-self: stretch — the card is align-items:center, so without
       this the column is only as tall as the image and there is no room
       to push anything to a bottom.
     · align-items: flex-end — sends the image down inside that column.
     · the negative margin cancels the card's own bottom padding, which
       is what stops the image resting a padding's width ABOVE the edge.
       It reads --foot-card-pad straight off the card, so retuning the
       padding at any breakpoint moves both together automatically.

   The card is overflow:hidden, so the overhang is clipped exactly at the
   rounded bottom edge instead of spilling out of it. */
.zx-foot-visual {
  position: relative;
  align-self: stretch;
  display: flex;
  align-items: flex-end;
  /* flex-START, not end. When a flex item is wider than its container the
     overflow spills toward the START side, so flex-end would push the
     artwork right and hang the excess back over the copy. From the start
     edge it grows rightward instead, into the card's own padding and then
     off the clipped edge. */
  justify-content: flex-start;
  min-width: 0;
  /* Bottom AND right padding are both cancelled. Bottom pins the artwork
     to the card's lower edge; right hands it the padding strip it was
     sitting inside, which is free width and therefore free height at the
     same aspect ratio — a bigger image without touching the card.

     Only these two sides: cancelling the left would run the artwork under
     the copy, and the top is left alone so the artwork stays inside the
     card rather than rising out of it. */
  margin-bottom: calc(-1 * var(--foot-card-pad));
  margin-right: calc(-1 * var(--foot-card-pad));
}

/* Fitted INSIDE the card, not overhanging it. The artwork scales down to
   whatever the column gives it and sits on the bottom edge.

   No radius, border or shadow: the artwork is already a composed panel
   with its own rounded card inside it, and framing a picture of a frame
   is what makes these read as pasted on. */
.zx-foot-visual__img {
  /* Pinned to the column box on all four sides, so the artwork is fully
     CONTAINED by the card — nothing rises past the top edge any more.

     Still absolute: that is what stops its height pushing the card taller
     (in flow the grid row grew 628px -> 750px). inset:0 just means the box
     is now exactly the column instead of extending above it.

     object-fit: contain + object-position bottom = the whole artwork is
     visible, scaled down to fit, and sat on the bottom edge. Any leftover
     room appears at the TOP, never as a crop. */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: contain;
  object-position: bottom center;
  /* Decorative and aria-hidden, so it must not swallow clicks meant for
     anything it sits over — the social row and buttons share the section
     it is hanging over. */
  pointer-events: none;
  /* No object-fit: the box IS the artwork's natural ratio now, so there
     is nothing to fit inside anything. Adding contain here would letterbox
     it back down and undo the scale. */
}

.zx-foot-avatars { display: inline-flex; }
.zx-foot-avatars i { width: 22px; height: 22px; border-radius: var(--r-full); border: 2px solid var(--ink); background: var(--ember-400); margin-left: -7px; }
.zx-foot-avatars i:nth-child(2) { background: var(--ember-600); }
.zx-foot-avatars i:nth-child(3) { background: var(--ember-800); }
.zx-foot-avatars i:first-child { margin-left: 0; }

/* bottom bar — logo + links (light) */
.zx-foot-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s5);
  margin-top: var(--s7);
  padding-inline: clamp(var(--s5), 3vw, var(--s7));
}
.zx-foot-logo { height: 30px; width: auto; justify-self: start; }
.zx-foot-copy { justify-self: end; text-align: right; font-family: var(--font-mono); font-size: var(--fs-mono); color: var(--muted); }
.zx-foot-legal { display: flex; gap: var(--s5); justify-self: center; }
.zx-foot-legal a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.zx-foot-legal a:hover { color: var(--primary); }

/* socials now live inside the dark card, under the CTA button */
.zx-foot-social { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s6); }
.zx-foot-social__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px; height: 48px;
  border-radius: var(--r-full);
  border: 1px solid var(--ember-600);
  background: var(--ember-600);
  color: var(--white);
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-spring);
}
.zx-foot-social__link svg { width: 22px; height: 22px; }
/* No translateY on hover. Nothing on this site moves from its position on
   hover — colour carries the whole state change. */
.zx-foot-social__link:hover { color: var(--ember-600); background: var(--white); border-color: var(--white); }

/* ---------- floating voice widget ---------- */
.zx-voice {
  position: fixed;
  right: var(--s5);
  bottom: var(--s5);
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  gap: var(--s3);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
/* the hero already offers a live-demo CTA — don't stack a second one on top of it */
.zx-voice.is-hidden {
  opacity: 0;
  transform: translateY(12px) scale(.9);
  pointer-events: none;
}
.zx-voice-label {
  background: var(--ink);
  color: var(--bone);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  padding: var(--s2) var(--s4);
  border-radius: var(--r-chip);
  box-shadow: var(--shadow-md);
  opacity: 0;
  
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
  pointer-events: none;
  white-space: nowrap;
}
.zx-voice:hover .zx-voice-label,
.zx-voice:focus-within .zx-voice-label { opacity: 1;  }
.zx-voice-btn {
  position: relative;
  isolation: isolate;
  width: 66px;
  height: 66px;
  border-radius: var(--r-full);
  border: 1px solid rgba(251, 250, 246, .14);
  /* subtle depth at rest so the ember reads as the hover state, not the default */
  background: radial-gradient(130% 130% at 30% 22%, #241612, var(--ink) 70%);
  color: var(--bone);
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(251, 250, 246, .12), 0 0 0 6px rgba(239, 90, 25, .06);
  transition: background var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              transform var(--dur-fast) var(--ease-spring);
}
/* expanding "voice" rings — a faint one idles, both flare on hover / focus */
.zx-voice-btn::before,
.zx-voice-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 2px solid var(--ember-500);
  opacity: 0;
  pointer-events: none;
}
.zx-voice-btn::before { animation: zx-ripple-idle 3.4s var(--ease-out) infinite; }
.zx-voice-btn:hover,
.zx-voice-btn:focus-visible {
  background: linear-gradient(155deg, var(--ember-400), var(--ember-600));
  border-color: var(--ember-400);
  box-shadow: var(--shadow-lg), 0 0 48px rgba(239, 90, 25, .55), inset 0 1px 0 rgba(255, 255, 255, .22);
  transform: translateY(-2px);
}
.zx-voice-btn:hover::before,
.zx-voice-btn:focus-visible::before { animation: zx-ripple 1.7s var(--ease-out) infinite; }
.zx-voice-btn:hover::after,
.zx-voice-btn:focus-visible::after { animation: zx-ripple 1.7s var(--ease-out) .85s infinite; }
.zx-voice-btn svg { width: 34px; height: 34px; transition: transform var(--dur-base) var(--ease-spring); }
.zx-voice-btn:hover svg,
.zx-voice-btn:focus-visible svg { transform: scale(1.08); }
@keyframes zx-ripple-idle {
  from { opacity: .26; transform: scale(1); }
  to   { opacity: 0;   transform: scale(1.55); }
}
@keyframes zx-ripple {
  from { opacity: .55; transform: scale(1); }
  to   { opacity: 0;   transform: scale(1.95); }
}
@media (prefers-reduced-motion: reduce) {
  .zx-voice-btn::before,
  .zx-voice-btn::after { animation: none !important; }
}

/* ---------- reveal helpers (JS sets initial state) ---------- */
[data-reveal] { will-change: transform, opacity; }

/* ---------- skip link ---------- */
.zx-skip {
  position: absolute;
  left: -9999px;
  top: var(--s4);
  z-index: var(--z-toast);
  background: var(--primary);
  color: var(--on-primary);
  padding: var(--s3) var(--s5);
  border-radius: var(--r-button);
  text-decoration: none;
  font-weight: var(--fw-medium);
}
.zx-skip:focus { left: var(--s4); }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1023px) {
  .zx-hero-foot { flex-direction: column; align-items: stretch; gap: var(--s6); }
  .zx-hero-actions { margin-bottom: var(--s8); }
  .zx-steps, .zx-cases, .zx-features, .zx-integrations { grid-template-columns: repeat(2, 1fr); }
  .zx-feature--lead { grid-column: span 2; grid-row: auto; }
  .zx-stats { grid-template-columns: repeat(2, 1fr); gap: var(--s7) var(--s5); }
  .zx-foot-card { grid-template-columns: 1fr; gap: var(--s7); }
  /* Once the card stacks, the artwork leads the column rather than
     sitting centred beside the copy, and is capped so it does not
     dominate the block it is illustrating. */
  /* Stacked: the artwork goes back INTO flow. Absolute positioning only
     makes sense while it has a column of its own to overhang — once the
     card is one column the copy is directly above it, and an out-of-flow
     image would sit on top of that copy instead of below it.
     The right-padding cancel goes too: with one column, pulling right
     would drag the artwork off the card's edge. */
  .zx-foot-visual { justify-content: flex-start; margin-right: 0; }
  .zx-foot-visual__img { position: static; width: 100%; max-width: 420px; }
}

@media (max-width: 767px) {
  .zx-nav .zx-nav-in { padding-inline: var(--s4); gap: var(--s4); }
  .zx-nav-links {
    position: fixed;
    top: var(--nav-height);
    inset-inline: 0;
    bottom: 0;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s1);
    margin: 0;
    padding: var(--s5) var(--gutter);
    background: var(--bg);
    transform: translateX(100%);
    /* Hidden, not merely off-screen. Left visible, the four links and both
       CTAs stayed in the tab order and the accessibility tree while parked
       outside the viewport — tabbing from the logo sent focus somewhere the
       user could not see. visibility is animatable in steps, so it flips at
       the end of the close and at the start of the open. */
    visibility: hidden;
    transition: transform var(--dur-slow) var(--ease-out),
                visibility 0s linear var(--dur-slow);
    overflow-y: auto;
  }
  .zx-nav-links.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur-slow) var(--ease-out),
                visibility 0s linear 0s;
  }
  .zx-nav-links a { padding: var(--s4); font-size: var(--fs-lead); border-radius: var(--r-md); }
  .zx-nav-links .zx-nav-links-cta {
    display: flex !important;
    flex-direction: column;
    gap: var(--s3);
    margin-top: var(--s5);
  }
  .zx-nav-cta { display: none; }
  /* auto, not a fixed gap. The base rule already sets margin-left: auto to
     push the toggle to the far edge; overriding it with --s2 parked the
     toggle immediately after the logo with dead space to its right. On
     mobile the drawer is position:fixed and the CTA row is hidden, so the
     logo and this button are the only two items in flow — auto splits them
     hard left and hard right. */
  .zx-nav-toggle { display: inline-flex; margin-left: auto; }
  /* mega-menu: on mobile it isn't a hover pop-over — it becomes an inline,
     always-open sub-list stacked under its trigger inside the drawer. */
  .zx-nav-trigger .zx-caret { display: none; }
  .zx-mega {
    position: static;
    display: block;
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    width: auto;
    min-width: 0;
    max-width: none;
    margin: 0 0 var(--s2);
    padding: 0;
    background: transparent;
    box-shadow: none;
    border: 0;
  }
  .zx-mega__panel { position: static; padding: 0; box-shadow: none; background: transparent; }
  .zx-mega__panel::before { display: none; }        /* kill the desktop glass plate */
  /* undo the desktop fade-in start state so the list is simply visible */
  .zx-mega__panel > .zx-mega__grid,
  .zx-mega__panel > .zx-mega__foot { opacity: 1; transform: none; }
  .zx-mega__grid { display: flex; flex-direction: column; gap: var(--s1); }
  .zx-mega-card {
    flex-direction: row;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s3);                    /* shorter rows */
    border-radius: var(--r-sm);
    background: var(--surface);
  }
  .zx-mega-card__icon { width: 28px; height: 28px; }
  .zx-mega-card__icon svg { width: 16px; height: 16px; }
  .zx-mega-card__title { font-size: var(--fs-small); }
  .zx-mega-card__desc { display: none; }             /* keep the mobile list compact */
  .zx-mega__foot { display: none; }

  .zx-steps, .zx-cases, .zx-features, .zx-integrations, .zx-quotes, .zx-faq { grid-template-columns: 1fr; }
  .zx-feature--lead { grid-column: auto; grid-row: auto; }
  .zx-step-num { font-size: var(--fs-h2); }
  .zx-hero { padding-inline: var(--s3); }
  /* display type needs its own mobile step — the fluid clamp floor is
     still too heavy for a 390px column */
  .zx-hero h1 { font-size: clamp(2.125rem, 8.5vw, 2.75rem); max-width: 100%; }
  .zx-hero-eyebrow { margin-bottom: var(--s5); }
  .zx-hero-actions { width: 100%; margin-bottom: var(--s7); }
  .zx-hero-actions .zx-btn { flex: 1 1 100%; }
  .zx-foot-card { --foot-card-pad: clamp(var(--s6), 7vw, var(--s7)); gap: var(--s6); }
  .zx-foot-card__actions { flex-direction: column; align-items: stretch; }
  /* On the narrowest screens the cap comes off — the column is already
     narrow enough that limiting the artwork just wastes width. */
  .zx-foot-visual__img { max-width: none; }
  .zx-foot-bar { grid-template-columns: 1fr; justify-items: start; gap: var(--s4); }
  .zx-foot-logo, .zx-foot-copy, .zx-foot-legal { justify-self: start; }
  .zx-foot-copy { text-align: left; }
  .zx-int-api { flex-direction: column; align-items: flex-start; }
  .zx-voice { right: var(--s4); bottom: var(--s4); }

  /* The label is one unbroken mono line with wide tracking; at 390px it ran
     past both viewport edges and got sliced. Let it wrap, and give it the
     gutter the marquee below it does not need. */
  .zx-strip-label {
    padding-inline: var(--gutter);
    letter-spacing: .10em;
    line-height: 1.5;
  }
  /* Display-size logos at 390px clip mid-word, which reads as broken layout
     rather than as a strip that is moving. */
  .zx-marquee-item { font-size: var(--fs-lead); }
  .zx-marquee-track { gap: var(--s6); padding-inline: var(--s4); }

  /* The frost is tuned for a wide bar over mostly-flat page. Over the hero
     on a narrow screen there is far less plate per pixel of busy artwork
     behind it, and hero copy read straight through the pill. Opacity up,
     mobile only — desktop glass is untouched. */
  .zx-nav-in::before {
    background: linear-gradient(180deg, rgba(255,255,255,.94) 0%, rgba(241, 237, 228, .88) 100%);
  }
}

@media (max-width: 479px) {
  .zx-stats { grid-template-columns: 1fr 1fr; gap: var(--s6) var(--s4); }
  .zx-stat .n { font-size: var(--fs-h2); }
}

/* ============================================================
   REDUCED MOTION — complete experience, no movement
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .zx *, .zx *::before, .zx *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .zx-marquee-track { animation: none !important; }
  .zx-marquee { flex-wrap: wrap; justify-content: center; -webkit-mask-image: none; mask-image: none; }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  .zx-atmosphere i { animation: none !important; }
}

/* compact split button for the nav pill */
.zx-btn--split.zx-btn--sm .zx-btn-badge { width: 38px; height: 38px; }
.zx-btn--split.zx-btn--sm .zx-btn-badge svg { width: 15px; height: 15px; }
.zx-btn--split.zx-btn--sm .zx-btn-label {
  min-height: 38px;
  padding-inline: var(--s4);
  font-size: var(--fs-button);
}

