/* Flow Source — hero
   Proportions and structure ported from kora.framer.media (sticky hero,
   scroll-reactive background, word/letter blur-in type) and
   spartanai.framer.website (inset rounded frame, nav pill, swallow CTA,
   bottom logo ticker).
   Colors only are from the Paper file "FlowSrc / 00 · Foundations". */

:root {
  /* --- Paper tokens, verbatim --- */
  --ground:      #FFFFFF;
  --newsprint:   #F0F0F0;
  --rule:        #DDE1E6;
  --slate:       #6B7A8C;
  --ink:         #0F2A44;
  --cobalt:      #1E6FD9;
  --sky:         #5AA9F0;
  --sky-rgb:     90, 169, 240;
  --glass:       #FFFFFFDB;   /* 86% white — cards */
  --glass-nav:   #FFFFFFE0;   /* 88% white — nav islands */
  --panel-tint:  #EAF3FD;

  /* the wash laid over the painting, as rgb components */
  --wash-rgb: 30, 111, 217;   /* cobalt */

  --on-dark:    #FFFFFF;
  --on-dark-72: rgba(255, 255, 255, .72);
  --on-dark-45: rgba(255, 255, 255, .45);
  --hairline:   rgba(255, 255, 255, .18);
  --dark-glass: rgba(15, 42, 68, .38);

  /* the one shadow in the system: floating glass only */
  --shadow-glass: 0 8px 30px rgba(15, 42, 68, .08);
  --border-glass: 1px solid rgba(15, 42, 68, .08);

  --font-display: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --frame-gutter: 12px;
  --hero-pad-x: 80px;
  --ticker-gap: 46px;

  /* The nav pill floats `--nav-inset` inside the frame, so for the gap to
     stay even around a corner the frame's radius has to be the pill's own
     radius plus that inset — otherwise the corner pinches on the diagonal. */
  --nav-inset: 22px;
  --nav-pill-h: 44px;
  --frame-radius: calc(var(--nav-inset) + (var(--nav-pill-h) / 2));
}

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

html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--newsprint);
  color: var(--on-dark);
  font-family: var(--font-display);
  font-size: 17px;
  line-height: 1.5;
  overflow-x: hidden;
}

h1, h2, h3, p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
ul { margin: 0; padding: 0; }

/* ---------------------------------------------------------------- hero shell
   .hero-scroll supplies the scroll distance the sticky hero animates across. */

.hero-scroll {
  --settle-screens: .4;
  position: relative;
  height: calc(380vh + var(--settle-screens) * 100vh);
}

.hero {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 700px;
  /* The frame reaching full bleed is its own, faster sub-ramp of act two:
     it is done by --pb .45 rather than dragging all the way to 1. The
     statement then begins exactly where this finishes, so the words only
     start arriving once the painting owns the whole screen. */
  --cov: clamp(0, calc(var(--pb, 0) / .45), 1);
  --ending: clamp(0, calc((var(--pb, 0) - .83) / .17), 1);
  --ending-eased: calc(var(--ending) * var(--ending) * (3 - 2 * var(--ending)));
  --frame-inset: calc(var(--frame-gutter) * (1 - var(--cov) + var(--ending-eased)));
  padding: var(--frame-inset);
  background: var(--newsprint);
  display: flex;
}

.hero-frame {
  position: relative;
  flex: 1;
  border-radius: calc(var(--frame-radius) * (1 - var(--cov) + var(--ending-eased)));
  overflow: hidden;
  background: var(--cobalt);
  isolation: isolate;
}

/* -------------------------------------------------------------- background */

.hero-bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; } /* decorative, aria-hidden */

.hero-bg-media {
  position: absolute;
  inset: 0;
  /* Act one eases it back and softens it. Act two carries that further,
     but only far enough to sit behind type — pushed harder (it was 1.60x
     and 35px of blur) the loop stops being watchable at all. */
  transform: scale(calc(1 + (.14 * var(--pa, 0)) + (.22 * var(--pb, 0))));
  filter: blur(calc((9px * var(--pa, 0)) + (6px * var(--pb, 0))));
  will-change: transform, filter;
  /* the still sits underneath the loop: it covers the first paint before
     video data arrives, and it is what remains when the loop is suppressed */
  background: url("assets/hero.jpg") center 58% / cover no-repeat;
}

/* motion is decoration here — with reduced motion the painting stays still */
@media (prefers-reduced-motion: reduce) {
  .hero-bg-video { display: none; }
}

.hero-bg-media img,
.hero-bg-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 58%;
}

/* the wash exists only to keep type legible over the painting */
.hero-wash {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(var(--wash-rgb), .68) 0%,
      rgba(var(--wash-rgb), .30) 34%,
      rgba(var(--wash-rgb), .42) 62%,
      rgba(var(--wash-rgb), .80) 100%),
    linear-gradient(96deg,
      rgba(var(--wash-rgb), .60) 0%,
      rgba(var(--wash-rgb), .16) 52%,
      rgba(var(--wash-rgb), .00) 78%);
  /* pulls back further in act two so the painting reads through it */
  opacity: calc(.84 + (.16 * var(--pa, 0)) - (.48 * var(--pb, 0)));
}

.hero-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 40%, transparent 40%, rgba(var(--wash-rgb), .52) 100%);
  mix-blend-mode: multiply;
}

/* act two only — four corner falloffs that close in as the painting fills
   the screen, so a full-bleed blur still has edges to hold it */
.hero-corners {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(62% 58% at 0% 0%,     rgba(6, 20, 34, .62), transparent 72%),
    radial-gradient(62% 58% at 100% 0%,   rgba(6, 20, 34, .62), transparent 72%),
    radial-gradient(62% 58% at 0% 100%,   rgba(6, 20, 34, .68), transparent 72%),
    radial-gradient(62% 58% at 100% 100%, rgba(6, 20, 34, .68), transparent 72%);
  opacity: var(--pb, 0);
}

.hero-grain {
  position: absolute;
  inset: -50%;
  opacity: .2;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------------- nav */

/* fixed rather than parked inside the frame, so it rides the whole page.
   Offsets keep it the same --nav-inset away from the frame's edge, which
   is what makes the frame's corner radius concentric with the pill's. */
.nav {
  position: fixed;
  z-index: 50;
  /* the row is as wide as the page but only the pill and CTA are real;
     the empty strip between them passes the pointer through */
  pointer-events: none;
  top: calc(var(--frame-gutter) + var(--nav-inset));
  left: calc(var(--frame-gutter) + var(--nav-inset));
  right: calc(var(--frame-gutter) + var(--nav-inset));
  display: flex;
  align-items: center;
  gap: 16px;
}

/* Hovering the nav throws the whole page out of focus behind it, so the
   island is the only thing readable while the bar is open. One fixed
   full-screen layer at z-index -1: under the pill and the CTA, but still
   inside the nav's z-index:50 stacking context, so it covers every
   section. pointer-events:none keeps it from eating the hover it
   depends on, or any click on the page underneath. */
.nav::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  background: rgba(255, 255, 255, .18);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  transition: opacity .45s ease;
}

/* :has, because the trigger (the pill) is a child of the element the layer
   hangs off. The CTA is deliberately NOT a trigger — hovering "Book a call"
   leaves the page in focus. Gated on .is-ready so it cannot fire under the
   splash. */
.is-ready .nav:has(.nav-stack:hover, .nav-stack:focus-within)::before {
  opacity: 1;
}

/* the bar is one thing or the other: while the pill is open and the page
   behind it has dropped out of focus, the CTA is covered rather than left
   floating sharp over a blurred page. Unclickable while it is hidden, so
   nothing invisible can be hit. */
.is-ready .nav:has(.nav-stack:hover, .nav-stack:focus-within) .cta {
  opacity: 0;
  transform: translateX(10px);
  pointer-events: none;
  transition: opacity .3s ease, transform .3s cubic-bezier(.22, 1, .36, 1);
}

/* pill and the composer that hangs beneath it, as one hover target.
   The composer is absolutely positioned so it adds no height to the bar —
   the CTA stays on the pill's centre line whether the composer is out or
   not — and `margin-right: auto` lives here now rather than on the pill,
   so the CTA is still pushed to the far edge. */
.nav-stack {
  position: relative;
  margin-right: auto;
  pointer-events: auto;
}

.nav > .cta { pointer-events: auto; }

/* --------------------------------------------------- nav ask composer
   The hero's composer follows the page up. It is only ever out while the
   bar is folded (past the hero) AND the stack is hovered or focused, so
   it reads as the second half of the same opening gesture rather than a
   permanent fixture.

   The padding-top is deliberately on this wrapper, not a margin on the
   form: it makes the gap between pill and composer part of the hover
   target, so the pointer can travel down into the field without the bar
   snapping shut under it. */
.nav-ask {
  position: absolute;
  top: 100%;
  left: 0;
  padding-top: 10px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity .3s ease, transform .4s cubic-bezier(.22, 1, .36, 1),
              visibility 0s linear .3s;
}

.is-ready.is-collapsed .nav-stack:hover .nav-ask,
.is-ready.is-collapsed .nav-stack:focus-within .nav-ask {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
  transition: opacity .35s ease .12s,
              transform .5s cubic-bezier(.22, 1, .36, 1) .12s,
              visibility 0s linear .12s;
}

/* the hero's field is fluid inside its column; this one is a fixed island
   that must never outgrow the frame it floats in */
.ask-nav {
  width: min(370px, calc(100vw - ((var(--frame-gutter) + var(--nav-inset)) * 2)));
  max-width: none;
}

/* Spartan's navigation pill: 44px tall (5 + 34 + 5), gap 24,
   padding 5px 26px 5px 5px, radius 100px. Surface per the Paper
   nav-island rule: 88% white + blur(16) + 1px ink-8% + one soft shadow. */
.nav-pill {
  display: flex;
  align-items: center;
  gap: 24px;
  height: var(--nav-pill-h);
  padding: 5px 26px 5px 5px;
  border-radius: 100px;
  border: var(--border-glass);
  background: var(--glass-nav);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: var(--shadow-glass);
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 34px;
  padding: 0 15px 0 6px;
  border-radius: 100px;
  background: var(--cobalt);
  color: var(--ground);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
  /* lifts the chip off the pill while the bar is open; drops flat again
     once the bar has folded back into it */
  box-shadow: 0 4px 14px rgba(30, 111, 217, .32);
  transition: box-shadow .4s ease;
}

.nav-brand img {
  width: 22px;
  height: 22px;
  /* the mark ships in cobalt tones; flatten it to read on the chip */
  filter: brightness(0) invert(1);
}

.nav-links { display: flex; align-items: center; }

.nav-links a {
  padding: 7px 16px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .02em;
  line-height: 1.4;
  white-space: nowrap;
  color: var(--ink);
  opacity: .65;
  border-radius: 100px;
  background-color: transparent;
  transition: opacity .2s ease, background-color .2s ease;
}

.nav-links a:hover,
.nav-links a[aria-current="page"] { opacity: 1; }

/* pointing at a link lights it as a soft sky-blue pill */
.nav-links a:hover,
.nav-links a:focus-visible {
  background-color: rgba(var(--sky-rgb), .15);
  color: var(--cobalt);
}

/* ---- act two: the statement ----------------------------------------
   The line rides the same --pb ramp as the blur and the darkening corners,
   so it surfaces only once the painting has gone full-bleed. Each word
   carries its own --i and derives its own 0-1 progress from that shared
   ramp, which means the stagger is a function of scroll position rather
   than a timer: scrub back up and the line un-reveals in step. */
.hero-statement {
  position: absolute;
  inset: 0;
  opacity: calc(1 - var(--statement-exit, 0));
  /* leaving, the line softens out of focus as it fades, so it reads as
     receding into the painting rather than just dimming */
  filter: blur(calc(var(--statement-exit, 0) * 10px));
  /* above the painting, below the hero copy — which has already cleared
     out in act one by the time any of this is visible */
  z-index: 1;
  margin: 0;
  /* the flex box only centres the line as a single block. The words wrap
     inside .hero-statement-line as ordinary text, so a second line sits
     under the first at line-height distance — wrapping flex items here
     instead would spread the rows down the full height of the frame. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 max(var(--hero-pad-x), 7vw);
  /* invisible full-frame box until its words start landing; hero.js adds
     .is-statement-live then, so the line becomes selectable/inspectable */
  pointer-events: none;
  color: var(--on-dark);
  text-shadow: 0 2px 34px rgba(6, 20, 34, .38);
}

.hero-statement-line {
  max-width: 15em;
  text-align: center;
  text-wrap: balance;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(34px, 6vw, 88px);
  line-height: 1.08;
  letter-spacing: -.025em;
  /* The line holds dead centre while it assembles, and only starts being
     dragged down once the last word has landed (--pb .83) — so the reveal
     and the travel read as two separate beats rather than one smeared
     move. */
  /* Smoothstep the raw progress — 3d² - 2d³. Mapped straight through, the
     line starts and stops abruptly at the two ends of a short window;
     eased, it gathers and settles instead of jerking into motion. */
  /* Land with a small bottom inset. Transform percentages follow the text
     box, keeping the spacing consistent as the heading wraps. */
  --drift-dist: calc(max(50vh, 350px) - var(--frame-inset) - 50% - 20px);
  /* a slight shrink rides along with the drag, so the line settles back
     into the frame instead of sliding down it at full size */
  transform:
    translateY(calc(var(--ending-eased) * var(--drift-dist)))
    scale(calc(1 - (.06 * var(--ending-eased))));
  transform-origin: 50% 100%;
  will-change: transform;
}

/* Each word takes its own slice of the ramp: it waits for the frame to
   finish covering the screen (--pb .45), offsets by its index, and takes
   a fifth of the ramp to land. Last word starts at .45 + 5 x .03 and is
   in by .80, just ahead of the drag at .83. The raw slice is smoothstepped
   into --e so every word gathers and settles instead of moving linearly,
   and it tips up off its own baseline — hinged at the bottom, rising from
   a lean back — so the line reads as standing up rather than fading in. */
.hs-word {
  --t: clamp(0, calc((var(--pb, 0) - .45 - (var(--i) * .03)) * 5), 1);
  --e: calc(var(--t) * var(--t) * (3 - 2 * var(--t)));
  display: inline-block;
  opacity: clamp(0, calc(var(--e) * 1.5), 1);
  transform-origin: 50% 100%;
  transform:
    perspective(700px)
    translateY(calc((1 - var(--e)) * .42em))
    rotateX(calc((1 - var(--e)) * -62deg))
    scale(calc(.94 + (.06 * var(--e))));
  filter: blur(calc((1 - var(--e)) * 9px));
  will-change: opacity, transform, filter;
}

/* the accent word arrives with a little extra flourish: a leftward lean
   that rights itself as it lands, then a swash under it that draws left
   to right and is finished before the drag starts (.83) */
.hs-accent {
  position: relative;
  transform:
    perspective(700px)
    translateY(calc((1 - var(--e)) * .42em))
    rotateX(calc((1 - var(--e)) * -62deg))
    rotate(calc((1 - var(--e)) * -7deg))
    scale(calc(.94 + (.06 * var(--e))));
}

.hs-accent::after {
  --s: clamp(0, calc((var(--pb, 0) - .68) / .15), 1);
  content: "";
  position: absolute;
  left: 4%;
  right: 2%;
  bottom: .02em;
  height: .07em;
  border-radius: 1em;
  background: linear-gradient(90deg, rgba(255, 255, 255, .2), rgba(255, 255, 255, .85));
  transform-origin: 0 50%;
  transform: scaleX(calc(var(--s) * var(--s) * (3 - 2 * var(--s)))) rotate(-1.5deg);
  opacity: clamp(0, calc(var(--s) * 3), 1);
}

/* the one serif accent, same Instrument Serif italic the pre-roll uses */
.hero-statement em {
  font-family: "Instrument Serif", Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 1.06em;
}

/* ---- the nav unfurls from the brand chip once the pre-roll lifts ----
   The pill is clipped back to the width of the Flow Source chip (measured
   into --nav-collapsed by hero.js) and opens rightward from there, so the
   chip reads as the thing the rest of the bar grows out of. */

.is-splitting .nav-pill {
  clip-path: inset(0 calc(100% - var(--nav-collapsed, 140px)) 0 0 round 100px);
}

.nav-pill { transition: clip-path .75s cubic-bezier(.22, 1, .36, 1) .1s; }

.is-ready .nav-pill { clip-path: inset(0 0 0 0 round 100px); }

/* the links ride out behind the opening edge, one after another */
.is-splitting .nav-links a { opacity: 0; transform: translateX(-10px); }

.nav-links a {
  transition:
    opacity .45s ease,
    transform .45s cubic-bezier(.22, 1, .36, 1),
    color .2s ease,
    background-color .25s ease;
}

.is-ready .nav-links a { opacity: .65; transform: none; }
.is-ready .nav-links a:hover,
.is-ready .nav-links a[aria-current="page"] { opacity: 1; }

.is-ready .nav-links a:nth-child(1) { transition-delay: .30s; }
.is-ready .nav-links a:nth-child(2) { transition-delay: .38s; }
.is-ready .nav-links a:nth-child(3) { transition-delay: .46s; }
.is-ready .nav-links a:nth-child(4) { transition-delay: .54s; }

/* Those staggers are the entrance only. They are set per link and never
   cleared, so without this every hover afterwards waits out the link's
   own entrance delay — up to .54s before the tint or the text responds.
   Matching specificity, declared later, so it wins; the fold-back staggers
   below sit at .is-ready.is-collapsed and are more specific, so the bar
   still folds into the chip one link at a time. */
.is-settled .nav-links a:nth-child(n) { transition-delay: 0s; }

/* the CTA lands last, just past the pill's far edge */
.is-splitting .cta { opacity: 0; transform: translateX(14px); }

.cta { transition: opacity .5s ease .6s, transform .5s cubic-bezier(.22, 1, .36, 1) .6s; }

.is-ready .cta { opacity: 1; transform: none; }

/* past the entrance, the CTA answers hover at once — without this it would
   inherit the intro's .6s delay every time it came back */
.is-settled .cta {
  transition: opacity .3s ease, transform .3s cubic-bezier(.22, 1, .36, 1);
}

/* ---- and folds back into the chip once the page is scrolling ----
   Same clip, run in reverse. The links leave outermost-first so the bar
   looks like it is being drawn back into the chip rather than fading. */

.is-ready.is-collapsed .nav-pill {
  clip-path: inset(0 calc(100% - var(--nav-collapsed, 140px)) 0 0 round 100px);
  transition-delay: .12s;
}

.is-ready.is-collapsed .nav-links a {
  opacity: 0;
  transform: translateX(-10px);
  transition-duration: .3s;
}

.is-ready.is-collapsed .nav-links a:nth-child(1) { transition-delay: .18s; }
.is-ready.is-collapsed .nav-links a:nth-child(2) { transition-delay: .12s; }
.is-ready.is-collapsed .nav-links a:nth-child(3) { transition-delay: .06s; }
.is-ready.is-collapsed .nav-links a:nth-child(4) { transition-delay: 0s; }

.is-ready.is-collapsed .nav-brand { box-shadow: none; }

/* hovering the folded chip pulls the bar back open */
.is-ready.is-collapsed .nav-stack:hover .nav-pill,
.is-ready.is-collapsed .nav-stack:focus-within .nav-pill {
  clip-path: inset(0 0 0 0 round 100px);
  transition-delay: 0s;
}

.is-ready.is-collapsed .nav-stack:hover .nav-brand,
.is-ready.is-collapsed .nav-stack:focus-within .nav-brand {
  box-shadow: 0 4px 14px rgba(30, 111, 217, .32);
}

.is-ready.is-collapsed .nav-stack:hover .nav-links a,
.is-ready.is-collapsed .nav-stack:focus-within .nav-links a {
  opacity: .65;
  transform: none;
  transition-duration: .4s;
}

.is-ready.is-collapsed .nav-stack:hover .nav-links a:hover,
.is-ready.is-collapsed .nav-stack:hover .nav-links a[aria-current="page"] { opacity: 1; }

.is-ready.is-collapsed .nav-stack:hover .nav-links a:nth-child(1) { transition-delay: .06s; }
.is-ready.is-collapsed .nav-stack:hover .nav-links a:nth-child(2) { transition-delay: .12s; }
.is-ready.is-collapsed .nav-stack:hover .nav-links a:nth-child(3) { transition-delay: .18s; }
.is-ready.is-collapsed .nav-stack:hover .nav-links a:nth-child(4) { transition-delay: .24s; }

.nav-links a:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 2px; border-radius: 100px; }
.nav-brand:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 100px; }

/* ------------------------------------------------------------------- CTA
   There is only one cobalt shape. At rest it is clipped back to a 38px
   disc at the left; on hover the clip opens to the full pill. The bars
   are a separate layer translated so they always sit centred on that
   clip's right edge — so the "disc travelling right" and the "fill
   spreading" are the same edge moving, not two objects in convoy. */

.cta {
  --cta-pad: 3px;
  --cta-disc: 38px;
  position: relative;
  display: flex;
  align-items: center;
  width: 152px;
  height: var(--nav-pill-h);
  padding: var(--cta-pad);
  border-radius: 100px;
  border: var(--border-glass);
  background: var(--glass-nav);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: var(--shadow-glass);
  overflow: hidden;
}

/* the single cobalt shape — a disc at rest, the whole pill on hover */
.cta-fill {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  inset: var(--cta-pad);
  border-radius: 100px;
  background: var(--cobalt);
  clip-path: inset(0 calc(100% - var(--cta-disc)) 0 0 round 100px);
  transition: clip-path .45s cubic-bezier(.22, 1, .36, 1);
}

.cta:hover .cta-fill,
.cta:focus-visible .cta-fill { clip-path: inset(0 0 0 0 round 100px); }

/* carries the bars along the clip's right edge; draws nothing itself */
.cta-mark {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  top: var(--cta-pad);
  left: var(--cta-pad);
  width: var(--cta-disc);
  height: calc(100% - (var(--cta-pad) * 2));
  transition: transform .45s cubic-bezier(.22, 1, .36, 1);
}

/* travel the full inner width, less the disc's own width */
.cta:hover .cta-mark,
.cta:focus-visible .cta-mark {
  transform: translateX(calc(100% * ((152px - (var(--cta-pad) * 2)) / var(--cta-disc)) - 100%));
}

/* Two fixed copies of the label rather than one that travels: the resting
   copy sits to the right of the disc, the hover copy sits where the disc
   is headed. Crossfading between them reads as the pair trading places,
   without the sliding text that animating one copy produces. */
.cta-label {
  position: absolute;
  z-index: 2;
  top: 50%;
  display: block;
  text-align: center;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .02em;
  line-height: 1.4;
  white-space: nowrap;
  transition:
    opacity .28s ease,
    transform .45s cubic-bezier(.22, 1, .36, 1);
}

.cta-label-rest {
  left: calc(var(--cta-pad) + var(--cta-disc) + 6px);
  right: 9px;
  color: var(--ink);
  opacity: 1;
  transform: translateY(-50%);
}

.cta-label-over {
  left: 9px;
  pointer-events: none;
  right: calc(var(--cta-pad) + var(--cta-disc) + 6px);
  color: var(--ground);
  opacity: 0;
  transform: translateY(-50%) translateX(8px);
}

.cta:hover .cta-label-rest,
.cta:focus-visible .cta-label-rest {
  opacity: 0;
  transform: translateY(-50%) translateX(-8px);
}

.cta:hover .cta-label-over,
.cta:focus-visible .cta-label-over {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}

/* stepped pixel loop, pinned to the disc so it rides along with it.
   Stands in for Spartan's 9-frame sprite. */
.cta-bars {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.cta-bars i {
  width: 2px;
  height: 15px;
  border-radius: 1px;
  background: var(--ground);
  transform-origin: 50% 50%;
  animation: flow-bars 1.15s steps(3, end) infinite;
}

.cta-bars i:nth-child(1) { animation-delay: 0ms; }
.cta-bars i:nth-child(2) { animation-delay: 130ms; }
.cta-bars i:nth-child(3) { animation-delay: 260ms; }
.cta-bars i:nth-child(4) { animation-delay: 390ms; }
.cta-bars i:nth-child(5) { animation-delay: 520ms; }

@keyframes flow-bars {
  0%, 100% { transform: scaleY(.34); }
  50%      { transform: scaleY(1); }
}

.cta:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; }

/* ----------------------------------------------------------------- content */

.hero-inner {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  /* Keep the introduction and its action in one compact reading group. */
  gap: 24px;
  padding: 132px var(--hero-pad-x) 336px;
  /* content lifts and clears within act one */
  transform: translateY(calc(-48px * var(--pa, 0)));
  opacity: calc(1 - (1.02 * var(--pa, 0)));
  will-change: transform, opacity;
}

.hero-lead {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

/* A broad headline above a consistent, narrower supporting column. */
.hero-title {
  max-width: 960px;
  font-size: clamp(35px, 5.2vw, 74px);
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.04em;
  line-height: 1.08;
  text-wrap: balance;
  color: var(--on-dark);
}

.hero-sub {
  max-width: 420px;
  font-size: 17px;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: -.01em;
  text-wrap: pretty;
  color: rgba(255, 255, 255, .88);
}

.hero-lead > .ask { max-width: 420px; }

/* the quieter second way in, for people who would rather talk than type */
.hero-alt { margin-top: -10px; font-size: 14px; font-weight: 500; color: rgba(255, 255, 255, .78); }
.hero-alt a { color: var(--on-dark); text-decoration: underline; text-decoration-color: rgba(255, 255, 255, .45); text-underline-offset: 3px; transition: text-decoration-color .2s; }
.hero-alt a:hover { text-decoration-color: currentColor; }
.hero-alt a:focus-visible { outline: 2px solid var(--on-dark); outline-offset: 3px; border-radius: 3px; }

/* Hero layer scale (inside .hero-frame's isolation context, DOM order =
   paint order): 0 painting · 1 statement · 2 copy · 3 foot.
   The copy and foot wrappers are full-frame boxes over the statement, so
   only their real content takes the pointer. Once act one has faded a
   group to opacity 0, hero.js adds .is-copy-cleared / .is-foot-cleared and
   that content stops too, so nothing invisible can be clicked. The ticker
   is left out on purpose: its :hover pause would otherwise change where
   its loop is when the foot fades back in. */
.hero-inner, .hero-lead,
.hero-foot, .hero-foot-in, .hero-foot-row { pointer-events: none; }
.hero-title, .hero-sub, .hero-lead > .ask,
.hero-trust, .hero-foot .case, .hero-foot .ticker, .hero-alt { pointer-events: auto; }
.hero.is-copy-cleared .hero-title,
.hero.is-copy-cleared .hero-sub,
.hero.is-copy-cleared .hero-lead > .ask,
.hero.is-copy-cleared .hero-alt,
.hero.is-foot-cleared .hero-trust,
.hero.is-foot-cleared .hero-foot .case,
.hero.is-foot-cleared .hero-foot .ticker { pointer-events: none; }
.hero.is-statement-live .hero-statement { pointer-events: auto; }

/* ------------------------------------------------------- case study card
   Kora's small case-study card: a 450px pill-cornered shell at 10px
   padding, split 1 : 1.75 between a stretched image and a text panel,
   with the metric row divided off by a hairline. Surfaces follow the
   Flow Source glass rule rather than Kora's cream. */

.case {
  flex: none;
  /* line the card up with the content column's right edge, not the rail's */
  margin-right: calc(var(--hero-pad-x) - 40px);
  display: flex;
  align-items: stretch;
  gap: 10px;
  width: 450px;
  max-width: 100%;
  padding: 10px;
  border-radius: var(--radius-panel, 24px);
  background: var(--glass);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: var(--shadow-glass);
  transition: transform .4s cubic-bezier(.22, 1, .36, 1);
}

.case:hover { transform: translateY(-4px); }

.case-media {
  flex: 1 0 0;
  width: 1px;
  border-radius: var(--radius-card, 16px);
  overflow: hidden;
}

.case-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s cubic-bezier(.22, 1, .36, 1);
}

.case:hover .case-media img { transform: scale(1.05); }

.case-body {
  flex: 1.75 0 0;
  width: 1px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-radius: var(--radius-card, 16px);
  background: var(--ground);
}

.case-top {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  padding: 20px;
}

.case-tag {
  padding: 5px 9px;
  border-radius: 30px;
  background: var(--cobalt);
  color: var(--ground);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.case-head {
  max-width: 300px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -.02em;
  text-wrap: balance;
  color: var(--ink);
}

.case-metric {
  display: flex;
  align-items: baseline;
  gap: 11px;
  padding: 18px 20px;
  border-top: 1px solid var(--rule);
}

.case-metric strong {
  font-size: 26px;
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.04em;
  line-height: 1;
  color: var(--cobalt);
}

.case-metric em {
  font-style: normal;
  font-size: 13px;
  line-height: 1.3;
  color: var(--slate);
}

/* "scale" variant: one solid person grows into a team, filling in on hover */
.case-metric--scale { align-items: center; gap: 14px; }

.case-scale {
  display: flex;
  align-items: flex-end;
  flex: none;
}

.case-scale i {
  width: 16px;
  height: 16px;
  background: var(--rule);
  /* person glyph: head + shoulders, painted by background-color */
  -webkit-mask: var(--person) center / contain no-repeat;
          mask: var(--person) center / contain no-repeat;
  --person: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='7.5' r='4.5'/%3E%3Cpath d='M3 22a9 9 0 0 1 18 0z'/%3E%3C/svg%3E");
  transition: background-color .35s ease, transform .45s cubic-bezier(.22, 1, .36, 1);
}

.case-scale i + i { margin-left: 1px; }
.case-scale i:first-child { width: 22px; height: 22px; background: var(--cobalt); margin-right: 6px; }
.case-scale i:nth-child(3) { transition-delay: .05s; }
.case-scale i:nth-child(4) { transition-delay: .1s; }
.case-scale i:nth-child(5) { transition-delay: .15s; }

.case:hover .case-scale i:not(:first-child),
.case:focus-visible .case-scale i:not(:first-child) {
  background: var(--cobalt);
  transform: translateX(2px);
}

.case-metric--scale em { font-size: 13px; line-height: 1.35; }
.case-metric--scale b { font-weight: 600; color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .case-scale i { transition: none; }
}

.case:focus-visible { outline: 2px solid var(--ground); outline-offset: 4px; }

/* --------------------------------------------------------- ask composer
   The "Ask Input" from the Paper landing file, brought down to the nav
   pill's height so the two read as one family: same 44px, same 5px inner
   padding, same relationship between shell and the round thing inside it.
   Live input rather than typeset copy — the placeholder types itself, and
   stops the moment anyone starts typing. */

.ask {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  max-width: 370px;
  height: var(--nav-pill-h);
  padding: 5px 5px 5px 20px;
  border-radius: var(--radius-pill, 999px);
  background: rgba(255, 255, 255, .90);
  border: 1px solid rgba(15, 42, 68, .15);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: var(--shadow-glass);
  transition: border-color .25s ease, box-shadow .25s ease;
}

.ask:focus-within {
  border-color: rgba(30, 111, 217, .45);
  box-shadow: 0 8px 30px rgba(15, 42, 68, .12);
}

/* The animated prompt is drawn by a ghost layer, not by the input's own
   placeholder: the input is flex:1, so a caret sitting after it landed at
   the far right of the field instead of against the text. The ghost puts
   text and caret in normal flow, so they stay welded together. */
.ask-field {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
}

.ask-ghost {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 3px;
  pointer-events: none;
  overflow: hidden;
}

.ask-ghost-text {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  line-height: 22px;
  letter-spacing: -.01em;
  white-space: nowrap;
  color: var(--slate);
}

.ask:focus-within .ask-ghost,
.ask.is-typed .ask-ghost { display: none; }

.ask-input {
  position: relative;
  z-index: 1;
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: none;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  line-height: 22px;
  letter-spacing: -.01em;
  color: var(--ink);
}

.ask-input::placeholder { color: transparent; }
.ask-input:focus { outline: none; }

/* the resting caret; the browser draws its own once the field is live */
.ask-caret {
  flex: none;
  width: 2px;
  height: 18px;
  border-radius: 1px;
  background: var(--cobalt);
  animation: caret-blink .9s steps(1, end) infinite;
}



.ask-send {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill, 999px);
  background: var(--cobalt);
  color: var(--ground);
  cursor: pointer;
  transition: transform .3s cubic-bezier(.22, 1, .36, 1);
}

.ask-send:hover { transform: scale(1.06); }
.ask-send:active { transform: scale(.96); }

/* same grammar as the nav CTA: two fixed copies swapping rather than one
   element moving, so the arrow reads as replaced, not nudged */
.ask-send-icon {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  overflow: hidden;
}

.ask-send-icon svg {
  grid-area: 1 / 1;
  transition: transform .42s cubic-bezier(.22, 1, .36, 1), opacity .28s ease;
}

.ask-send-icon svg:nth-child(2) { opacity: 0; transform: translateY(150%); }

.ask-send:hover .ask-send-icon svg:nth-child(1),
.ask-send:focus-visible .ask-send-icon svg:nth-child(1) {
  opacity: 0;
  transform: translateY(-150%);
}

.ask-send:hover .ask-send-icon svg:nth-child(2),
.ask-send:focus-visible .ask-send-icon svg:nth-child(2) {
  opacity: 1;
  transform: none;
}

.ask-send:focus-visible { outline: 2px solid var(--ground); outline-offset: 3px; }

/* ------------------------------------------------------------ logo ticker
   Spartan's bottom block: a trust line capped at 300px over a masked,
   continuously scrolling rail. Tiles 189px wide, gap 10. */

/* The scroll fade lives on the outer element and the entrance animation on
   the inner one. They cannot share a node: an entrance that ends in
   `forwards` holds opacity at animation precedence, which silently beats
   any opacity the scroll sets. */
.hero-foot {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: -10px;
  padding: 0 40px 50px;
  opacity: calc(1 - (1.12 * var(--pa, 0)));
}

/* the trust line and the case card share this row, bottom-aligned, so the
   card's lower edge and the copy's last line sit on the same lane */
.hero-foot-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 64px;
  width: 100%;
}

.hero-foot-in {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* the block is anchored to the frame's bottom, so widening this gap
     lifts the trust line while the rail stays put */
  gap: 36px;
}

.hero-trust {
  /* pull the copy onto the headline's left edge; the rail stays full width */
  margin-left: calc(var(--hero-pad-x) - 40px);
  max-width: 320px;
  flex: none;
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: -.01em;
  color: var(--on-dark-72);
}

.hero-trust strong {
  display: block;
  margin-bottom: 8px;
  color: var(--on-dark);
  font-weight: 600;
}

.ticker {
  width: 100%;
  overflow: hidden;
  border-radius: 10px;
  -webkit-mask: linear-gradient(90deg, #0000 0%, #000 8.03421% 92.0098%, #0000 100%);
          mask: linear-gradient(90deg, #0000 0%, #000 8.03421% 92.0098%, #0000 100%);
}

.ticker-track {
  display: flex;
  align-items: center;
  gap: var(--ticker-gap);
  width: max-content;
  animation: ticker-scroll 38s linear infinite;
}

.ticker:hover .ticker-track { animation-play-state: paused; }

.ticker-track li { flex: none; list-style: none; }

.ticker-track img {
  /* Size by height, never width: the sources top out at 180px wide, so a
     fixed 189px slot was upscaling every mark. Locking the cap height
     downscales all of them, which is what actually sharpens the rail, and
     lets each wordmark keep its true width so the gaps read evenly. */
  height: 22px;
  width: auto;
  object-fit: contain;
  /* the marks ship at mixed greys; flatten then invert so every one of
     them lands on pure white, with alpha preserved */
  filter: brightness(0) invert(1);
  opacity: .92;
  transition: opacity .3s ease;
}

.ticker-track li:hover img { opacity: 1; }

/* Match visible artwork heights; HighLevel’s SVG has been trimmed to its mark. */
.ticker-track img[src$="cargowise.svg"],
.ticker-track img[src$="roofr.svg"] { height: 28px; }
.ticker-track img[src$="gohighlevel.svg"] { height: 30px; }

/* Two identical runs, so one full run is -50% plus half a gap. */
@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - (var(--ticker-gap) / 2))); }
}

/* ------------------------------------------------------- split-text reveal */

.split-word { white-space: nowrap; }

.split-unit {
  display: inline-block;
  opacity: 0;
  filter: blur(9px);
  transform: translateY(16px);
  will-change: opacity, filter, transform;
}

.is-ready .split-unit {
  animation: blur-in .85s cubic-bezier(.22, 1, .36, 1) forwards;
  animation-delay: var(--d, 0ms);
}

.is-ready .split-unit.is-revealed {
  animation: none;
  opacity: 1;
  filter: none;
  transform: none;
  will-change: auto;
}

@keyframes blur-in {
  to { opacity: 1; filter: blur(0); transform: none; }
}

.is-splitting [data-reveal] { opacity: 0; transform: translateY(14px); }

.is-ready [data-reveal] {
  animation: rise-in .8s cubic-bezier(.22, 1, .36, 1) forwards;
  animation-delay: var(--d, 0ms);
}

@keyframes rise-in {
  to { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ next section */

.after {
  position: relative;
  z-index: 1;
  background: var(--newsprint);
  color: var(--ink);
  padding: 120px var(--hero-pad-x);
  /* The next section enters during the end of the statement's slower
     settling phase, while the text remains clear of its upper edge. */
  margin-top: -18vh;
  border-radius: 28px 28px 0 0;
}

.after-eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--cobalt);
}

.after-title {
  max-width: 900px;
  margin-top: 18px;
  font-size: clamp(28px, 3.6vw, 48px);
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.04em;
  line-height: 1.08;
  text-wrap: balance;
}

/* on white, hairline rules separate content — boxes are rare */
.after-grid {
  margin-top: 56px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 32px;
}

.after-grid h3 {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.02em;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--rule);
}

.after-grid p {
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--slate);
}

/* ----------------------------------------------------------- comparison
   Two mechanisms live in this section and they must not be confused.

   The base rules below are the STATIC layout: heading above, cards side by
   side, everything at rest. That is what phones get, what reduced-motion
   gets, and what is left if the script never runs — a finished section
   rather than a blank screen.

   .is-scrub, added by hero.js only on a wide viewport with motion allowed,
   converts the same markup into a pinned stage driven by one progress
   value --p (0 -> 1 across the screen of scroll after the stage pins):

     heading   opacity 1 -> 0, thrown outward to +/-2000px, rotateY +/-60deg,
               scaling up to 1.5, under perspective(1200px)
     cards     opacity 0 -> 1, scale .7 -> 1

   Both read the same --p with no easing of any kind, so the heading leaving
   and the cards arriving are one continuous exchange rather than two
   animations that happen to overlap. Any ease applied to one and not the
   other breaks the effect immediately. */

.compare {
  --compare-max: 1180px;

  position: relative;
  z-index: 1;
  background: var(--newsprint);
  color: var(--ink);
  padding: 132px var(--hero-pad-x) 148px;
}

.compare-stage {
  max-width: var(--compare-max);
  margin: 0 auto;
}

.compare-title {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  font-size: clamp(35px, 6.7vw, 80px);
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.045em;
  line-height: 1.04;
  text-wrap: balance;
}

.compare-line-b { color: var(--slate); }
.compare-brand { color: var(--cobalt); }

.compare-cards {
  margin-top: 72px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

/* ------------------------------------------------- the pinned stage */

.compare.is-scrub {
  /* One screen of pinned stage, then a ruler per move: the first screen
     scrubs the heading out and the cards in, the second screen's midpoint
     fires the split, the third's fires the focus, and the last half screen
     is run-out so the pair is not snatched away the instant it settles. */
  height: 450vh;
  padding: 0 var(--hero-pad-x);
}

.compare.is-scrub .compare-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 660px;
  display: grid;
  place-items: center;
  /* Let the split heading cross the content gutters. The page already
     contains horizontal overflow at the viewport edge. */
  overflow: visible;
}

/* Two transforms on two nodes, and they must stay on two nodes.

   The WRAPPER runs the entry — the heading rises 300px into the middle of
   the stage, easing down from 1.1 to its true size as it fades up. The
   LINES run the exit. Keeping them separate is what lets the exit rewrite
   its own transform without touching the centring, and vice versa.

   Composition order is load-bearing: the centring translate comes FIRST
   and the animated functions are appended after it. Because scale() is
   applied after translate(-50%, -50%), it does not multiply the centring
   offset, so the block stays dead centre at every scale value. Folding
   the y into the centring translate gives a visibly different — wrong —
   result.

   --e defaults to 1, the settled state, so the static layout and a blocked
   script both get a heading that is simply present. */
.compare.is-scrub .compare-title {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(100%, var(--compare-max));
  transform: translate(-50%, -50%)
             translateY(calc(-300px * (1 - var(--e, 1))))
             scale(calc(1 + (.1 * (1 - var(--e, 1)))));
  opacity: var(--e, 1);
  pointer-events: none;
  will-change: transform, opacity;
  z-index: 2;
}

/* The title wrapper is pointer-events:none so its box never blocks the
   cards; the lines themselves take hits again so the visible heading stays
   selectable. Once thrown (p=1) they sit far off-screen. */
.compare.is-scrub .compare-line {
  pointer-events: auto;
  display: block;
  opacity: calc(1 - var(--p, 0));
  will-change: transform, opacity;
}

/* mirrored: one line is thrown left and turned away from the viewer, the
   other right, so the heading splits along its own centre line */
.compare.is-scrub .compare-line-a {
  transform: perspective(1200px)
             translateX(calc(-2000px * var(--p, 0)))
             rotateY(calc(-60deg * var(--p, 0)))
             scale(calc(1 + (.5 * var(--p, 0))));
}

.compare.is-scrub .compare-line-b {
  transform: perspective(1200px)
             translateX(calc(2000px * var(--p, 0)))
             rotateY(calc(60deg * var(--p, 0)))
             scale(calc(1 + (.5 * var(--p, 0))));
}

/* --fit is measured by hero.js: the largest scale at which the pair still
   fits inside the pinned screen, capped at 1. It is what stops the cards
   from growing past the fold as they arrive — the zoom ends at whatever
   size actually fits rather than always at full size, so nothing is ever
   left hanging below the viewport. On a tall screen it is simply 1 and
   changes nothing. */
.compare.is-scrub .compare-cards {
  margin-top: 0;
  width: min(100%, var(--compare-max));
  opacity: var(--p, 0);
  transform: scale(calc((.7 + (.3 * var(--p, 0))) * var(--fit, 1)));
  will-change: transform, opacity;
}
/* at p=0 the cards are fully transparent; keep them out of hit-testing */
.compare.is-scrub.is-cards-hidden .compare-cards { pointer-events: none; }

/* The cards follow two eased scroll ramps. They open into a comparison,
   hold side by side, then close into the final stack. No timed transform
   transitions: stopping or reversing the scroll keeps the cards in step. */
.compare.is-scrub .cmp-card {
  --stack: calc(1 - var(--spread, 0) + var(--focus, 0));
  will-change: transform;
}

.compare.is-scrub .cmp-card-before {
  z-index: 1;
  transform: translateX(calc((50% + 10px) * var(--stack)))
             translate(calc(34px * var(--focus, 0)), calc(30px * var(--focus, 0)))
             scale(calc(1 - .06 * var(--focus, 0)));
}

.compare.is-scrub .cmp-card-after {
  z-index: 0;
  transform: translateX(calc((-50% - 10px) * var(--stack)))
             scale(calc(.8 + .2 * var(--spread, 0)));
}

.compare.is-scrub .cmp-card-before .cmp-inside {
  opacity: calc(1 - .54 * var(--focus, 0));
}

/* Change stacking order only while the cards are fully separated. This
   keeps the handoff invisible in either scroll direction. */
.compare.is-scrub.is-focus .cmp-card-before { z-index: 0; }
.compare.is-scrub.is-focus .cmp-card-after { z-index: 1; }

/* ------------------------------------------------------------- cards */

.cmp-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: clamp(460px, 66vh, 580px);
  padding: clamp(32px, 4.5vh, 48px) 44px;
  border-radius: 32px;
}

/* Blur the photograph alone; the scrim and live text stay sharp. */
.cmp-card::before,
.cmp-card::after {
  content: "";
  position: absolute;
  pointer-events: none;
  z-index: -1;
}

.cmp-card::before {
  inset: -12px;
  background: var(--cmp-photo) center / cover no-repeat;
  filter: blur(3px) saturate(.65);
}

.cmp-card::after {
  inset: 0;
  background: linear-gradient(180deg, rgba(16, 25, 34, .64), rgba(16, 25, 34, .48) 38%, rgba(16, 25, 34, .92));
}

.cmp-card-after::before { filter: blur(3px) saturate(.85); }
.cmp-card-after::after {
  background: linear-gradient(180deg, rgba(12, 52, 103, .72), rgba(15, 67, 132, .48) 38%, rgba(9, 42, 84, .95));
}

/* one layer inside the card, so the split can dim all of its contents at
   once without touching the card's own surface */
.cmp-inside {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

/* Opaque fallback surfaces also prevent bleed-through during the stack. */
.cmp-card-before {
  --cmp-photo: url("assets/compare-before.jpg");
  background: #18212a;
  color: var(--on-dark);
  border: 1px solid rgba(255, 255, 255, .12);
}

/* The cobalt surface makes the outcome the focal point of the pair. */
.cmp-card-after {
  --cmp-photo: url("assets/compare-after.jpg");
  background: var(--cobalt);
  color: var(--on-dark);
  border: none;
  box-shadow: var(--shadow-glass);
}

/* While the pair is stacked, the After card sits concentric BEHIND the
   Before card at .8 — smaller, so the card itself is hidden, but a 60px
   blur offset 24px down still spills out past the Before card's edges as
   a dark smudge with no object attached to it. It has no shadow until it
   is a card you can actually see. */
.compare.is-scrub .cmp-card-after {
  box-shadow: 0 8px 30px rgba(15, 42, 68, calc(.08 * var(--spread, 0)));
}

.cmp-head {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.cmp-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.cmp-card-before .cmp-label { color: var(--on-dark); }
.cmp-card-after  .cmp-label { color: var(--on-dark); }
.cmp-card-after .cmp-mark { filter: brightness(0) invert(1); }

.cmp-mark { width: 16px; height: 16px; }

.cmp-claim {
  max-width: 13ch;
  font-size: clamp(30px, 3.3vw, 46px);
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.03em;
  line-height: 1.12;
}

.cmp-card-before .cmp-claim { color: var(--on-dark); }

/* The rows carry no entrance of their own. An earlier pass staggered them
   in and it fought the zoom: the cards were still growing while their
   contents were separately arriving, which read as two events. They ride
   the card. */
.cmp-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: auto;
  list-style: none;
}

.cmp-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: clamp(8px, 1.35vh, 13px) 0;
}


.cmp-badge {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  margin-top: 1px;
  border-radius: 100px;
}

.cmp-card-before .cmp-badge {
  background: rgba(255, 255, 255, .14);
  color: var(--on-dark);
}

.cmp-card-after .cmp-badge {
  background: var(--ground);
  color: var(--cobalt);
}

.cmp-text {
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -.01em;
}

.cmp-card-before .cmp-text { color: var(--on-dark); }
.cmp-card-after  .cmp-text { color: var(--on-dark); }

/* ------------------------------------------------------------- breakpoints */

@media (max-width: 1199.98px) {
  :root { --hero-pad-x: 48px; }
  .hero-inner { padding-top: 132px; }
  .hero-foot { padding: 0 28px 44px; }
  .hero-trust { margin-left: calc(var(--hero-pad-x) - 28px); }
  .case { margin-right: calc(var(--hero-pad-x) - 28px); }
  /* .is-scrub is never added below 1200px, so only the static layout
     needs tuning here */
  .compare { padding: 104px var(--hero-pad-x) 112px; }
  .cmp-card { padding: 36px 32px 40px; }
  .nav-links { display: none; }
  .nav-pill { gap: 0; padding: 5px; }
  .nav-ask { display: none; }
}

@media (max-width: 809.98px) {
  :root { --hero-pad-x: 20px; --nav-inset: 16px; --ticker-gap: 30px; }

  .hero-scroll {
    --settle-screens: .24;
    height: calc(210vh + var(--settle-screens) * 100vh);
  }
  .hero { min-height: 560px; }

  .hero-inner {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    gap: 20px;
    padding: 110px var(--hero-pad-x) 170px;
  }

  .hero-foot-row { gap: 0; }

  /* no room for it beside a 3-line headline at this height */
  .case { display: none; }

  .hero-lead { gap: 24px; }
  .hero-copy { gap: 20px; }
  .hero-sub { font-size: 15px; }

  .hero-actions { gap: 10px; }

  .cta { width: 140px; --cta-disc: 35px; }
  .cta:hover .cta-mark,
  .cta:focus-visible .cta-mark {
    transform: translateX(calc(100% * ((140px - (var(--cta-pad) * 2)) / var(--cta-disc)) - 100%));
  }

  .hero-foot { padding: 0 20px 24px; }
  .hero-foot-in { gap: 24px; }
  .hero-trust { margin-left: 0; }
  .ticker-track img { height: 18px; }
  .ticker-track img[src$="cargowise.svg"],
  .ticker-track img[src$="roofr.svg"] { height: 23px; }
  .ticker-track img[src$="gohighlevel.svg"] { height: 25px; }
  /* the whole sticky scroll is much shorter here, so the overlap that
     matches the drift is proportionally smaller */
  .after { padding: 72px var(--hero-pad-x); margin-top: -10vh; }
  /* Match the mobile hero's 560px minimum height. */
  .hero-statement-line { --drift-dist: calc(max(50vh, 280px) - var(--frame-inset) - 50% - 16px); }

  /* side by side stops being a comparison and starts being two columns of
     6pt text; stacked, the two claims still read against each other */
  .compare { padding: 76px var(--hero-pad-x) 84px; }
  .compare-cards { grid-template-columns: 1fr; gap: 16px; margin-top: 44px; }
  .cmp-card { min-height: 460px; padding: 30px 24px 34px; gap: 26px; }
  .cmp-claim { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .split-unit,
  .is-splitting [data-reveal] { opacity: 1; filter: none; transform: none; animation: none !important; }

  .hero-bg-media { transform: none; filter: none; }
  .hero-inner { transform: none; opacity: 1; }
  .hero-corners { opacity: 0; }
  /* no scroll driver runs, so --pb never moves and the line would sit
     invisible on top of the hero copy — drop it instead */
  .hero-statement { display: none; }

  .is-splitting .nav-pill,
  .is-ready.is-collapsed .nav-pill { clip-path: none; }
  .is-splitting .nav-links a,
  .is-ready.is-collapsed .nav-links a,
  .is-splitting .cta { opacity: 1; transform: none; }
  .nav-pill, .nav-links a, .cta { transition: none; }

  .cta-fill,
  .cta-mark,
  .cta-label { transition: none; }
  .cta-bars i { animation: none; transform: scaleY(.7); }
  .ticker-track { animation: none; }
  .ask-caret { animation: none; }

  /* hero.js withholds .is-scrub here, so the static layout stands; this
     is belt and braces in case the class is ever applied some other way */
  .compare.is-scrub { height: auto; padding: 104px var(--hero-pad-x) 112px; }
  .compare.is-scrub .compare-stage { position: static; height: auto; overflow: visible; }
  .compare.is-scrub .compare-title { position: static; transform: none; width: auto; }
  .compare.is-scrub .compare-line { opacity: 1; transform: none; }
  .compare.is-scrub .compare-cards { opacity: 1; transform: none; margin-top: 72px; }
  /* Keep both cards fully readable if scrub styles are applied here. */
  .compare.is-scrub .cmp-card { transform: none; transition: none; }
  .compare.is-scrub .cmp-inside { opacity: 1; transition: none; }
  .ask-send-icon svg { transition: none; }
}

/* ============================================================ pre-roll
   Transcribed from the Paper file "03 · Splash Screens", with one
   departure: the sans is Manrope tuned exactly like .hero-title rather
   than the artboards' Inter Tight, so the pre-roll and the headline it
   hands off to are the same voice. The emphasis word keeps Instrument
   Serif italic, set 8% larger so it optically matches the sans.
   Beat 01-02 sit on the navy plate in white + sky; beat 03 opens the
   painting and switches to ink + cobalt. */

.splash {
  position: fixed;
  z-index: 100;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--ink);
  opacity: 1;
  transition: opacity .6s ease;
}

.splash.is-leaving { opacity: 0; pointer-events: none; }

/* the page underneath is held still until the pre-roll lifts */
body.splash-active { overflow: hidden; }

.splash-plate { position: absolute; inset: 0; }

.splash-tex,
.splash-horizon {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}

.splash-tex { background-image: url("assets/splash-bg.jpg"); }

/* beat 03 — the horizon opens from the centre line outward */
.splash-horizon {
  background-image: url("assets/hero.jpg");
  background-position: center 58%;
  opacity: 0;
  clip-path: inset(50% 0 50% 0);
  transition: opacity .5s ease, clip-path .9s cubic-bezier(.22, 1, .36, 1);
}

.splash.is-light .splash-horizon { opacity: 1; clip-path: inset(0 0 0 0); }
.splash.is-light .splash-tex { opacity: 0; transition: opacity .6s ease; }

.splash-lane {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0;
  max-width: 100%;
  padding: 0 24px;
  white-space: pre;
  font-family: var(--font-display);
  font-size: clamp(34px, 7.1vw, 104px);
  font-weight: 600;
  font-variation-settings: "wght" 585;
  letter-spacing: -.04em;
  line-height: 1;
  color: #FFFFFF;
}

.splash.is-light .splash-lane { color: var(--ink); }

/* the serif runs 8% larger than the sans, exactly as the artboards set it */
.splash-em {
  font-family: "Instrument Serif", Georgia, serif;
  font-size: 1.077em;
  font-style: italic;
  font-weight: 400;
  color: var(--sky);
}

.splash.is-light .splash-em { color: var(--cobalt); }

.splash-caret {
  flex: none;
  width: 4px;
  height: .865em;
  margin-left: .04em;
  background: var(--sky);
  transform: translateY(.06em);
}

.splash.is-light .splash-caret { background: var(--cobalt); }

/* the caret only blinks while the line is being held, never mid-keystroke */
.splash.is-holding .splash-caret { animation: caret-blink .9s steps(1, end) infinite; }

@keyframes caret-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@media (max-width: 809.98px) {
  .splash-lane { padding: 0 20px; }
  .splash-caret { width: 3px; }
}

@media (prefers-reduced-motion: reduce) {
  .splash { display: none; }
  body.splash-active { overflow: auto; }
}

/* ---------------------------------------------------------------- closing band
   The last ask before the footer: an ink band in the same inset frame as the
   rest of the page, one statement and the call button. */

.closing { position: relative; padding: 12px; background: var(--newsprint); }
.closing-in {
  max-width: 1600px;
  margin: 0 auto;
  padding: clamp(72px, 8vw, 130px) clamp(22px, 5vw, 72px) clamp(56px, 6vw, 96px);
  border-radius: 40px;
  background: var(--ink);
  color: var(--on-dark);
  overflow: hidden;
}
.closing-eyebrow { display: flex; align-items: center; gap: 20px; max-width: 520px; font: 400 12px/1.4 var(--font-mono); letter-spacing: .04em; text-transform: uppercase; color: var(--on-dark-72); }
.closing-eyebrow img { width: 34px; height: 24px; object-fit: contain; filter: brightness(0) invert(1); }
.closing-rule { flex: 1; height: 1px; background: var(--hairline); }
.closing-title { max-width: 1100px; margin-top: 40px; font-size: clamp(38px, 5.6vw, 84px); font-weight: 500; line-height: 1.02; letter-spacing: -.055em; text-wrap: balance; }
.closing-title em { font-family: "Instrument Serif", Georgia, serif; font-weight: 400; letter-spacing: -.02em; color: var(--sky); }
.closing-lead { display: flex; flex-direction: column; align-items: flex-start; gap: 28px; max-width: 360px; margin: clamp(36px, 4vw, 56px) 0 0 min(36%, 520px); }
.closing-lead p { color: var(--on-dark-72); font-size: 15px; line-height: 1.6; }
.closing-cta { display: inline-flex; align-items: center; gap: 16px; min-height: 48px; padding: 5px 22px 5px 5px; border-radius: 25px; background: var(--cobalt); color: #fff; font-size: 14px; font-weight: 600; transition: background .25s; }
.closing-cta-arrow { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 50%; background: #fff; color: var(--cobalt); }
.closing-cta-arrow svg { transition: transform .3s cubic-bezier(.22, 1, .36, 1); }
.closing-cta:hover { background: #1559b4; }
.closing-cta:hover .closing-cta-arrow svg { transform: translateX(3px); }
.closing-cta:focus-visible { outline: 2px solid #fff; outline-offset: 4px; }

/* ---------------------------------------------------------------- footer */
.site-footer { padding: 0 12px 12px; background: var(--newsprint); color: var(--ink); }
.site-footer-in {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 16px 32px;
  max-width: 1600px;
  margin: 0 auto;
  padding: 26px clamp(22px, 5vw, 72px);
  font-size: 13px;
}
.site-footer-brand { display: inline-flex; align-items: center; gap: 9px; font-size: 15px; font-weight: 600; letter-spacing: -.02em; }
.site-footer-tag { color: var(--slate); }
.site-footer-links { display: flex; flex-wrap: wrap; gap: 8px 22px; }
.site-footer-links a { color: var(--ink); text-decoration: underline; text-decoration-color: rgba(15, 42, 68, .25); text-underline-offset: 3px; transition: color .2s, text-decoration-color .2s; }
.site-footer-links a:hover { color: var(--cobalt); text-decoration-color: currentColor; }
.site-footer-legal { color: var(--slate); font: 400 11px/1.4 var(--font-mono); letter-spacing: .03em; }
.site-footer a:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; border-radius: 4px; }

@media (max-width: 900px) {
  .site-footer-in { grid-template-columns: 1fr; gap: 12px; padding-block: 30px 22px; }
  .closing-lead { margin-left: 0; }
}

/* ---------------------------------------------------------------- simple pages
   /privacy/ and the 404: a white sheet in the page's inset frame. */

body.doc-body { color: var(--ink); }
.doc-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; max-width: 1600px; margin: 0 auto; padding: 22px clamp(22px, 5vw, 72px); }
.doc-brand { display: inline-flex; align-items: center; gap: 9px; font-size: 15px; font-weight: 600; letter-spacing: -.02em; }
.doc-back { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 18px; border-radius: 25px; background: var(--ground); border: var(--border-glass); font-size: 13px; font-weight: 500; transition: background .2s; }
.doc-back:hover { background: var(--panel-tint); }
.doc-main { padding: 0 12px 12px; }
.doc-sheet { max-width: 1600px; margin: 0 auto; padding: clamp(56px, 7vw, 110px) clamp(22px, 5vw, 72px); border-radius: 40px; background: var(--ground); }
.doc-sheet > * { max-width: 720px; }
.doc-eyebrow { color: #526a80; font: 400 11px/1.5 var(--font-mono); letter-spacing: .055em; text-transform: uppercase; }
.doc-sheet h1 { margin-top: 16px; font-size: clamp(42px, 6vw, 84px); font-weight: 500; line-height: 1; letter-spacing: -.055em; }
.doc-sheet h1 em { font-family: "Instrument Serif", Georgia, serif; font-weight: 400; letter-spacing: -.02em; color: var(--cobalt); }
.doc-date { margin-top: 18px; color: var(--slate); font: 400 12px/1.5 var(--font-mono); }
.doc-lede { margin-top: 28px; color: #2b4560; font-size: clamp(18px, 1.6vw, 21px); line-height: 1.55; letter-spacing: -.02em; }
.doc-sheet h2 { margin-top: 48px; font-size: 22px; font-weight: 600; line-height: 1.25; letter-spacing: -.03em; }
.doc-sheet p + p, .doc-sheet h2 + p, .doc-sheet h2 + ul { margin-top: 12px; }
.doc-sheet section > p, .doc-sheet section li { color: #3b5571; font-size: 16px; line-height: 1.7; }
.doc-sheet section ul { padding-left: 20px; }
.doc-sheet section li + li { margin-top: 6px; }
.doc-sheet a:not(.doc-cta) { color: var(--cobalt); text-decoration: underline; text-underline-offset: 3px; }
.doc-cta { display: inline-flex; align-items: center; gap: 16px; margin-top: 36px; min-height: 48px; padding: 5px 22px 5px 5px; border-radius: 25px; background: var(--cobalt); color: #fff; font-size: 14px; font-weight: 600; transition: background .2s; }
.doc-cta:hover { background: #1559b4; }
.doc-cta .closing-cta-arrow svg { transform: rotate(180deg); }
.doc-body .site-footer { padding-top: 0; }
