/* ============================================================
   Xalo — Brand tokens & site-wide overrides
   Source of truth: .claude/XALO-BRAND.md
   This file runs BEFORE React hydration, so rules persist even
   as Framer re-renders.
   ============================================================ */

/* ============================================================
   Huly-style primary CTA — exact port of huly.io "See in Action"
   button (captured via Chrome MCP inspection on 2026-04-19).
   Applied to all primary CTAs OUTSIDE the Nav Bar.
   Structure (injected by migrate-xalo-copy.mjs):
     a.xalo-huly
       └─ div.xalo-huly__glow-wrap (absolute, z-index -10)
            ├─ div.xalo-huly__glow-outer  (204x103, blur 5px)
            └─ div.xalo-huly__glow-inner  (121x121, bright core)
       └─ [existing Framer label content]
   ============================================================ */
a.xalo-huly,
.xalo-huly {
  /* Pill layout + size (Huly: h-10 px-16, text-12 font-bold uppercase) */
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  position: relative !important;
  z-index: 10 !important;
  height: 40px !important;
  min-height: 40px !important;
  max-height: 40px !important;
  padding: 0 64px !important;
  border-radius: 9999px !important;
  /* Huly palette */
  background: #d1d1d1 !important;
  background-color: #d1d1d1 !important;
  color: #0b0d10 !important;
  border: 1px solid rgba(255, 255, 255, 0.6) !important;
  /* Warm outside halo biased to right (matches huly.io reference). */
  box-shadow:
    20px 0 36px -6px rgba(80, 240, 140, 0.35),
    0 0 16px -2px rgba(170, 255, 200, 0.25) !important;
  /* Typography */
  font-family: 'Space Grotesk','Inter',system-ui,sans-serif !important;
  font-size: 12px !important;
  font-weight: 700 !important;
  line-height: 1 !important;
  letter-spacing: -0.015em !important;
  text-transform: uppercase !important;
  text-decoration: none !important;
  white-space: nowrap !important;
  /* Kill Framer transforms & animations */
  transform: none !important;
  translate: none !important;
  scale: 1 !important;
  rotate: none !important;
  animation: none !important;
  transition: background 200ms, color 200ms !important;
  /* Match huly.io exactly — overflow:hidden clips the glow to the pill's
     rounded rect so the gradient softly terminates at the edge. The
     apparent "halo bleeding out" in the reference is the gradient fade
     within the pill, not actual pixels outside. */
  overflow: hidden !important;
  cursor: pointer !important;
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  isolation: isolate;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* Lock hover states — no size / colour / transform change (Huly's is static). */
a.xalo-huly:hover,
a.xalo-huly:focus,
a.xalo-huly:focus-visible,
a.xalo-huly:active,
.xalo-huly:hover,
.xalo-huly:focus,
.xalo-huly:active {
  background: #d1d1d1 !important;
  background-color: #d1d1d1 !important;
  color: #0b0d10 !important;
  border: 1px solid rgba(255, 255, 255, 0.6) !important;
  /* Keep warm halo on hover/focus/active. */
  box-shadow:
    20px 0 36px -6px rgba(80, 240, 140, 0.35),
    0 0 16px -2px rgba(170, 255, 200, 0.25) !important;
  transform: none !important;
  scale: 1 !important;
}

/* Text inside the button — force Huly colour + typography on every descendant
   (Framer injects nested wrappers with their own colour tokens). Also bump
   text to z-index 1 so it paints ABOVE the glow wrapper (glow sits at
   z-index 0 inside the anchor). */
a.xalo-huly > *:not(.xalo-huly__glow-wrap),
a.xalo-huly *:not(.xalo-huly__glow-wrap):not(.xalo-huly__glow-inner):not(.xalo-huly__glow-outer),
.xalo-huly p,
.xalo-huly span,
.xalo-huly div:not(.xalo-huly__glow-wrap):not(.xalo-huly__glow-inner):not(.xalo-huly__glow-outer) {
  color: #0b0d10 !important;
  font-size: 12px !important;
  font-weight: 700 !important;
  line-height: 1 !important;
  letter-spacing: -0.015em !important;
  text-transform: uppercase !important;
  background: transparent !important;
  position: relative !important;
  z-index: 1 !important;
}

/* Disable Framer's font-size:0 + ::after replacement trick on non-nav CTAs
   (that trick was only needed for the nav "Get in touch" label swap). */
a.xalo-huly p,
.xalo-huly p {
  font-size: 12px !important;
  line-height: 1 !important;
}
a.xalo-huly p::after,
.xalo-huly p::after { content: none !important; }

/* Kill the wave glow pseudo-elements on .xalo-huly — Huly uses child divs
   for its glow, not pseudos. */
a.xalo-huly::before,
a.xalo-huly::after,
.xalo-huly::before,
.xalo-huly::after {
  content: none !important;
  display: none !important;
  background: none !important;
  animation: none !important;
}

/* Glow wrapper — cursor-following. --hx is 0..1 (pointer X fraction across
   button). The wrapper's centre tracks the pointer horizontally. It's
   stretched full-width of the pill (left:0 right:0) and its flex children
   (the two glow layers) are centred inside, positioned via --hx. */
.xalo-huly__glow-wrap {
  position: absolute !important;
  top: 0 !important;
  bottom: 0 !important;
  left: 0 !important;
  right: 0 !important;
  z-index: 0 !important;
  pointer-events: none !important;
  display: block !important;
  transition: none !important;
}

/* Inner bright core — restored to original subtle scale (0.30) so
   the INSIDE of the pill stays clean and the whole flare drama
   lives on the OUTSIDE via the pill's box-shadow (see .xalo-huly
   rule above). */
.xalo-huly__glow-inner {
  position: absolute !important;
  top: 50% !important;
  left: 0 !important;
  width: 121px !important;
  height: 121px !important;
  transform: translate(calc(var(--hx, 0.82) * var(--huly-btn-w, 207px) - 50%), -50%) scale(calc(1 + var(--hedge, 0.65) * 0.30)) !important;
  background: radial-gradient(
    50% 50% at 50% 50%,
    rgba(240, 255, 240, 0.85) 0%,
    rgba(190, 255, 215, 0.70) 24%,
    rgba(120, 245, 175, 0.45) 42%,
    rgba(70, 232, 138, 0.22) 60%,
    rgba(15, 107, 62, 0.00) 88%
  ) !important;
  pointer-events: none !important;
  transition: none !important;
  will-change: transform;
}

/* Outer blurred halo — restored to original 0.40 scale response.
   Only the OUTSIDE pill-edge bloom is amplified (see .xalo-huly
   box-shadow rule above). */
.xalo-huly__glow-outer {
  position: absolute !important;
  top: 50% !important;
  left: 0 !important;
  width: 204px !important;
  height: 103px !important;
  transform: translate(calc(var(--hx, 0.82) * var(--huly-btn-w, 207px) - 50%), -50%) scale(calc(1 + var(--hedge, 0.65) * 0.40)) !important;
  background: radial-gradient(
    43.3% 44.23% at 50% 49.51%,
    rgba(230, 255, 235, 0.60) 20%,
    rgba(170, 250, 200, 0.45) 42%,
    rgba(90, 220, 140, 0.25) 62%,
    rgba(40, 170, 90, 0.00) 100%
  ) !important;
  filter: blur(6px) !important;
  pointer-events: none !important;
  transition: none !important;
  will-change: transform;
}

/* Unlock any hardcoded inline width Framer baked into the CTA wrapper so
   the Huly button's new 64px horizontal padding governs width.
   Scoped to Framer-generated wrappers only — previously the
   `*:has(> .xalo-huly-wrap)` wildcard also caught our own `.xr-*`
   containers (e.g. `.xr-hero__container`), and the `max-width: none
   !important` there was leaking past the viewport at mobile widths. */
[data-framer-name="Button"]:has(.xalo-huly),
[data-framer-name="Buttons"]:has(.xalo-huly),
[data-framer-name="Button"]:has(.xalo-huly-wrap),
[data-framer-name="Buttons"]:has(.xalo-huly-wrap),
[data-framer-component-type]:has(> a.xalo-huly),
[data-framer-component-type]:has(> .xalo-huly-wrap),
[data-framer-component-type]:has(> span > a.xalo-huly) {
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  height: auto !important;
  transform: none !important;
  animation: none !important;
  overflow: visible !important;
  position: relative;
  z-index: 20;
}

/* Stacking fix: the hero 3D canvas sits at body-level with z-index 0 and
   renders later in DOM order than #main. Positioned elements stack above
   static ones regardless of DOM order, so the 3D figure paints over the
   hero text/CTA by default.
   Solution: promote ONLY the CTA's nearest Framer wrapper to a stacking
   context that sits above the 3D, without making #main opaque (which
   would hide the 3D entirely). We use the .xalo-huly-wrap's own
   z-index 20 + the Framer "Content" ancestor being already z:2 which
   creates its own stacking context. We force that chain with explicit
   z-index/position on the Framer button containers. */
[data-framer-name="Buttons"]:has(.xalo-huly-wrap),
[data-framer-name="Button"]:has(.xalo-huly-wrap) {
  position: relative !important;
  z-index: 50 !important;
}

/* Stop Framer section wrappers from clipping the Huly glow bleed. Any
   ancestor of a .xalo-huly-wrap must have overflow:visible so the warm
   halo can spread outside the pill. Scoped via :has() so only ancestors
   that actually contain a Huly CTA are affected — unrelated Framer
   sections keep their original clipping. */
:has(.xalo-huly-wrap) {
  overflow: visible !important;
  overflow-x: visible !important;
  overflow-y: visible !important;
}

/* Register --hx, --hedge, --huly-btn-w with @property so Chrome treats
   them as animatable typed properties. Without this, custom properties
   used inside calc() in transform/background don't recompute on change
   because the browser doesn't know they're numeric. */
@property --hx {
  syntax: '<number>';
  inherits: true;
  initial-value: 0.8;
}
@property --hedge {
  syntax: '<number>';
  inherits: true;
  initial-value: 0.6;
}
@property --huly-btn-w {
  syntax: '<length>';
  inherits: true;
  initial-value: 207px;
}

/* Wrap around each Huly CTA. The anchor stays overflow:hidden (so the
   INSIDE glow is clipped cleanly by the pill shape). The .xalo-huly__bleed
   sibling sits OUTSIDE the anchor and paints the warm halo that escapes
   the pill at the left/right ends. Wrap is overflow:visible so the bleed
   can spread.
   z-index 20 + isolation keeps the button + halo ABOVE the hero 3D canvas
   (which sits at body-level z-index 0). Without this the 3D figure's
   legs/feet render in front of the "Learn more" CTA. */
.xalo-huly-wrap {
  position: relative !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  isolation: isolate !important;
  overflow: visible !important;
  line-height: 0 !important;
  vertical-align: middle !important;
  z-index: 20 !important;
}

/* Outside bleed — stretched to fill the wrap (inset: 0) so vertical
   centring is handled by the wrap's box. Using transform: translateY(-50%)
   here was unreliable (some pages had it stripped by post-hydration
   Framer normalisation, pushing the halo 20px below the pill). */
.xalo-huly__bleed {
  position: absolute !important;
  top: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  left: 0 !important;
  inset: 0 !important;
  width: auto !important;
  height: auto !important;
  transform: none !important;
  pointer-events: none !important;
  z-index: -1 !important;
  border-radius: 9999px !important;
}
.xalo-huly__bleed::before {
  content: '' !important;
  position: absolute !important;
  top: 50% !important;
  left: 0 !important;
  /* Tight to the pill — only ~25px of extra spread per side and ~20px
     vertical, so the halo concentrates around the rounded end instead
     of blooming wide. Matches the reference images (halo cups the
     rounded edge rather than floating far away from it). */
  width: calc(var(--huly-btn-w, 207px) + 50px) !important;
  height: calc(100% + 40px) !important;
  transform: translate(calc(var(--hx, 0.82) * var(--huly-btn-w, 207px) - 50%), -50%) !important;
  /* Saturated brand-green core bleeding outward (was warm red-orange). */
  background: radial-gradient(
    closest-side,
    rgba(190, 255, 210, 0.85) 0%,
    rgba(80, 240, 140, 0.60) 24%,
    rgba(40, 200, 100, 0.32) 46%,
    rgba(20, 150, 70, 0.12) 70%,
    rgba(20, 150, 70, 0.00) 94%
  ) !important;
  filter: blur(8px) !important;
  opacity: calc(var(--hedge, 0.65) * 0.95) !important;
  pointer-events: none !important;
  border-radius: 50% !important;
}

:root {
  /* Colour */
  --xalo-black: #000000;
  --xalo-white: #FFFFFF;
  --xalo-green-500: #2FE88A;
  --xalo-green-700: #0F6B3E;
  --xalo-green-900: #052219;
  --xalo-fg-muted: rgba(255, 255, 255, 0.72);
  --xalo-fg-subtle: rgba(255, 255, 255, 0.55);
  --xalo-fg-faint: rgba(255, 255, 255, 0.30);
  --xalo-border: rgba(255, 255, 255, 0.15);
  --xalo-success: #6BFF8B;
  --xalo-error: #FF6B6B;

  /* Typography */
  --xalo-display: 'Space Grotesk', system-ui, -apple-system, sans-serif;
  --xalo-body: 'Inter', system-ui, -apple-system, sans-serif;

  /* Gradients */
  --xalo-signal-gradient:
    radial-gradient(120% 80% at 90% 50%,
      #2FE88A 0%,
      #0F6B3E 25%,
      #052219 55%,
      #000000 80%);
  --xalo-signal-gradient-bottom:
    radial-gradient(100% 60% at 50% 110%,
      #2FE88A 0%,
      #0F6B3E 20%,
      #052219 45%,
      #000000 75%);
}

/* --- Global type overrides ------------------------------------ */

/* Headlines -> Space Grotesk; body -> Inter. Apply at high specificity
   to beat Framer's inline styles. */
h1, h2, h3, h4, h5, h6,
h1 *, h2 *, h3 *, h4 *, h5 *, h6 * {
  font-family: var(--xalo-display) !important;
  font-weight: 500 !important;
  letter-spacing: -0.03em !important;
}
h1, h1 * { letter-spacing: -0.04em !important; }

body, p, li, a, span, input, textarea, button, label {
  font-family: var(--xalo-body);
}

/* Keep italic spans italic (Source Serif 4) untouched — they use inline styles */
span[style*="Source Serif 4"] {
  font-family: 'Source Serif 4', serif !important;
}

/* --- Structural overrides from previous sprints ---------------- */

/* Hide Pricing / Changelog / Contact nav link wrappers */
nav .framer-1jjp9c3,
nav .framer-1d189m,
nav .framer-1tfljoj { display: none !important; }

/* Anti-flash: if a nav anchor text hasn't been updated yet (still says the
   original word), replace it via a CSS generated-content trick so the user
   never sees it. Works by hiding the original text and overlaying our label. */
/* Nav link labels are now rewritten at runtime by the
   mobileNavFixScript in lib/inject-content.ts — it sets the DOM
   textContent to "What We Do" / "Book a Demo" for the two real
   page anchors. That avoids the font-size:0 + ::after trick that
   was doubling labels on mobile (the mobile compact-link rule
   below forces font-size:13px and overrides the 0-trick, so the
   original text + ::after text both render). */

/* Globally hide placeholder-route nav items (Pricing + Changelog pages
   don't exist on this site; any links to /pricing or /changelog in nav
   or footer should never render). Applies across every breakpoint. */
nav [data-framer-name="Link"]:has(a[href="/pricing"]),
nav [data-framer-name="Link"]:has(a[href="/changelog"]),
footer a[href="/pricing"],
footer a[href="/changelog"],
[data-framer-name="Footer"] a[href="/pricing"],
[data-framer-name="Footer"] a[href="/changelog"] {
  display: none !important;
}

/* ============================================================
   Per-page overrides via <html data-xalo-page="..."> stamp
   (set by lib/serve-framer.ts based on the served filename).
   ============================================================ */

/* /what-we-do no longer hosts the Book a Demo form — it lives only on
   /book-a-demo now. The form is injected by scripts/migrate-xalo-copy.mjs
   into <div id="xalo-form-source-product"> (the internal ID is still
   "product" since WP content is still keyed that way). Hide that
   wrapper so the form doesn't appear at the bottom of the page. */
[data-xalo-page="what-we-do"] #xalo-form-source-product {
  display: none !important;
}
/* Also hide the stale upsell CTA section ("The teams that adopt this
   first...") — it's not in the Figma wireframe. */
[data-xalo-page="what-we-do"] section[data-framer-name="CTA"] {
  display: none !important;
}

/* ============================================================
   Figma injected sections — Why Now, Already Deployed, What to Expect
   Injected post-hydration by the boot script in lib/inject-content.ts
   (xaloInjectedSectionsScript) and held in place via MutationObserver.
   Styled to match the home brand: dark bg, green accent, tight type.
   ============================================================ */
.xalo-injected-section {
  position: relative;
  width: 100%;
  background: var(--xalo-black);
  color: var(--xalo-white);
  padding: 112px 80px;
  box-sizing: border-box;
  z-index: 2;
  font-family: var(--xalo-body);
}
.xalo-injected-section::before {
  /* Subtle radial glow to match the home-page atmosphere. */
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 60% 50% at 50% 0%, rgba(47, 232, 138, 0.08), transparent 70%);
}
.xalo-injected-wrap {
  max-width: 1280px;
  margin: 0 auto;
  position: relative;
}
.xalo-injected-eyebrow {
  font-family: var(--xalo-body);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--xalo-green-500);
  margin: 0 0 18px;
}
.xalo-injected-head {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
  gap: 48px;
  align-items: end;
  margin-bottom: 56px;
}
.xalo-injected-head--stacked {
  grid-template-columns: 1fr;
  gap: 16px;
  align-items: start;
}
.xalo-injected-headline {
  font-family: var(--xalo-display);
  font-weight: 500;
  font-size: 54px;
  line-height: 1.06;
  letter-spacing: -0.03em;
  color: var(--xalo-white);
  margin: 0;
}
.xalo-injected-lede {
  font-family: var(--xalo-body);
  font-size: 18px;
  line-height: 1.6;
  color: var(--xalo-fg-muted);
  margin: 0;
  max-width: 640px;
}
.xalo-injected-rows {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--xalo-border);
}
.xalo-injected-row {
  display: grid;
  grid-template-columns: 60px minmax(0, 360px) minmax(0, 1fr);
  gap: 40px;
  align-items: start;
  padding: 36px 0;
  border-bottom: 1px solid var(--xalo-border);
}
.xalo-injected-row--no-num {
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
}
.xalo-injected-num {
  font-family: var(--xalo-body);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.12em;
  color: var(--xalo-fg-subtle);
  margin: 3px 0 0;
}
.xalo-injected-row-title {
  font-family: var(--xalo-display);
  font-weight: 500;
  font-size: 26px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--xalo-white);
  margin: 0;
}
.xalo-injected-row-copy {
  font-family: var(--xalo-body);
  font-size: 17px;
  line-height: 1.62;
  color: var(--xalo-fg-muted);
  margin: 0;
}
.xalo-injected-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.xalo-injected-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--xalo-border);
  border-radius: 8px;
  padding: 40px 32px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.xalo-injected-card-num {
  font-family: var(--xalo-display);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  color: var(--xalo-green-500);
  margin: 0 0 4px;
}
.xalo-injected-card-title {
  font-family: var(--xalo-display);
  font-weight: 500;
  font-size: 20px;
  line-height: 1.3;
  color: var(--xalo-white);
  margin: 0;
}
.xalo-injected-card-copy {
  font-family: var(--xalo-body);
  font-size: 15px;
  line-height: 1.65;
  color: var(--xalo-fg-muted);
  margin: 0;
}

/* Mobile + iPad-portrait tuning */
@media (max-width: 809.98px) {
  .xalo-injected-section { padding: 72px 20px; }
  .xalo-injected-head {
    grid-template-columns: 1fr;
    gap: 18px;
    margin-bottom: 36px;
  }
  .xalo-injected-headline { font-size: 34px; }
  .xalo-injected-row {
    grid-template-columns: 1fr;
    gap: 10px;
    padding: 28px 0;
  }
  .xalo-injected-row--no-num { grid-template-columns: 1fr; }
  .xalo-injected-row-title { font-size: 22px; }
  .xalo-injected-row-copy { font-size: 15px; }
  .xalo-injected-cards {
    grid-template-columns: 1fr;
  }
  .xalo-injected-card { padding: 28px 24px; }
  .xalo-injected-card-num { font-size: 36px; }
  .xalo-injected-card-title { font-size: 18px; }
}

/* /book-a-demo: the Figma spec has Hero → What to Expect → Form.
   The existing Framer page inherited an FAQs section + a redundant
   bottom "See the platform live." CTA from the old contact template
   which the Figma drops — hide both. */
[data-xalo-page="book-a-demo"] section[data-framer-name="FAQ"],
[data-xalo-page="book-a-demo"] section[data-framer-name="FAQs"],
[data-xalo-page="book-a-demo"] section[data-framer-name="CTA"] {
  display: none !important;
}
/* Same trick for the header CTA button during the flash window.
   Match BOTH the pre-script state (a[data-framer-name="Button 3"] —
   server-rendered, before inject-content.js clones + adds .xalo-glow)
   AND the post-script state (a.xalo-glow). Otherwise the heavier the
   backdrop-filter, the slower the first paint, and the longer the
   original Framer ~17px text sits on screen before the 14px ::after
   takes over. */
nav a[data-framer-name="Button 3"] [data-framer-name="Get started"] p,
nav a[data-framer-name="Button 3"] [data-framer-name="Get in touch"] p,
nav a.xalo-glow [data-framer-name="Get started"] p,
nav a.xalo-glow [data-framer-name="Get in touch"] p {
  font-size: 0 !important;
}
nav a[data-framer-name="Button 3"] [data-framer-name="Get started"] p::after,
nav a[data-framer-name="Button 3"] [data-framer-name="Get in touch"] p::after,
nav a.xalo-glow [data-framer-name="Get started"] p::after,
nav a.xalo-glow [data-framer-name="Get in touch"] p::after {
  content: "Book a Demo" !important;
  font-size: 14px !important;
  /* Give the pseudo text a real line-height so it vertically centres in
     the pill. Framer's p carries line-height: 0 (font-size-0 trick), so
     without this the glyphs draw above the pill's midline. */
  line-height: 1 !important;
  display: inline-block !important;
  vertical-align: middle !important;
  color: inherit !important;
  font-family: var(--xalo-body) !important;
  font-weight: 500 !important;
}
/* Make the ::after wrap properly inside the pill: flex-centre its parent p
   so "Get in touch" sits on the navMid regardless of the p's own
   line-height quirks. */
nav a[data-framer-name="Button 3"] [data-framer-name="Get started"] p,
nav a[data-framer-name="Button 3"] [data-framer-name="Get in touch"] p,
nav a[data-framer-name="Button 3"] p,
nav a.xalo-glow [data-framer-name="Get started"] p,
nav a.xalo-glow [data-framer-name="Get in touch"] p,
nav a.xalo-glow p {
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  line-height: 1 !important;
  height: 14px !important;
}

/* --- Nav anti-flash: debounced reveal via transition-delay --------
   The SSR HTML already contains `data-xalo-logo-link="1"` and
   `href="/product"` (the server-side inject-content pre-runs the logo
   swap + About rewrite). Only `data-xalo-cloned="1"` is added
   client-side (after the CTA anchor is cloned to strip Framer Motion
   listeners).
   But Framer React then hydrates, and because its virtual DOM doesn't
   contain any of our server-injected attributes, hydration briefly
   strips them — the logo reverts to the MonoAI/GenAI original, the
   About link reverts to "About", etc. Our client script reapplies
   everything within one rAF, but the user still sees a frame or two of
   the reverted state in between.
   Fix: debounce the reveal using CSS transition-delay. The nav stays
   at opacity 0 by default; when :has(a[data-xalo-cloned="1"]) matches,
   a 450 ms delayed transition to opacity 1 fires. If :has ever
   stops matching during that window (e.g. hydration strips the marker),
   the default rule re-applies instantly with no delay, and the 450 ms
   clock restarts once :has matches again. Net effect: the nav only
   becomes visible once the markers have been stable for ≥450 ms — long
   enough for hydration + reapply to settle.
   Safety fallback: a CSS animation flips opacity to 1 at 1500 ms no
   matter what, so a persistent script failure doesn't leave the nav
   invisible forever. */
nav[data-framer-name="Desktop"] {
  opacity: 0;
  transition: opacity 0s linear 0s;
  animation: xalo-nav-safety-reveal 0s linear 1500ms forwards;
}
nav[data-framer-name="Desktop"]:has(a[data-xalo-cloned="1"]) {
  opacity: 1;
  transition: opacity 200ms ease 450ms;
}
@keyframes xalo-nav-safety-reveal {
  to { opacity: 1; }
}

/* --- Floating liquid-glass pill nav ---------------------------- */
/* Framer renders the nav inside `.framer-1bmvd1p-container`
   (data-framer-name="Nav Bar") as `position: absolute; top: 0`. We
   flip it to `fixed` (so it sticks on scroll) and make it a transparent
   flex wrapper that centres the actual <nav> pill horizontally.
   pointer-events:none on the wrapper so it doesn't eat clicks outside the
   pill bounds; the pill re-enables them. */
[data-framer-name="Nav Bar"],
.framer-1bmvd1p-container {
  position: fixed !important;
  top: 14px !important;
  left: 0 !important;
  right: 0 !important;
  width: 100% !important;
  max-width: 100% !important;
  min-width: 0 !important;
  height: auto !important;
  z-index: 50 !important;
  display: flex !important;
  align-items: flex-start !important;
  justify-content: center !important;
  pointer-events: none !important;
  background: transparent !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border: none !important;
  box-shadow: none !important;
  transform: none !important;
}

/* The pill itself. The SVG filter defined in lib/inject-content.ts
   (#xalo-liquid-glass) warps the backdrop via feTurbulence +
   feDisplacementMap — that's the "liquid" refraction. The blur +
   saturate on top of it is standard frosted-glass. The linear gradient
   adds the Apple-style top highlight sheen. Inset shadows trace the
   top/bottom edges like refracted light. */
nav[data-framer-name="Desktop"] {
  /* 30% narrower than before (was min(50vw, 920px)). */
  width: min(35vw, 644px) !important;
  max-width: min(35vw, 644px) !important;
  min-width: 0 !important;
  height: auto !important;
  min-height: 0 !important;
  /* Equal padding on all four sides so the "Get in touch" button has the same
     breathing room top/right/bottom (logo on the left uses margin to match). */
  padding: 6px !important;
  margin: 0 auto !important;
  border-radius: 999px !important;
  pointer-events: auto !important;
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.18) 0%,
      rgba(255, 255, 255, 0.04) 45%,
      rgba(255, 255, 255, 0.08) 100%),
    rgba(14, 18, 16, 0.32) !important;
  backdrop-filter: url(#xalo-liquid-glass) blur(14px) saturate(180%) !important;
  -webkit-backdrop-filter: blur(14px) saturate(180%) !important; /* Safari fallback */
  border: 1px solid rgba(255, 255, 255, 0.16) !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.34),
    inset 0 -1px 0 rgba(0, 0, 0, 0.20),
    inset 0 0 24px rgba(255, 255, 255, 0.04),
    0 10px 32px rgba(0, 0, 0, 0.38),
    0 2px 8px rgba(0, 0, 0, 0.22) !important;
  isolation: isolate;
  transition: box-shadow 240ms ease, background 240ms ease !important;
}

/* Ensure the inner container keeps flex layout without any hardcoded
   Framer heights fighting the new compact pill. */
[data-framer-name="Nav Bar"] nav [data-framer-name="Container"] {
  height: auto !important;
  min-height: 0 !important;
}

/* Hide Mobile Closed / Mobile Open nav variants at desktop widths so they
   don't render as a second empty pill alongside Desktop. */
@media (min-width: 810px) {
  [data-framer-name="Nav Bar"] > nav[data-framer-name="Mobile Closed"],
  [data-framer-name="Nav Bar"] > nav[data-framer-name="Mobile Open"] {
    display: none !important;
  }
}

/* Shrink the "Get in touch" pill button inside the nav so it fits the
   compact pill (the default .xalo-glow padding of 14px/28px is sized for
   hero CTAs). Scoped by nav-ancestor so the hero CTA button elsewhere
   on the page keeps its original size.
   Also matches the pre-script state (data-framer-name="Button 3") so
   sizing is correct from the first paint — otherwise heavy
   backdrop-filter can delay the clone step and flash default 17px text. */
[data-framer-name="Nav Bar"] a.xalo-glow,
[data-framer-name="Nav Bar"] a[data-framer-name="Button 3"] {
  /* Equal breathing room top/bottom AND sides around the text. */
  padding: 12px 20px !important;
  font-size: 13px !important;
  line-height: 1 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  height: auto !important;
  min-height: 0 !important;
  box-sizing: border-box !important;
}
/* Unlock Framer's hardcoded inline width / height on the button's wrapper
   divs so the pill actually grows to fit padding. Targets the anchor's
   wrapper (framer-1b1aom6-container lineage) regardless of data-framer-name
   variations per page. Matches both pre-script (Button 3) and post-script
   (.xalo-glow) anchor states. */
[data-framer-name="Nav Bar"] [class*="framer-"][class*="-container"]:has(a.xalo-glow),
[data-framer-name="Nav Bar"] [class*="framer-"][class*="-container"]:has(a[data-framer-name="Button 3"]),
[data-framer-name="Nav Bar"] [data-framer-name="Button 2"],
[data-framer-name="Nav Bar"] [data-framer-name="Button"]:has(a.xalo-glow),
[data-framer-name="Nav Bar"] [data-framer-name="Button"]:has(a[data-framer-name="Button 3"]),
[data-framer-name="Nav Bar"] *:has(> a.xalo-glow),
[data-framer-name="Nav Bar"] *:has(> a[data-framer-name="Button 3"]) {
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  height: auto !important;
  min-height: 0 !important;
  flex: 0 0 auto !important;
  display: inline-flex !important;
  align-items: center !important;
}

/* Nav layout: Logo left, [Links + Button] together on the right.
   Original flex uses justify-content:space-between between 3 siblings which
   puts Links in the middle. Switch to margin-left:auto on Links so it joins
   the Button group on the right. */
nav [data-framer-name="Container"] {
  display: flex !important;
  align-items: center !important;
}
nav [data-framer-name="Container"] > * {
  align-self: center !important;
}
/* Tighten line-heights so glyph bboxes match their visible centres and
   the logo / links / button all sit on the same optical line. */
nav [data-framer-name="Logo"],
nav [data-framer-name="Logo"] *,
nav [data-framer-name="Links"],
nav [data-framer-name="Links"] *,
nav [data-framer-name="Button"] a.xalo-glow,
nav [data-framer-name="Button 2"] a.xalo-glow {
  line-height: 1 !important;
}
/* Logo centring: the SVG's bbox extends up to include the 'l' ascender,
   but the visual weight (x, a, o) sits lower. Measured: X letter centre
   is at y=40.5 while navMid=36.5. Nudge the logo up 4px so the X letter
   sits on the nav's centre line. */
nav [data-framer-name="Logo"] {
  display: flex !important;
  align-items: center !important;
  justify-content: flex-start !important;
  transform: translateY(-4px) !important;
}
nav [data-framer-name="Logo"] p,
nav [data-framer-name="Logo"] h1,
nav [data-framer-name="Logo"] div {
  margin: 0 !important;
  display: flex !important;
  align-items: center !important;
}
nav [data-framer-name="Container"] > [data-framer-name="Logo"] {
  margin-right: auto !important;
}
nav [data-framer-name="Container"] > [data-framer-name="Links"] {
  margin-left: auto !important;
  margin-right: 16px !important;
  display: flex !important;
  align-items: center !important;
}

/* Kill Framer's hover animation on remaining nav text links (blue arrow /
   underline that "flashed" on hover and click). Keep a subtle colour shift
   so the link still feels interactive. */
nav [data-framer-name="Link"] a,
nav [data-framer-name="Link"] a * {
  transition: color 0.15s ease !important;
  text-decoration: none !important;
  background-image: none !important;
}
nav [data-framer-name="Link"] a::before,
nav [data-framer-name="Link"] a::after,
nav [data-framer-name="Link"] a > *::before,
nav [data-framer-name="Link"] a > *::after {
  display: none !important;
  content: none !important;
}
nav [data-framer-name="Link"] a:hover,
nav [data-framer-name="Link"] a:hover * {
  color: var(--xalo-green-500) !important;
}
/* Hide any highlight/annotation overlays Framer paints above nav links */
nav [data-framer-name="Link"] svg,
nav [data-framer-name="Link"] [class*="highlight"],
nav [data-framer-name="Link"] [class*="annotation"] {
  display: none !important;
}

/* Footer Pricing / Changelog wrappers */
.framer-i4gqag,
.framer-1hoe7ux { display: none !important; }

/* --- Footer cleanup -------------------------------------------- */

/* The primary-CTA tagging pass (see index.html ~line 565) adds .xalo-glow
   to ANY anchor whose trimmed text is "Get in touch". That's right for the
   nav header but wrong for the footer, where we want it to read like the
   other plain footer links (e.g. "What We Do", "Privacy Policy").
   Scope: strip all pill styling ONLY for the footer occurrence. */
footer a.xalo-glow,
[data-framer-name="Footer"] a.xalo-glow {
  background: transparent !important;
  color: inherit !important;
  padding: 0 !important;
  border: none !important;
  border-radius: 0 !important;
  font-weight: inherit !important;
  font-size: inherit !important;
  letter-spacing: inherit !important;
  box-shadow: none !important;
  display: inline !important;
  isolation: auto !important;
  overflow: visible !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
footer a.xalo-glow::before,
footer a.xalo-glow::after,
[data-framer-name="Footer"] a.xalo-glow::before,
[data-framer-name="Footer"] a.xalo-glow::after {
  content: none !important;
  display: none !important;
  background: none !important;
  box-shadow: none !important;
}
footer a.xalo-glow > *,
footer a.xalo-glow p,
[data-framer-name="Footer"] a.xalo-glow > *,
[data-framer-name="Footer"] a.xalo-glow p {
  color: inherit !important;
  font-weight: inherit !important;
  background: transparent !important;
}

/* Hide social links Instagram / YouTube / Twitter-X.  Keep LinkedIn.
   The footer renders each link as a list item; the anchor is a child of
   the list wrapper, so `:has(> a)` scopes the hide to just that row. */
footer a[href*="instagram.com"],
footer a[href*="youtube.com"],
footer a[href*="//x.com"],
footer a[href*="twitter.com"],
[data-framer-name="Footer"] a[href*="instagram.com"],
[data-framer-name="Footer"] a[href*="youtube.com"],
[data-framer-name="Footer"] a[href*="//x.com"],
[data-framer-name="Footer"] a[href*="twitter.com"] {
  display: none !important;
}
/* Collapse the outer column-row wrapper for each hidden social link.
   Structure is: column > div.row-wrapper > <p> > <a>. Without this rule
   the inner <p> is hidden but the row-wrapper still contributes to the
   Connect column's 16 px row-gap, stacking ~64 px of empty space above
   LinkedIn. Matching `div:has(> p > a[…])` scopes the hide to exactly
   that row-wrapper, not any higher ancestor. */
footer div:has(> p > a[href*="instagram.com"]),
footer div:has(> p > a[href*="youtube.com"]),
footer div:has(> p > a[href*="//x.com"]),
footer div:has(> p > a[href*="twitter.com"]),
[data-framer-name="Footer"] div:has(> p > a[href*="instagram.com"]),
[data-framer-name="Footer"] div:has(> p > a[href*="youtube.com"]),
[data-framer-name="Footer"] div:has(> p > a[href*="//x.com"]),
[data-framer-name="Footer"] div:has(> p > a[href*="twitter.com"]) {
  display: none !important;
}

/* "Get template" / Polar buy button — also hide the container wrapper
   so it doesn't push past the viewport on mobile (fixed-position,
   baked width 140px, left 282px = right 422px on a 375px viewport). */
a[href*="buy.polar.sh"],
.framer-8Ocpt,
.framer-19yonik-container,
[data-framer-name="Buy Button"] { display: none !important; }

/* Hero CTAs: hide the first button ("Get in touch") and centre the
   remaining "Learn more" button. The nav-bar "Get in touch" stays — this
   selector is scoped to the Header section's Buttons container only. */
section[data-framer-name="Header"] [data-framer-name="Buttons"] > *:first-child {
  display: none !important;
}
section[data-framer-name="Header"] [data-framer-name="Buttons"] {
  justify-content: center !important;
}

/* Pre-footer CTA section: hide both buttons ("Get in touch" + "Learn
   more") — they duplicate the nav CTA and aren't needed. Scope to the
   CTA section only, so nav + footer CTAs are untouched. */
section[data-framer-name="CTA"] [data-framer-name="Buttons"] {
  display: none !important;
}

/* Framer badge */
#__framer-badge-container,
.__framer-badge,
[data-framer-badge],
.framer-badge { display: none !important; }

/* Hero pill "Already deployed…" */
section[data-framer-name="Header"] .framer-1kig8y7-container,
section[data-framer-name="Header"] [data-framer-name="Pill"] { display: none !important; }

/* --- Logo lockup ---------------------------------------------- */

/* Hide ONLY the old flower-icon SVG and the MONO-AI text wrapper. */
[data-framer-name="Logo"] [data-framer-component-type="SVG"]:not(.xalo-logo-svg),
[data-framer-name="Logo"] [data-framer-name="GenAI"] {
  display: none !important;
}
/* Hide duplicate Logo breakpoint variants (marked by our client script) */
[data-framer-name="Logo"][data-xalo-hide="1"] {
  display: none !important;
}

/* Our injected SVG logo — both nav and footer. */
.xalo-logo-svg {
  height: 26px; /* +10% again (was 24px) */
  width: auto;
  color: var(--xalo-white);
  vertical-align: middle;
}

/* Tighten the logo lockup inside the nav: no extra padding/margin around the
   Logo wrapper, so it sits flush to the pill's 6px inner padding and matches
   the "Get in touch" button's distance from the right edge. */
[data-framer-name="Nav Bar"] [data-framer-name="Logo"],
[data-framer-name="Nav Bar"] [data-framer-name="Logo"] > * {
  padding: 0 !important;
  margin: 0 !important;
  gap: 0 !important;
}
[data-framer-name="Nav Bar"] [data-framer-name="Logo"] {
  margin-right: auto !important;
  padding-left: 6px !important; /* balances the button's right-side breathing room */
}
footer .xalo-logo-svg,
[data-framer-name="Footer"] .xalo-logo-svg {
  height: 28px;
}
/* Show only the FIRST logo per nav / per footer. Framer renders multiple
   breakpoint variants but hiding rules were disrupted by our modifications. */
nav [data-framer-name="Logo"] ~ div [data-framer-name="Logo"],
nav [data-framer-name="Logo"] ~ nav [data-framer-name="Logo"],
[data-framer-name="Logo"] ~ [data-framer-name="Logo"] svg.xalo-logo-svg {
  display: none !important;
}
/* Simpler, high-specificity: hide all but the first injected logo svg */
svg.xalo-logo-svg ~ svg.xalo-logo-svg,
[data-framer-name="Logo"]:has(svg.xalo-logo-svg) ~ [data-framer-name="Logo"] svg.xalo-logo-svg {
  display: none !important;
}

/* --- Brand-aligned hero backgrounds --------------------------- */

/* 1) Kill the Framer rainbow/prismatic swirl on hero sections so our
   brand gradient is the only visible background treatment. */
section[data-framer-name="Header"] img,
section[data-framer-name="Header"] video,
section[data-framer-name="Header"] [data-framer-component-type="SVG"]:not(.xalo-logo-svg):not(.xalo-marquee *):not(.xalo-marquee-item *),
section[data-framer-name="Header"] [data-framer-name="Background"],
section[data-framer-name="Header"] [data-framer-name="Image"],
section[data-framer-name="Header"] [data-framer-name="Visual"],
section[data-framer-name="Header"] [style*="background-image"]:not([data-xalo-bg]) {
  display: none !important;
}
/* Re-allow partner-logo SVG divs once they live inside our injected marquee.
   The rule above matches data-framer-component-type="SVG" inside the hero;
   the marquee items are relocated there and we need them visible. */
section[data-framer-name="Header"] .xalo-marquee [data-framer-component-type="SVG"],
section[data-framer-name="Header"] .xalo-marquee-item [data-framer-component-type="SVG"],
section[data-framer-name="Header"] .xalo-marquee-item,
section[data-framer-name="Header"] .xalo-marquee-item * {
  display: inline-block !important;
}
section[data-framer-name="Header"] .xalo-marquee-item svg {
  display: block !important;
}

/* The partner logos were exported with fill:black, and they now sit on a
   black hero. Force them white via currentColor (the svg's computed color
   is already white) so they're actually visible. */
.xalo-marquee-item,
.xalo-marquee-item * {
  color: #fff !important;
}
.xalo-marquee-item svg,
.xalo-marquee-item svg * {
  fill: currentColor !important;
}
.xalo-marquee--in-hero .xalo-marquee-item svg,
.xalo-marquee--in-hero .xalo-marquee-item svg * {
  fill: #ffffff !important;
}
.xalo-marquee--in-hero .xalo-marquee-item {
  opacity: 0.85;
}

/* Also kill the rainbow video that sits OUTSIDE the hero section (absolute
   positioned siblings with framerusercontent video src). */
video[src*="framerusercontent.com"] {
  display: none !important;
}

/* 2) Hero section: pure black canvas. The 3D particle scene is mounted in
   <body> by /xalo-hero-3d.js and visually positioned over this section.
   On the home page we drop the radial gradient (3D scene replaces it).
   On product + contact, the gradient stays. We toggle via a body class
   set by the hero script when the scene mounts. */
section[data-framer-name="Header"] {
  /* Background (color + dot pattern) comes from the site-wide
     `section[class*="framer-"]` rule further down so every section
     matches. Don't re-declare `background:` here or the shorthand
     would wipe the dot image. */
  position: relative;
  isolation: isolate;
  /* Taller hero so the 3D figure can sit above the headline, peachworlds-style */
  min-height: 100vh !important;
}
/* On the home page (body has xalo-hero3d-active), the 3D figure occupies
   the TOP-CENTER portion of the hero, and the headline + CTAs sit BELOW
   it — peachworlds-style. We push the Framer hero content down with top
   padding that's tuned to match peach (figure ends ~50% down the hero). */
/* Previously this padding pushed the headline down to sit below the figure.
   Peach-style: headline layers OVER the figure (canvas is z-index: 0, hero
   content is z-index: 2), so no padding override is needed. */
/* ============================================================
   HARD RULE: the 3D canvas sits at the BOTTOM of the z-stack,
   ALL page content sits above it. Applied site-wide, not just
   to the home hero, so the figure can never occlude text.

   1) Body (and html) paint the base black so the transparent
      canvas shows against black.
   2) Canvas container is z-index: 0 with isolation.
   3) Every Framer section that is a direct child of body gets
      z-index: 2 when xalo-hero3d-active, with transparent
      background so the canvas bleeds through only on the hero.
   ============================================================ */
body.xalo-hero3d-active,
html:has(body.xalo-hero3d-active) {
  /* Longhand so the dot-pattern background-image from the global
     html/body rule isn't wiped by shorthand. */
  background-color: var(--xalo-black) !important;
}
/* Home-page hero: transparent COLOR only (longhand) so the dot
   pattern from the site-wide section rule is preserved. z-index: 1
   puts the hero above the 3D canvas (z: 0) so the dots render on
   top of the particles. Hero content at z: 2 stays above the dots. */
body.xalo-hero3d-active section[data-framer-name="Header"] {
  background-color: transparent !important;
  z-index: 1;
}
/* REACT REBUILD OVERRIDE: the transparency above made the React hero
   read as a DIFFERENT "black" to every other .xr-section — body
   pattern bleeding through at a different density than the section's
   own ::before pattern. For React pages we keep the hero's solid black
   + section ::before dots, and the 3D canvas (transparent particles)
   paints on top. That way every section on the page — including the
   hero — shares the exact same bg + dot pattern. */
html[data-xalo-react-app="1"] body.xalo-hero3d-active section[data-framer-name="Header"] {
  background-color: var(--xalo-black) !important;
}
/* Only touch the section's DIRECT children. Adding `position: relative`
   to deeper Framer wrappers (Content, Title, Container) triggers Framer
   Motion to re-register and its enter-animation gets stuck at the
   starting state (opacity 0.001, translateY 10). */
body.xalo-hero3d-active section[data-framer-name="Header"] > * {
  position: relative;
  z-index: 2;
}

/* ============================================================
   Peach effects pipeline — faked in CSS because EffectComposer
   attenuates our additive point cloud.

   From peach's world panel:
     - Vignette: Normal, 100% opacity
     - Bloom: Screen, 10% opacity (very subtle)
     - Hue Saturation: Normal, 11% (subtle saturation bump)
     - Tone Mapping, Noise, Pixelation, DOF, SSAO — all off
   ============================================================ */
/* Vignette is now handled by the Three.js VignetteNoiseShader pass in the
   post-processing composer (see xalo-hero-3d.js). The old CSS ::after
   overlay doubled up with it and made the corners too dark. */
/* Hue/Saturation bump (peach's Normal 11%) is now handled inside the
   Three.js pipeline by the VignetteNoise + saturated emissive material.
   No CSS filter needed. */
/* Header background — green signal-gradient ::before removed (was causing
   a green flash on page load before the 3D hero initialised). The 3D
   figure provides all the visual interest behind the headline now; the
   header section stays plain black until 3D paints. */
section[data-framer-name="Header"] > * { position: relative; z-index: 1; }

/* ============================================================
   Hero balance — lift the headline + subhead + CTA group so it
   sits higher in the 100vh hero (better balance against the 3D
   figure and bottom marquee). Tighten the gap between the
   subhead and the Learn more button. Also restore the subhead's
   visibility — Framer's inline opacity:0.001 stays because the
   per-word stagger animation only targets spans, but the subhead
   <p> is plain text with no inner spans.
   ============================================================ */
section[data-framer-name="Header"] [data-framer-name="Content"] {
  transform: translateY(-72px);
}
section[data-framer-name="Header"] [data-framer-name="Buttons"] {
  /* Pushes the button halfway down the gap it shares with the
     marquee caption. Paired with the marquee's matching margin-top
     below so the caption stays at its previous Y — only the button
     moves. */
  margin-top: 79px !important;
}
/* The "Already deployed in the Premier League" caption + logos marquee
   is JS-positioned via buttons.getBoundingClientRect().bottom, so any
   net shift on Buttons drags it. Net Buttons shift here is
   (Content -72 px) + (Buttons +38 px) = -34 px upward. Equal-but-opposite
   margin-top pins the marquee at its original Y. */
.xalo-marquee--in-hero {
  /* Paired with the 79px on Buttons above: together they split the
     original 158px vertical span so the button sits centred and the
     caption lands at the same Y as before. */
  margin-top: 79px !important;
}
/* Subhead <p> — animate it in to match the heading word stagger. */
section[data-framer-name="Header"] [data-framer-name="Title"] [data-framer-component-type="RichTextContainer"] > p {
  opacity: 0.001;
  filter: blur(10px);
  transform: translateY(10px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1) 1100ms,
    filter 700ms cubic-bezier(0.22, 1, 0.36, 1) 1100ms,
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1) 1100ms;
  will-change: opacity, filter, transform;
}
section[data-framer-name="Header"].xalo-anim-go [data-framer-name="Title"] [data-framer-component-type="RichTextContainer"] > p {
  opacity: 1 !important;
  filter: blur(0) !important;
  transform: translateY(0) !important;
}

/* 3) Bottom-biased gradient divider on every other section so the page
   rhythm matches the deck. Also applies the huly-style perforated-grid
   pattern in the section background so text/icons naturally sit ABOVE
   the dots (no perforation over typography or imagery).
   Applied to EVERY Framer section — including the Header (hero) and the
   last section — so the page reads as one consistent black backdrop. */
section[class*="framer-"] {
  background-color: var(--xalo-black) !important;
  background-image: radial-gradient(
    rgba(255, 255, 255, 0.08) 1px,
    transparent 1px
  ) !important;
  background-size: 16px 16px !important;
  background-position: 0 0 !important;
}

/* ============================================================
   Huly-style edge-glow halo — applied around the Features
   Illustrations cards row ($3-4B / 20 years / Zero systems).
   Two soft, blurred radial blooms bleed out of the row's edges:
   brand green from the top-left, white from the bottom-right.
   Sits behind the row content (z-index -1) so the cards stay
   crisp on top.
   ============================================================ */
section[data-framer-name="Features Illustrations"] {
  overflow: visible !important;
}

/* Hide all "Get in touch" buttons inside the Features Illustrations
   section — the cards read cleanly without the CTA repeated six times. */
section[data-framer-name="Features Illustrations"] [data-framer-name="Button"] {
  display: none !important;
}
section[data-framer-name="Features Illustrations"] [data-framer-name="Container"] {
  overflow: visible !important;
}
section[data-framer-name="Features Illustrations"] [data-framer-name="Container"] > div:has(> [data-framer-name="3"]) {
  position: relative !important;
  isolation: isolate;
  overflow: visible !important;
}
/* Subtle perimeter rim-light to anchor the halo against the box edge. */
section[data-framer-name="Features Illustrations"] [data-framer-name="Container"] > div:has(> [data-framer-name="3"]) {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    0 0 0 1px rgba(255, 255, 255, 0.06);
  border-radius: 24px;
}

/* Dual conic-gradient halo (huly.io style) — two layered paths rotating
   on the same `--xalo-edge-angle` variable:
     - ::before  = INNER path, tight, bright, hugging the edge
     - ::after   = OUTER path, diffuse, softer, spreading further out
   Synchronised rotation → both halos sit on the same edge at any moment,
   reading as a single neon rim with a wide ambient bloom behind it.
   Two bright regions 180° apart (green-white core + cool-white core) so
   there are always two glowing edges opposite each other. Corners bend
   naturally because the light is defined by angle, not element shape. */
@property --xalo-edge-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

/* INNER halo — brighter, tight against the edge (bleeds ~14 px past). */
section[data-framer-name="Features Illustrations"] [data-framer-name="Container"] > div:has(> [data-framer-name="3"])::before {
  content: '';
  position: absolute;
  inset: -8px;
  border-radius: 32px;           /* container 24 + inset 8 */
  pointer-events: none;
  z-index: -1;
  filter: blur(6px);
  background: conic-gradient(
    from var(--xalo-edge-angle),
    rgba(47,  232, 138, 0.00)   0deg,
    rgba(47,  232, 138, 0.50)  12deg,
    rgba(180, 252, 210, 0.92)  22deg,
    rgba(255, 255, 255, 1.00)  30deg,   /* green-slice hot core */
    rgba(180, 252, 210, 0.92)  38deg,
    rgba(47,  232, 138, 0.50)  48deg,
    rgba(47,  232, 138, 0.00)  62deg,
    rgba(0,   0,   0,   0.00) 180deg,
    rgba(255, 255, 255, 0.00) 180deg,
    rgba(255, 255, 255, 0.38) 192deg,
    rgba(255, 255, 255, 0.82) 202deg,
    rgba(255, 255, 255, 1.00) 210deg,   /* white-slice hot core */
    rgba(255, 255, 255, 0.82) 218deg,
    rgba(255, 255, 255, 0.38) 228deg,
    rgba(255, 255, 255, 0.00) 242deg,
    rgba(0,   0,   0,   0.00) 360deg
  );
  animation: xalo-edge-sweep 22s linear infinite;
}

/* OUTER halo — wider angular spread, lower peak, heavy blur for the
   ambient bleed. Peaks align angularly with the inner layer. */
section[data-framer-name="Features Illustrations"] [data-framer-name="Container"] > div:has(> [data-framer-name="3"])::after {
  content: '';
  position: absolute;
  inset: -70px;
  border-radius: 94px;           /* container 24 + inset 70 */
  pointer-events: none;
  z-index: -2;
  filter: blur(28px);
  background: conic-gradient(
    from var(--xalo-edge-angle),
    rgba(47,  232, 138, 0.10)   0deg,
    rgba(47,  232, 138, 0.22)  10deg,
    rgba(100, 245, 170, 0.36)  20deg,
    rgba(150, 250, 195, 0.52)  30deg,   /* aligned with inner green peak */
    rgba(100, 245, 170, 0.36)  40deg,
    rgba(47,  232, 138, 0.22)  50deg,
    rgba(47,  232, 138, 0.10)  65deg,
    rgba(0,   0,   0,   0.00)  95deg,
    rgba(0,   0,   0,   0.00) 175deg,
    rgba(255, 255, 255, 0.00) 175deg,
    rgba(255, 255, 255, 0.12) 185deg,
    rgba(255, 255, 255, 0.26) 195deg,
    rgba(255, 255, 255, 0.42) 205deg,
    rgba(255, 255, 255, 0.50) 210deg,   /* aligned with inner white peak */
    rgba(255, 255, 255, 0.42) 215deg,
    rgba(255, 255, 255, 0.26) 225deg,
    rgba(255, 255, 255, 0.12) 235deg,
    rgba(255, 255, 255, 0.00) 245deg,
    rgba(0,   0,   0,   0.00) 360deg
  );
  animation: xalo-edge-sweep 22s linear infinite;
}

@keyframes xalo-edge-sweep {
  to { --xalo-edge-angle: 360deg; }
}

/* 4) Kill generic Framer prismatic/rainbow images that are used purely for
   hero background decoration across the site (they're imported from the
   MonoAI template). We allow spotlight / testimonial avatars to remain.
   Heuristic: hide images whose parent section has "Header", "CTA", or
   "Hero" in its Framer name, and which are not avatars inside spotlight. */
section [data-framer-name="Header"] img,
section [data-framer-name="CTA"] img,
section [data-framer-name="Hero"] img:not([data-framer-name="Avatar"] img) {
  display: none !important;
}

/* The Ready-to-automate / final-CTA block (.framer-*, data-framer-name="CTA")
   currently has a rainbow background; apply the signal gradient instead. */
[data-framer-name="CTA"] {
  background-color: var(--xalo-black) !important;
  position: relative;
  isolation: isolate;
}
[data-framer-name="CTA"]::before {
  content: '';
  position: absolute;
  /* Float the glow up off the section's bottom edge so the brightest
     part of the gradient (0% stop) doesn't slam into the footer. A
     generous 140px buffer of plain dot-grid below the glow lets the
     eye read the fade-off as a soft horizon, not a cut. */
  bottom: 140px;
  /* Span well past the section edges so the horizontal mask falloff
     lands off-screen and nothing reads as a cut. */
  left: -40%;
  right: -40%;
  height: 70%;
  /* Vertical gradient = a luminous horizon.
       - very bottom: almost-white green core (like stage lighting
         hitting the floor)
       - quick ramp into full brand green
       - long soft fade to transparent upward
     No radial at this stage — the horizontal shape comes from the
     mask-image below, which is what lets the *top* of the bloom
     feather into the dot-grid instead of painting a solid stripe. */
  background-image: linear-gradient(to top,
      rgba(0, 0, 0, 0) 0%,
      rgba(140, 248, 185, 0.55) 3%,
      rgba(220, 255, 230, 0.98) 7%,
      rgba(140, 248, 185, 0.95) 10%,
      #2FE88A 14%,
      rgba(47, 232, 138, 0.80) 22%,
      rgba(20, 140, 80, 0.55) 34%,
      rgba(15, 107, 62, 0.32) 50%,
      rgba(5, 34, 25, 0.14) 70%,
      rgba(0, 0, 0, 0) 90%);
  /* Horizontal shaping: a wide radial at bottom-center with a long
     soft tail. Feathers the linear gradient so the glow tapers off
     at the left/right before reaching the extended edges of the box. */
  -webkit-mask-image: radial-gradient(65% 100% at 50% 100%,
      #000 10%,
      rgba(0, 0, 0, 0.92) 30%,
      rgba(0, 0, 0, 0.65) 55%,
      rgba(0, 0, 0, 0.25) 75%,
      rgba(0, 0, 0, 0) 92%);
  mask-image: radial-gradient(65% 100% at 50% 100%,
      #000 10%,
      rgba(0, 0, 0, 0.92) 30%,
      rgba(0, 0, 0, 0.65) 55%,
      rgba(0, 0, 0, 0.25) 75%,
      rgba(0, 0, 0, 0) 92%);
  pointer-events: none;
  z-index: 0;
  /* Additive blend so the dot-grid bg stays readable underneath and
     the glow never covers dots, it only brightens them. */
  mix-blend-mode: screen;
  opacity: 0.85;
  /* Breath/pulsate cycle. 14s, heavy easing, opacity does the work
     (horizon + brightness), not position — matches the quiet,
     dreamy feel of the reference. */
  animation: xalo-cta-glow-breath 14s ease-in-out infinite;
  will-change: opacity, transform;
}
@keyframes xalo-cta-glow-breath {
  0%   { opacity: 0.32; transform: scaleX(0.96); }
  45%  { opacity: 0.95; transform: scaleX(1.02); }
  55%  { opacity: 0.95; transform: scaleX(1.02); }
  100% { opacity: 0.32; transform: scaleX(0.96); }
}
@media (prefers-reduced-motion: reduce) {
  [data-framer-name="CTA"]::before { animation: none; opacity: 0.7; transform: none; }
}
[data-framer-name="CTA"] > * { position: relative; z-index: 1; }

/* 5) Green accent on numbered tags & eyebrow pills */
[data-framer-name="Numbers"] p,
.xalo-eyebrow {
  color: var(--xalo-green-500) !important;
}

/* 6) Primary CTA hover: no ring, no shadow — the only hover effect is the
   green mouse-tracking glow behind the pill (see `.xalo-glow::after`). */

/* 7) Tighter body type (Inter 500 at 16/1.5 matches deck) */
p:not(h1 p):not(h2 p):not(h3 p):not(h4 p):not(h5 p):not(h6 p) {
  font-family: var(--xalo-body) !important;
}

/* --- Global ensure black canvas -------------------------------
   Paint the dot pattern all the way down on <html>/<body> so any
   uncovered gap between sections, or any section that doesn't
   match the `section[class*="framer-"]` selector, still shows the
   same dots. This kills the "black strip with no dots" boundaries.

   IMPORTANT: the radius/alpha/grid MUST match the `.xr-section::before`
   rule in xalo-react.css (1px dot, 0.04 alpha, 14×14 grid), otherwise
   the React hero — whose background goes transparent when the 3D
   particle canvas mounts (`body.xalo-hero3d-active`) — shows the body
   pattern through, and reads as a DIFFERENT black to every other
   section (which shows its own `.xr-section::before` pattern instead).
   Previously this was a much denser 4×4 grid, which made the hero feel
   "grainier" than the rest of the page. */
html, body {
  background-color: var(--xalo-black) !important;
  background-image: radial-gradient(
    rgba(255, 255, 255, 0.08) 1px,
    transparent 1px
  ) !important;
  background-size: 16px 16px !important;
  background-position: 0 0 !important;
}

/* --- Buttons -------------------------------------------------- */

/* Primary CTA (white pill) — keep Framer's style but swap tracking/family */
a.xalo-glow,
a[data-framer-name="Buy Button"] {
  font-family: var(--xalo-body) !important;
  font-weight: 500 !important;
  letter-spacing: 0 !important;
}

/* --- Form overrides (contact page Framer form) ---------------- */

section.framer-g960bz .framer-1ao5ral { display: none !important; }

/* --- Product-page surgical overrides (from earlier sprint) ---- */

/* Hide the odometer stats + stock photo inside Features Walkthrough */
section.framer-1t9m0xz .framer-1nxu5c1,
section.framer-1t9m0xz img,
section.framer-1t9m0xz [data-framer-name="Visual"],
section.framer-1t9m0xz [data-framer-name="Image"] { display: none !important; }

/* Hide team-member portrait images on product multi-sport section */
section.framer-1f05fca img,
section.framer-1f05fca [data-framer-name="Photo"],
section.framer-1f05fca [data-framer-name="Image"],
section.framer-1f05fca [data-framer-name="Visual"] { display: none !important; }

/* --- Xalo form (injected on /contact + /product) -------------- */
/* Styles for the injected form live in scripts/xalo-form.mjs */

/* ============================================================
   Xalo glow pill button — Huly-inspired, signal-green halo
   Apply by adding the `xalo-glow` class to any anchor/button.
   We also auto-apply to the existing Framer header CTA
   (data-framer-name="Button 3") site-wide.
   ============================================================ */

/* ============================================================
   Xalo pill button — white pill, STAYS white always, with a
   soft signal-green glow that tracks the mouse cursor.
   - Button fill never changes on hover (no white→black flip).
   - No size change, no scale, no transform.
   - Only the ::before glow responds to the cursor position.
   ============================================================ */

.xalo-glow {
  /* Cursor-tracking vars set by JS. --mx/--my are px from the button's
     top-left, --glow is 0..1 for halo opacity (eased on enter/leave). */
  --mx: 50%;
  --my: 50%;
  --glow: 0;
  --xalo-glow-rgb: 47, 232, 138;
}

.xalo-glow,
a.xalo-glow {
  position: relative;
  /* Solid black pill with a bright green outline — matches James' reference
     sketch. The outline is a soft green ring that also gets a faint halo
     so it reads as "glowing" without overwhelming the copy. */
  background: #000000 !important;
  background-color: #000000 !important;
  color: #ffffff !important;
  border: 1px solid rgba(47, 232, 138, 0.45) !important;
  box-shadow: none !important;
  border-radius: 999px !important;
  text-decoration: none !important;
  cursor: pointer;
  /* Kill Framer's hardcoded inline width so padding dictates button size. */
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  flex: 0 0 auto !important;
  /* overflow VISIBLE — glow must bleed OUTSIDE the pill at the edges. */
  overflow: visible !important;
  /* Own stacking context so ::after z-index:-1 stays behind the button
     but not behind the page underneath. */
  isolation: isolate;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  /* Kill every Framer hover transition so nothing moves / resizes / fades. */
  transition: none !important;
  transform: none !important;
  animation: none !important;
}

/* HARD-lock every interaction state — no colour flip, no scale, no shadow,
   no transform, no font-size change, no padding change. The ONLY thing
   that changes on hover is the ::after glow's opacity (see below).
   Framer swaps variant classes on hover via React (not CSS :hover), so we
   also target descendants, containers, AND the ancestor wrapper so variant-
   driven transform/translate/scale can't move the pill. */
.xalo-glow,
.xalo-glow *,
a.xalo-glow,
a.xalo-glow *,
.xalo-glow:hover,
.xalo-glow:hover *,
.xalo-glow:focus,
.xalo-glow:focus-visible,
.xalo-glow:active,
a.xalo-glow:hover,
a.xalo-glow:hover *,
a.xalo-glow:focus,
a.xalo-glow:focus-visible,
a.xalo-glow:active {
  transition: none !important;
  transition-property: none !important;
  transition-duration: 0s !important;
  transform: none !important;
  translate: none !important;
  scale: 1 !important;
  rotate: none !important;
  animation: none !important;
  animation-name: none !important;
  will-change: auto !important;
}

/* Lock hover styles explicitly (in case Framer's variant swap attempts
   to change these via class swap). */
.xalo-glow:hover,
.xalo-glow:focus,
.xalo-glow:focus-visible,
.xalo-glow:active,
a.xalo-glow:hover,
a.xalo-glow:focus,
a.xalo-glow:focus-visible,
a.xalo-glow:active {
  background: #000000 !important;
  background-color: #000000 !important;
  color: #ffffff !important;
  border: 1px solid rgba(47, 232, 138, 0.45) !important;
  box-shadow: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* Also kill transitions on the Button container wrappers that Framer
   uses to host Motion-driven animations for variant swaps. These are
   direct ancestors of .xalo-glow inside the nav/hero/footer. */
[data-framer-name="Button"]:has(> * .xalo-glow),
[data-framer-name="Button"]:has(> * > .xalo-glow),
[data-framer-name="Button"]:has(.xalo-glow),
[data-framer-name="Buttons"]:has(.xalo-glow),
*:has(> a.xalo-glow) {
  transform: none !important;
  translate: none !important;
  scale: 1 !important;
  transition: none !important;
  animation: none !important;
}

/* Lock text — same colour, same size, same weight, always. */
.xalo-glow > *,
.xalo-glow *,
.xalo-glow p,
.xalo-glow span,
a.xalo-glow > *,
a.xalo-glow *,
a.xalo-glow p,
a.xalo-glow span,
.xalo-glow:hover > *,
.xalo-glow:hover p,
a.xalo-glow:hover > *,
a.xalo-glow:hover p {
  color: #ffffff !important;
  background: transparent !important;
  transform: none !important;
  transition: none !important;
}

/* ============================================================
   "Wave" glow — continuously sweeping halo behind the pill.
   Ported from simeydotme's BaeZJqd (WAVE variant).
   - Solid black pill sits on top (no change to fill).
   - ::after is a wide green gradient behind the pill that
     translates left→right forever, simulating light moving
     across the button edges.
   - Heavy blur + mask to glow OUTSIDE the pill.
   ============================================================ */
.xalo-glow::after,
a.xalo-glow::after {
  content: '' !important;
  position: absolute !important;
  /* Extend well beyond the pill so the halo is visible OUTSIDE the
     button edges. Framer resets top/left to 0 on anchor pseudos, so
     we force every side. */
  top: -20px !important;
  right: -40px !important;
  bottom: -20px !important;
  left: -40px !important;
  inset: -20px -40px !important;
  z-index: -1 !important;
  pointer-events: none;
  display: block !important;
  border-radius: 999px !important;
  /* 3-stop linear gradient — transparent → brand green → transparent.
     background-size 300% 100% + animated background-position slides
     the green band across horizontally. */
  background: linear-gradient(
    90deg,
    rgba(var(--xalo-glow-rgb), 0) 0%,
    rgba(var(--xalo-glow-rgb), 0) 25%,
    rgba(var(--xalo-glow-rgb), 0.95) 50%,
    rgba(var(--xalo-glow-rgb), 0) 75%,
    rgba(var(--xalo-glow-rgb), 0) 100%
  ) !important;
  background-size: 300% 100% !important;
  background-position: 0% 50%;
  filter: blur(18px) !important;
  opacity: 0.75 !important;
  animation: xalo-wave 3.2s linear infinite !important;
  transition: opacity 240ms ease-out !important;
}

/* Secondary inner ring — tighter, faster, reversed direction. Gives the
   "multi-wave" shimmer from the reference pen. */
.xalo-glow::before,
a.xalo-glow::before {
  content: '' !important;
  position: absolute !important;
  top: -6px !important;
  right: -12px !important;
  bottom: -6px !important;
  left: -12px !important;
  inset: -6px -12px !important;
  z-index: -1 !important;
  pointer-events: none;
  display: block !important;
  border-radius: 999px !important;
  background: linear-gradient(
    90deg,
    rgba(var(--xalo-glow-rgb), 0) 0%,
    rgba(var(--xalo-glow-rgb), 0.85) 50%,
    rgba(var(--xalo-glow-rgb), 0) 100%
  ) !important;
  background-size: 250% 100% !important;
  background-position: 100% 50%;
  filter: blur(6px) !important;
  opacity: 0.55 !important;
  animation: xalo-wave-reverse 2.6s linear infinite !important;
}

/* Hover — brighten the halo (no size, no transform change). */
.xalo-glow:hover::after,
a.xalo-glow:hover::after { opacity: 1 !important; }
.xalo-glow:hover::before,
a.xalo-glow:hover::before { opacity: 0.85 !important; }

@keyframes xalo-wave {
  0%   { background-position:   0% 50%; }
  100% { background-position: 200% 50%; }
}
@keyframes xalo-wave-reverse {
  0%   { background-position: 200% 50%; }
  100% { background-position:   0% 50%; }
}

/* Reference site (monoai.framer.website) keeps every CTA as a white pill with the
   green mouse-tracking glow — no colour flip, no scale, no background animation.
   We used to force the Nav Bar CTA to a black pill; that override is removed so
   the nav CTA matches the hero/footer CTAs. */

/* Reduced motion — freeze the wave, keep a static green halo. */
@media (prefers-reduced-motion: reduce) {
  .xalo-glow::after,
  a.xalo-glow::after,
  .xalo-glow::before,
  a.xalo-glow::before {
    animation: none !important;
    background-position: 50% 50% !important;
    opacity: 0.6 !important;
  }
}

/* ============================================================
   Logo carousel ("Already deployed in the Premier League.")
   The Framer CDN marquee JS was stripped, leaving the carousel
   at opacity:0 with a static translateX(0). Re-enable + animate
   with a CSS keyframe. The JS in lib/inject-content.ts clones
   the <ul>'s children once for a seamless loop.
   ============================================================ */
/* --- Hero enter animation (per-word blur-in, stagger) ------------
   Mirrors monoai.framer.website's hero entrance. Targets Framer's
   existing per-word spans inside the hero h1 and subtitle <p>, plus the
   Buttons group.

   Previously this used a class-toggle pattern (`.xalo-anim-go` added
   by React useEffect flipped the opacity from 0.001 → 1 via
   transition). That was brittle — if the transition was preempted by
   any other style recalc on the same property it could latch at
   0.001 and never recover, leaving the hero text invisible.

   New approach: a pure CSS `@keyframes xalo-hero-word-in` that runs
   automatically on page load with `animation-fill-mode: both` so the
   element sits at the "to" state after the animation completes — no
   class toggle needed, nothing to get stuck on. Stagger is still done
   via `animation-delay` on :nth-child below. */
@keyframes xalo-hero-word-in {
  from {
    opacity: 0.001;
    filter: blur(10px);
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    filter: blur(0);
    transform: translateY(0);
  }
}
section[data-framer-name="Header"] h1 > span,
section[data-framer-name="Header"] p > span {
  display: inline-block !important;
  animation: xalo-hero-word-in 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
  will-change: opacity, filter, transform;
}
/* Don't animate the marquee caption or its logo span wrappers */
section[data-framer-name="Header"] .xalo-marquee p > span,
section[data-framer-name="Header"] .xalo-marquee span,
section[data-framer-name="Header"] [data-framer-name="Buttons"] span,
section[data-framer-name="Header"] [data-framer-name="Companies"] span {
  opacity: 1 !important;
  filter: none !important;
  transform: none !important;
  transition: none !important;
}

/* H1 word stagger (up to 16 words) — now via animation-delay since
   the reveal is a keyframe animation, not a transition. */
section[data-framer-name="Header"] h1 > span:nth-child(1)  { animation-delay: 150ms; }
section[data-framer-name="Header"] h1 > span:nth-child(2)  { animation-delay: 205ms; }
section[data-framer-name="Header"] h1 > span:nth-child(3)  { animation-delay: 260ms; }
section[data-framer-name="Header"] h1 > span:nth-child(4)  { animation-delay: 315ms; }
section[data-framer-name="Header"] h1 > span:nth-child(5)  { animation-delay: 370ms; }
section[data-framer-name="Header"] h1 > span:nth-child(6)  { animation-delay: 425ms; }
section[data-framer-name="Header"] h1 > span:nth-child(7)  { animation-delay: 480ms; }
section[data-framer-name="Header"] h1 > span:nth-child(8)  { animation-delay: 535ms; }
section[data-framer-name="Header"] h1 > span:nth-child(9)  { animation-delay: 590ms; }
section[data-framer-name="Header"] h1 > span:nth-child(10) { animation-delay: 645ms; }
section[data-framer-name="Header"] h1 > span:nth-child(11) { animation-delay: 700ms; }
section[data-framer-name="Header"] h1 > span:nth-child(12) { animation-delay: 755ms; }
section[data-framer-name="Header"] h1 > span:nth-child(13) { animation-delay: 810ms; }
section[data-framer-name="Header"] h1 > span:nth-child(14) { animation-delay: 865ms; }
section[data-framer-name="Header"] h1 > span:nth-child(15) { animation-delay: 920ms; }
section[data-framer-name="Header"] h1 > span:nth-child(16) { animation-delay: 975ms; }

/* Subtitle starts ~1050ms in, 40ms stagger */
section[data-framer-name="Header"] p > span:nth-child(1) { animation-delay: 1050ms; }
section[data-framer-name="Header"] p > span:nth-child(2) { animation-delay: 1090ms; }
section[data-framer-name="Header"] p > span:nth-child(3) { animation-delay: 1130ms; }
section[data-framer-name="Header"] p > span:nth-child(4) { animation-delay: 1170ms; }
section[data-framer-name="Header"] p > span:nth-child(5) { animation-delay: 1210ms; }
section[data-framer-name="Header"] p > span:nth-child(6) { animation-delay: 1250ms; }
section[data-framer-name="Header"] p > span:nth-child(7) { animation-delay: 1290ms; }
section[data-framer-name="Header"] p > span:nth-child(8) { animation-delay: 1330ms; }

/* Buttons block — single fade after subtitle finishes. Uses a keyframe
   animation that only runs once .xalo-anim-go is on the Header so it
   can't fight Framer's inline opacity during hydration. */
@keyframes xalo-buttons-in {
  from { opacity: 0.001; transform: translateY(12px); }
  to   { opacity: 1;     transform: translateY(0); }
}
section[data-framer-name="Header"] [data-framer-name="Buttons"] {
  opacity: 0.001;
  transform: translateY(12px);
  will-change: opacity, transform;
}
section[data-framer-name="Header"].xalo-anim-go [data-framer-name="Buttons"] {
  animation: xalo-buttons-in 650ms cubic-bezier(0.22, 1, 0.36, 1) 1500ms forwards;
}

/* (Previously an "After" state toggled by `.xalo-anim-go` — now
   redundant because the keyframe animation above runs on load with
   `animation-fill-mode: both` and settles at the "to" state. Kept
   the comment so anyone hunting for the old selector finds the
   replacement.) */

@keyframes xalo-logo-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--xalo-marquee-shift, 50%))); }
}
@keyframes xalo-marquee-enter {
  from { opacity: 0; transform: translate(-50%, 12px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
[data-framer-name="Companies"] section[style*="opacity:0"],
[data-framer-name="Companies"] section[style*="opacity: 0"] {
  opacity: 1 !important;
}
[data-framer-name="Companies"] ul[data-xalo-marquee="1"] {
  animation: xalo-logo-marquee 32s linear infinite;
  will-change: transform;
  transform: translateX(0) !important; /* override Framer's inline translateX(-0px) */
}
[data-framer-name="Companies"] ul[data-xalo-marquee="1"]:hover {
  animation-play-state: paused;
}
/* Soften logo colour so they read as silent partners (matches monoai.framer.website feel) */
[data-framer-name="Companies"] [data-framer-name="Companies Logos"] {
  opacity: 0.75;
  transition: opacity 200ms ease;
}
[data-framer-name="Companies"] [data-framer-name="Companies Logos"]:hover {
  opacity: 1;
}

/* ============================================================
   Fresh marquee markup injected by marqueeBootScript
   (lib/inject-content.ts). This runs when the Framer
   ul-based marquee fails to render (stuck opacity:0 because
   the scroll-trigger JS was stripped). The JS builds:
     <div class="xalo-marquee">
       <div class="xalo-marquee-track">
         <div class="xalo-marquee-item">…logo…</div> × 2 passes
       </div>
     </div>
   ============================================================ */
[data-framer-name="Companies"] .xalo-marquee {
  width: 100%;
  max-width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 12%, #000 88%, transparent 100%);
  margin-top: 24px;
  padding: 8px 0;
}
[data-framer-name="Companies"] .xalo-marquee-track {
  display: flex;
  align-items: center;
  gap: 64px;
  width: max-content;
  animation: xalo-logo-marquee 32s linear infinite;
  will-change: transform;
}
[data-framer-name="Companies"] .xalo-marquee-track:hover {
  animation-play-state: paused;
}
[data-framer-name="Companies"] .xalo-marquee-item {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.75;
  transition: opacity 200ms ease;
  /* Defeat any stuck Framer inline opacity:0 on descendants */
}
[data-framer-name="Companies"] .xalo-marquee-item:hover { opacity: 1; }
[data-framer-name="Companies"] .xalo-marquee-item,
[data-framer-name="Companies"] .xalo-marquee-item *,
[data-framer-name="Companies"] .xalo-marquee-item [style*="opacity:0"],
[data-framer-name="Companies"] .xalo-marquee-item [style*="opacity: 0"] {
  opacity: 1 !important;
  visibility: visible !important;
}
[data-framer-name="Companies"] .xalo-marquee-item img,
[data-framer-name="Companies"] .xalo-marquee-item svg {
  height: 32px;
  width: auto;
  display: block;
}
/* When the new marquee is mounted, hide any leftover Framer wrapper in the
   section so we don't double-render. */
[data-framer-name="Companies"][data-xalo-marquee-ready="1"] ul,
[data-framer-name="Companies"][data-xalo-marquee-ready="1"] .framer-1e4u0zr-container {
  display: none !important;
}

/* ============================================================
   In-hero variant — the boot script relocates the built marquee
   under the hero's Buttons container. These rules target the
   moved element (no longer inside [data-framer-name="Companies"])
   so the scoped rules above don't apply.
   ============================================================ */
.xalo-marquee--in-hero {
  /* Absolutely positioned inside section[data-framer-name="Header"].
     Top is set dynamically by the boot script from
     buttons.getBoundingClientRect(). left:50% + translateX centres it
     horizontally inside the hero.
     z-index 50 is well above the 3D canvas (z:0) and hero content (z:2)
     so the logos always sit on top of the particle figure. */
  position: absolute !important;
  left: 50% !important;
  transform: translate(-50%, 0);
  top: 0;
  animation: xalo-marquee-enter 700ms cubic-bezier(0.22, 1, 0.36, 1) 600ms both;
  /* Narrower track — we only show 4 partner logos now, not the 15-item
     Framer default set, so 640px was too wide and left dead space. */
  width: min(480px, 100% - 48px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 6%, #000 94%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 6%, #000 94%, transparent 100%);
  padding: 4px 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  z-index: 50 !important;
  pointer-events: auto;
  isolation: isolate;
}
.xalo-marquee--in-hero .xalo-marquee-track {
  display: flex;
  align-items: center;
  /* Use padding-right on each item (below) instead of flex gap so the
     -50% keyframe loop lands seamlessly — flex `gap` only adds space
     BETWEEN items, which skews the halfway point and causes a visible
     skip at loop boundaries. */
  gap: 0;
  width: max-content;
  animation: xalo-logo-marquee 40s linear infinite;
  will-change: transform;
}
.xalo-marquee--in-hero .xalo-marquee-item {
  padding-right: 56px;
}
.xalo-marquee--in-hero .xalo-marquee-caption {
  font-family: var(--xalo-body) !important;
  font-size: 16px !important;
  font-weight: 500 !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  color: rgba(255, 255, 255, 0.8) !important;
  text-align: center !important;
  margin: 0 !important;
  line-height: 1.4 !important;
}
.xalo-marquee--in-hero .xalo-marquee-track:hover {
  animation-play-state: paused;
}
.xalo-marquee--in-hero .xalo-marquee-item {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.45;
  transition: opacity 200ms ease;
}
.xalo-marquee--in-hero .xalo-marquee-item:hover { opacity: 0.85; }
.xalo-marquee--in-hero .xalo-marquee-item,
.xalo-marquee--in-hero .xalo-marquee-item *,
.xalo-marquee--in-hero .xalo-marquee-item [style*="opacity:0"],
.xalo-marquee--in-hero .xalo-marquee-item [style*="opacity: 0"] {
  visibility: visible !important;
}
.xalo-marquee--in-hero .xalo-marquee-item img,
.xalo-marquee--in-hero .xalo-marquee-item svg {
  /* Let each logo use its intrinsic viewBox size (heights 14-24px) so
     proportions stay natural — matches monoai.framer.website exactly. */
  height: auto;
  width: auto;
  display: block;
  filter: brightness(0) invert(1);
}
.xalo-marquee--in-hero .xalo-marquee-item svg,
.xalo-marquee--in-hero .xalo-marquee-item svg * {
  fill: #ffffff !important;
}

/* Huly-style perforated-grid pattern is now painted into each
   section's background (see the `section[class*="framer-"]…`
   rule in section 3 below). That keeps the dots strictly in
   the background layer — text, icons and images always sit
   above them, so typography never gets perforated. The Header
   section is excluded from the rule so the 3D hero region
   stays a clean dark stage. */

/* ============================================================
   Home-only: hide "Same underlying problem. Same platform."
   section + the 6 sport/differentiator tiles below it.
   This is the Framer "Features Icons" block (section.framer-uxauty).
   The same content already renders on the What We Do (/product)
   page as the multi-sport tile grid (see migrate-xalo-copy.mjs
   "Meet the team" → "Same underlying problem. Same platform."
   swap + teamToSport table), so this is a clean removal on home
   to avoid duplication. The class does not appear in product.html,
   so a global selector is safe.
   ============================================================ */
section.framer-uxauty {
  display: none !important;
}

/* ============================================================
   Home-only: hide "The next edge in elite sport is informational."
   This is the Framer "Integrations" block (section.framer-bn6wlg),
   a headline + subcopy + "Get in touch" CTA. James asked for a
   straight removal — no destination, just drop it from home.
   Class does not appear in product.html, so a global selector is
   page-scoped by construction.
   ============================================================ */
section.framer-bn6wlg {
  display: none !important;
}

/* ============================================================
   Home-only: hide "Live in elite sport today." section
   (section.framer-1k3n6t8, Framer "Features Cards" block — 8
   headline/subcopy cards: Tactics have plateaued / The data exists
   / A short runway / Premier League / Performance staff / More
   environments / Every source in / No new hardware). Straight
   removal; no destination on What We Do.
   Class does not appear in product.html, so a global selector is
   page-scoped by construction.
   ============================================================ */
section.framer-1k3n6t8 {
  display: none !important;
}

/* ============================================================
   Home-only: hide the "Three ACLs last season" + "Before this we
   were guessing" testimonial/stat block (section.framer-rwigqs,
   Framer "Features Walkthrough"). Contains "Already deployed in
   the Premier League" headline, two quote/stat pairs (+50% / +145%
   and +120%), a Proline gradient card, and a "Get in touch" CTA.
   Straight removal; no destination on What We Do.
   Class does not appear in product.html, so a global selector is
   page-scoped by construction.
   ============================================================ */
section.framer-rwigqs {
  display: none !important;
}

/* ============================================================
   Home Features Walkthrough (Connect / Predict / Decide): hide
   the "Get in touch" CTA on the Connect and Predict cards only,
   leave Decide's CTA intact.
   Each card's Title Wrapper contains a Framer-generated button
   container (div.framer-*-container) that wraps the xalo-huly
   anchor. The container classes are stable (regenerated from
   framer.bak on hard-reset) — captured via DOM inspection:
     • Connect  → div.framer-zmx4xm-container  (Title 1)
     • Predict  → div.framer-1ds8omt-container (Title 2)
     • Decide   → div.framer-1fz71f5-container (Title 3, UNCHANGED)
   Hiding the container (not just the anchor) collapses the flex
   column so there's no empty slot under the card copy.
   ============================================================ */
section.framer-15fvg7a [data-framer-name="Title 1"] div.framer-zmx4xm-container,
section.framer-15fvg7a [data-framer-name="Title 2"] div.framer-1ds8omt-container {
  display: none !important;
}

/* ============================================================
   Home-page section rhythm — after hiding the 4 duplicate/unwanted
   sections (Features Icons, Integrations, Features Cards, Features
   Walkthrough "Three ACLs" testimonial) the remaining sections had
   Framer's original 96–300px padding baked in, which meant the gaps
   between visible sections were 192–396px — James called it out as
   looking "quite large". Tighten everything to a consistent ~140–170px
   combined gap between adjacent section contents. Selectors use
   [data-framer-name] because the framer-* hashes are stable but these
   are the semantic anchors Framer itself uses. !important to override
   Framer's generated class-level padding.
   ============================================================ */
/* Airy baseline: every section gets pt/pb 120px so content breathes
   with room on both sides. Where a section's trailing glow needs
   extra fall-off space before the next section (Features Illustrations,
   CTA) the padding-bottom is bumped further. */
[data-framer-name="Features Illustrations"] {
  padding-top: 120px !important;
  padding-bottom: 220px !important;
}
[data-framer-name="Features Walkthrough"] {
  padding-top: 120px !important;
  padding-bottom: 120px !important;
}
[data-framer-name="Testimonials"] {
  padding-top: 140px !important;
  padding-bottom: 140px !important;
}
[data-framer-name="FAQ"] {
  padding-top: 120px !important;
  padding-bottom: 120px !important;
}
/* CTA was the worst offender: pt:300 / pb:200 — 500px of padding
   around a ~200px headline. Trim to match the rest of the rhythm, but
   keep a generous padding-bottom so the green glow (::before, floated
   48px above the footer boundary) has room to fade into dot-grid
   without getting cut off by the footer. */
[data-framer-name="CTA"] {
  padding-top: 96px !important;
  padding-bottom: 280px !important;
}

/* ============================================================
   Footer cleanup — load-bearing fixes for issues that kept coming
   back on migration reruns:
     1) "Get in touch" in the footer must be a plain text link,
        not a Huly pill. The migration script (buildClientScript
        in scripts/migrate-xalo-copy.mjs) is the primary fix —
        it now bails on any footer anchor before the primary-text
        tagging loop can wrap it with a glow pill. These CSS rules
        are the belt-and-braces fallback: even if some future edit
        to the migration re-introduces the pill class/wrapper in
        the footer, these rules flatten it back to a plain link.
     2) Hide "Made in Framer" credit (framer-1s069h7).
     3) Hide "404" link wrapper (framer-19r16kc).
   ============================================================ */

/* (Legacy footer Get-in-touch reset removed. The old Framer footer
   had a default Huly-classed link that we needed to strip to plain
   text. The new React footer deliberately USES the Huly button for
   its "Book a demo" CTA, so the full pill styling must be allowed
   through inside the footer.) */

/* Remove "Made in Framer" credit link wrapper and the 404 link wrapper.
   Both sit under the footer Copyright / Legal links column. Targeting
   the Framer-generated wrapper class (stable across hard-resets because
   framer.bak is the same source). */
footer .framer-1s069h7,
[data-framer-name="Footer"] .framer-1s069h7,
footer .framer-19r16kc,
[data-framer-name="Footer"] .framer-19r16kc {
  display: none !important;
}

/* ============================================================
   Mobile responsiveness
   ============================================================ */

/* Safety net: no element should ever widen the document past the
   layout viewport. Combined with `initial-scale=1` (migration),
   this prevents the "zoomed-out" mobile view where Framer's baked
   min-width values leak past device-width.
   Selectors match the `:has(.xalo-huly-wrap)` specificity (upstream
   rule sets overflow:visible on every CTA ancestor incl. html/body)
   so we win on cascade-later, letting html/body clip horizontally
   while inner CTA wrappers keep overflow:visible for the halo. */
html, body,
html:has(.xalo-huly-wrap),
body:has(.xalo-huly-wrap) {
  overflow-x: clip !important;
  max-width: 100vw !important;
}

/* Framer bakes fixed widths (442px) into its mobile nav variant.
   On phones narrower than 442px those widths leak past device-width
   and widen the layout viewport. Clamp the outermost nav wrappers
   to viewport width; leave inner children (Logo, Content) at their
   natural size so they don't stretch and overlap. */
@media (max-width: 809.98px) {
  nav[data-framer-name="Mobile Closed"],
  [data-framer-name="Nav Bar"],
  [data-framer-name="ProgressiveBlur"] {
    width: 100vw !important;
    max-width: 100vw !important;
    left: 0 !important;
    right: 0 !important;
  }
  /* Scroll-bg absolute-positioned element that Framer bakes at
     pre-responsive widths. */
  .framer-iua6jc {
    max-width: 100vw !important;
  }

  /* Framer ships the Mobile Closed nav with flex-direction: column
     which stacks Logo / Content / Links / Button vertically. Force
     a single horizontal row (Logo left, hamburger right) so the nav
     is a single strip. */
  nav[data-framer-name="Mobile Closed"],
  nav[data-framer-name="Mobile Closed"] > [data-framer-name="Container"] {
    flex-direction: row !important;
    justify-content: space-between !important;
    align-items: center !important;
    height: auto !important;
    min-height: 56px !important;
  }
  /* Logo NAV wraps the xalo SVG only (hamburger Content hidden below).
     Shrink to content so the inline nav links + CTA can sit alongside. */
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Logo"] {
    width: auto !important;
    max-width: none !important;
    flex: 0 0 auto !important;
    flex-direction: row !important;
    justify-content: flex-start !important;
    align-items: center !important;
    padding: 0 !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Logo"] > a {
    justify-content: flex-start !important;
    flex: 0 0 auto !important;
  }
  /* Hide the hamburger entirely — we inline the nav links instead. */
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Logo"] [data-framer-name="Content"] {
    display: none !important;
  }
  /* Show the inline Links + Button so the two other pages (What We Do,
     Get in touch) sit on the right of the mobile nav strip. Only two
     pages to list so no burger menu needed.
     IMPORTANT: Framer ships the Links container with opacity:0 on the
     Mobile Closed variant (it only fades in when the burger drawer
     opens). Force opacity:1 + visibility so the inline links render
     at page load without needing to open the menu.
     flex: 0 0 auto + justify-content: flex-end pins the two links as a
     tight group at the RIGHT edge of the nav, not drifting across the
     middle. */
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] {
    display: flex !important;
    flex: 0 0 auto !important;
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 20px !important;
    margin-left: auto !important;
    width: max-content !important;
    max-width: none !important;
    min-width: 0 !important;
    height: auto !important;
    padding: 0 !important;
    opacity: 1 !important;
    visibility: visible !important;
    pointer-events: auto !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Link"] {
    opacity: 1 !important;
    visibility: visible !important;
    pointer-events: auto !important;
    /* Framer bakes padding-left: 80px on the Contact Link div (inherited
     from desktop spacing). Zero it so the two links sit flush together
     with just the Links' gap between them, not a baked 80px shim. */
    padding: 0 !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Button"] {
    display: inline-flex !important;
    flex: 0 0 auto !important;
    align-items: center !important;
    width: auto !important;
    max-width: none !important;
    min-width: 0 !important;
    height: auto !important;
    padding: 0 !important;
  }
  /* Compact link label so "What We Do" fits next to the logo + CTA pill. */
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] [data-framer-name="Link"] a,
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] p {
    font-size: 13px !important;
    line-height: 1 !important;
    white-space: nowrap !important;
  }
  /* Show only the two real pages (/what-we-do + /book-a-demo) in the
     mobile inline nav. Pricing + Changelog are placeholder routes that
     don't exist on this site — hidden globally further down. */
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Link"]:has(a[href="/what-we-do"]),
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Link"]:has(a[href="/book-a-demo"]) {
    display: flex !important;
  }
  /* Breathing room from the viewport edges for the mobile strip. */
  nav[data-framer-name="Mobile Closed"] > [data-framer-name="Container"] {
    padding-left: 20px !important;
    padding-right: 20px !important;
    gap: 12px !important;
  }

  /* Mobile nav = frosted glass bar. The solid-black default was from
     the scroll-bg + ProgressiveBlur both sitting fully opaque; strip
     those and put a proper frosted glass on the nav itself so the
     hero shows through with a subtle dark tint + blur. */
  nav[data-framer-name="Mobile Closed"] {
    background: rgba(10, 10, 12, 0.45) !important;
    backdrop-filter: blur(18px) saturate(1.3) !important;
    -webkit-backdrop-filter: blur(18px) saturate(1.3) !important;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important;
  }
  /* Outer Nav Bar wrapper stays transparent (it's the positioning
     shell for the glass nav). */
  [data-framer-name="Nav Bar"] {
    background-color: transparent !important;
    background: transparent !important;
  }
  /* Kill the ProgressiveBlur + scroll-bg — our own glass on the nav
     provides the blur now. Leaving them opaque would double up and
     produce the old solid-black look. */
  [data-framer-name="ProgressiveBlur"] {
    background: transparent !important;
  }
  .framer-iua6jc,
  [data-framer-name="scroll bg"] {
    background-color: transparent !important;
  }
}

/* iPad-portrait sub-range: Framer still shows the mobile nav variant up
   to 810px, but at 768px the phone-tuned 56px strip looks cramped with
   too much whitespace around small text. Give the nav more vertical
   room, larger link type, and a bigger logo so the glass bar has
   presence on iPad. */
@media (min-width: 641px) and (max-width: 809.98px) {
  nav[data-framer-name="Mobile Closed"],
  nav[data-framer-name="Mobile Closed"] > [data-framer-name="Container"] {
    min-height: 76px !important;
  }
  nav[data-framer-name="Mobile Closed"] > [data-framer-name="Container"] {
    padding-left: 32px !important;
    padding-right: 32px !important;
    gap: 20px !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Logo"] svg.xalo-logo-svg {
    height: 28px !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] {
    gap: 28px !important;
  }
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] [data-framer-name="Link"] a,
  nav[data-framer-name="Mobile Closed"] [data-framer-name="Links"] p {
    font-size: 15px !important;
  }
}

/* Hero: on phones the 3D wireframe is positioned via Framer's
   absolute layout and overlaps the headline. Constrain the hero
   Content column so text stays left-aligned and the 3D stays
   behind as a backdrop, not overlapping. */
@media (max-width: 809.98px) {
  /* Section overall horizontal padding — keep content inside the
     viewport no matter what Framer baked in. */
  /* Mobile safe-zone, applied to any Framer-baked section. Uses the
     `--xr-safe` variable declared in xalo-react.css so the gutter
     stays in sync with the React-side sections. DO NOT hardcode —
     if you edit this, update :root in xalo-react.css instead. */
  section[data-framer-name] {
    padding-left: var(--xr-safe) !important;
    padding-right: var(--xr-safe) !important;
  }

  /* Hero: fill exactly one viewport so the title sits in the
     lower-middle, the CTA row tucks directly under it, and the
     "already deployed" marquee pins to the bottom of the screen.
     Explicit height (not min) — Framer ships a hidden "Video"
     placeholder child that bloats to 390px and stretches the
     section past the viewport, leaving the marquee below the fold.
     Scoped to `:not(.xr-hero--no-canvas)` so that hero pages WITHOUT
     the 3D canvas (e.g. /what-we-do, /book-a-demo) don't inherit the
     40vh push-down designed to make room for the 3D figure above. */
  section[data-framer-name="Header"]:not(.xr-hero--no-canvas) {
    height: 100vh !important;
    min-height: 100vh !important;
    max-height: 100vh !important;
    padding-top: 40vh !important;
    padding-bottom: 120px !important;
    box-sizing: border-box !important;
    overflow: hidden !important;
  }
  /* Kill the invisible Video placeholder child that contributes
     ~390px of dead height on mobile. The existing rule at the top
     of the file targets the <video> tag; this one targets the
     Framer component wrapper by name. */
  section[data-framer-name="Header"] > [data-framer-name="Video"] {
    display: none !important;
  }
  /* Collapse the large visual gap Framer leaves between the subtext and
     the CTA — on mobile we want the button tucked right under the copy. */
  section[data-framer-name="Header"] [data-framer-name="Buttons"] {
    margin-top: 20px !important;
  }
  /* Pin the relocated marquee to the bottom of the hero instead of
     letting the JS place() function stick it 24px under the CTA. The
     !important wins over the inline style the script writes. */
  .xalo-marquee--in-hero {
    top: auto !important;
    bottom: 40px !important;
    margin-top: 0 !important;
  }
  /* Hero headline: Framer's 36px desktop-mobile font overflows the
     viewport with long words like "doesn't". Shrink to 28px on phones.
     Alignment stays `center` to match the rest of the hero block;
     a previous revision forced `left` here but that made the text
     hug the safe-zone edge and read as if the padding was missing
     (it wasn't — just the alignment was off). */
  section[data-framer-name="Header"] h1 {
    font-size: 28px !important;
    line-height: 1.15 !important;
    text-align: center !important;
    word-break: break-word !important;
  }
  section[data-framer-name="Header"] [data-framer-name="Container"] {
    text-align: center !important;
  }

  /* Trim the big section paddings we set for desktop — on phones
     60–80px reads as plenty of air. */
  [data-framer-name="Features Illustrations"] {
    padding-top: 72px !important;
    padding-bottom: 140px !important;
  }
  [data-framer-name="Features Walkthrough"] {
    padding-top: 72px !important;
    padding-bottom: 72px !important;
  }
  [data-framer-name="Testimonials"] {
    padding-top: 88px !important;
    padding-bottom: 88px !important;
  }
  [data-framer-name="FAQ"] {
    padding-top: 72px !important;
    padding-bottom: 72px !important;
  }
  [data-framer-name="CTA"] {
    padding-top: 72px !important;
    padding-bottom: 180px !important;
  }

  /* Marquee: narrow to viewport and keep logos small enough to read. */
  .xalo-marquee,
  .xalo-marquee--in-hero {
    width: min(320px, 100% - 32px) !important;
  }

  /* CTA glow: tame the negative left/right so it doesn't force a
     wider horizontal scroll area on phones. */
  [data-framer-name="CTA"]::before {
    left: -20% !important;
    right: -20% !important;
  }

  /* FAQ: force single-column stack on phones (override the 2-col
     grid applied at desktop). */
  .xalo-faq__grid {
    grid-template-columns: 1fr !important;
  }

  /* Hero 3D canvas fills the whole Header section; on phones the
     text stacks over it and becomes unreadable because the figure
     sits right behind the headline. Dim it significantly on phones
     so the figure reads as atmosphere, not subject. */
  #xalo-hero-3d {
    opacity: 0.35 !important;
  }

  /* And make the hero headline/subcopy sit on a slightly more
     legible background by boosting text contrast. */
  section[data-framer-name="Header"] h1,
  section[data-framer-name="Header"] [data-framer-name="Hero Title"] {
    text-shadow: 0 2px 14px rgba(0, 0, 0, 0.75);
  }
}
