/* ==========================================================================
   Kore: core: design tokens, reset, typography, buttons, animation utilities
   ========================================================================== */

/* ---------- Brand display face (self-hosted) ---------- */
@font-face {
  font-family: "Neuzeit Grotesk";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Reg.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Reg.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bol.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bol.woff") format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bla.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bla.woff") format("woff");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Lig.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Lig.woff") format("woff");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

/* Weight-named aliases. The feature-explorer CSS ported from the previous Kore
   site asks for "Neuzeit Grotesk Regular" / "Bold" as separate families, so
   these keep that stylesheet working verbatim. */
@font-face {
  font-family: "Neuzeit Grotesk Regular";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Reg.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Reg.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk Bold";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bol.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bol.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk Light";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Lig.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Lig.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neuzeit Grotesk Black";
  src:
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bla.woff2") format("woff2"),
    url("../fonts/NeuzeitGrotesk/NeuzeitGro-Bla.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Brand */
  --blue: #10537e;
  --blue-deep: #0a3b5c;
  --blue-900: #072a44;
  --cyan: #00b4e5;
  --cyan-soft: #6fdcff;
  --yellow: #ffbb00;

  /* Neutrals, the site is white-first */
  --white: #ffffff;
  --paper: #fbfcfe;
  --paper-2: #f4f7fb;
  --line: #e7ecf3;
  --line-2: #dde5ef;
  --ink: #0b1524;
  --ink-2: #2a3a4f;
  --muted: #5c6b80;
  --muted-2: #8593a6;

  /* Accent (overridden per product with --accent) */
  --accent: #10537e;

  /* Type */
  --font-display:
    "Neuzeit Grotesk", "Sora", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body:
    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* Space & shape */
  --container: 1400px;
  --gutter: 28px;
  --r-sm: 12px;
  --r: 18px;
  --r-lg: 26px;
  --r-xl: 34px;

  /* Elevation: switched off. Separation comes from borders and background
     tints instead; the brand direction is flat. Restoring depth is a matter of
     putting values back in these four tokens. */
  --sh-1: none;
  --sh-2: none;
  --sh-3: none;
  --sh-cyan: none;

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  /* Entrance curve: a long, gentle decelerate with no overshoot. Used by every
     reveal so arrivals across the site share one feel. */
  --ease-out-soft: cubic-bezier(0.16, 0.84, 0.3, 1);
  --t-fast: 0.18s;
  --t: 0.32s;
  --t-slow: 0.6s;

  --header-h: 76px;
  /* Where sticky sub-navs park. Normally just under the header; when the
     header slides away on scroll-down, JS flips this to 0 so nothing is left
     floating with an empty band above it. */
  --sticky-top: var(--header-h);
}

body.is-header-hidden {
  --sticky-top: 0px;
}

/* ---------- Reset ---------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 24px);
}

body {
  margin: 0;
  background: var(--white);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}
body.is-locked {
  overflow: hidden;
}

img,
svg,
video {
  max-width: 100%;
  display: block;
}
img {
  height: auto;
}
a {
  color: inherit;
  text-decoration: none;
}
button {
  font: inherit;
  color: inherit;
}
ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}
h1,
h2,
h3,
h4,
h5 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.08;
  color: var(--ink);
}
p {
  margin: 0;
}

::selection {
  background: rgba(0, 180, 229, 0.22);
  color: var(--ink);
}

:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  border-radius: 6px;
}
/* Form controls show focus by turning their own border cyan, so they opt out
   of the global outline rather than carrying two indicators. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
}

/* iOS zooms the page whenever a focused field is under 16px. Everything the
   visitor can type into is 16px on phones for that reason, the smaller
   desktop sizing is a design choice, not something worth a zoom jump. */
@media (max-width: 820px) {
  input,
  select,
  textarea {
    font-size: 16px !important;
  }
  /* The custom select is a button, so it never triggers the zoom, but it
     should still line up with the fields beside it. */
  .ksel__btn {
    font-size: 16px;
  }
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.k-skip {
  position: fixed;
  top: -100px;
  left: 16px;
  z-index: 2000;
  background: var(--ink);
  color: #fff;
  padding: 12px 18px;
  border-radius: 10px;
  transition: top var(--t) var(--ease);
}
.k-skip:focus {
  top: 16px;
}

/* ---------- Layout ---------- */
.k-container {
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* Article measure: 780px of text plus the gutters. Anything using this
   container lines up with the article body exactly. */
.k-container--narrow {
  max-width: calc(780px + var(--gutter) * 2);
}
.k-container--wide {
  max-width: 1660px;
}

/* Two adjacent sections each contributed 115px at 1440px, so every seam in the
   page was a 230px band of nothing. Halved at the wide end and trimmed at the
   narrow end; the cap stops it growing again on a large monitor. */
.k-section {
  padding: clamp(44px, 3.8vw, 58px) 0;
  position: relative;
}
/* A section arrived at by anchor has to clear the fixed header, or it lands
   with its heading underneath it. Covers cross-page jumps such as the Company
   dropdown, where the browser does the scrolling and no script is involved. */
section[id] {
  scroll-margin-top: calc(var(--header-h) + 20px);
}
.k-section--tight {
  padding: clamp(30px, 2.6vw, 40px) 0;
}
/* Neighbouring sections were each paying for the same gap: 72px of bottom
   padding followed by 72px of top padding, with nothing between them to show
   for it. The second one drops its top padding and the first one's bottom
   carries the seam on its own.

   Not when either side paints a solid background: there the padding sits
   inside the coloured band, holding the content off a visible edge, so it is
   doing a job. The tint gradient starts and ends white, so it collapses like
   a plain section. */
.k-section:not(.k-section--paper) + .k-section:not(.k-section--paper),
/* Two paper sections in a row are one continuous band of the same colour, so
   the join between them is invisible and pays twice like any other. */
.k-section--paper + .k-section--paper {
  padding-top: 0;
}

.k-section--paper {
  background: var(--paper);
}
.k-section--tint {
  background: linear-gradient(180deg, #fff 0%, var(--paper-2) 60%, #fff 100%);
}

/* ---------- Type scale ---------- */
.k-display {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 6.2vw, 5rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
}
.k-h1 {
  font-size: clamp(2.2rem, 4.6vw, 3.7rem);
  line-height: 1.06;
  letter-spacing: -0.03em;
}
.k-h2 {
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.028em;
}
.k-h3 {
  font-size: clamp(1.35rem, 2vw, 1.75rem);
  line-height: 1.2;
  letter-spacing: -0.02em;
}
.k-h4 {
  font-size: 1.15rem;
  line-height: 1.3;
  letter-spacing: -0.012em;
}

.k-lede {
  font-size: clamp(1.05rem, 1.35vw, 1.25rem);
  line-height: 1.6;
  color: var(--muted);
  max-width: 62ch;
}
.k-text {
  color: var(--muted);
}
.k-text--sm {
  font-size: 0.95rem;
}
/* Inline links in body copy stay recognisable as links. */
.k-text a,
.k-lede a,
.faq__inner a,
.plan__note a {
  color: var(--blue);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--blue) 34%, transparent);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--t) var(--ease);
}
.k-text a:hover,
.k-lede a:hover,
.faq__inner a:hover {
  text-decoration-color: var(--blue);
}

.k-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  padding: 7px 14px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 8%, #fff);
  border: 1px solid color-mix(in srgb, var(--accent) 18%, #fff);
}
.k-eyebrow--light {
  color: #cdeefb;
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(255, 255, 255, 0.18);
}
.k-eyebrow .k-icon {
  opacity: 0.9;
}

/* The gradient is painted on whatever element actually holds the glyphs.
   That second selector is not decoration: the split-text animation rewraps
   every word into `.word > span`, and those spans inherit the transparent fill
   while having no background of their own. Chrome lets the parent's clipped
   background show through them; WebKit does not once a descendant is
   transformed, which is what the animation does, so on iOS the phrase was
   painted transparent on transparent and never appeared at all. */
.k-grad,
.k-grad .word > span {
  background: linear-gradient(
    100deg,
    var(--blue) 0%,
    var(--cyan) 52%,
    #7ad0ff 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
/* Anything that cannot clip a background to text gets the flat brand colour
   rather than an invisible headline. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .k-grad,
  .k-grad .word > span {
    background: none;
    color: var(--cyan);
    -webkit-text-fill-color: currentColor;
  }
}

/* Section heading block */
.k-head {
  max-width: 780px;
  margin-bottom: clamp(36px, 4vw, 64px);
}
.k-head--center {
  margin-inline: auto;
  text-align: center;
}
.k-head > * + * {
  margin-top: 18px;
}
.k-head--split {
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 32px 60px;
  align-items: end;
}
.k-head--split > * + * {
  margin-top: 0;
}
.k-head--split .k-eyebrow {
  grid-column: 1 / -1;
  justify-self: start;
}

/* ---------- Buttons ---------- */
.k-btn {
  --btn-bg: var(--ink);
  --btn-fg: #fff;
  --btn-bd: transparent;
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 14px 26px;
  border-radius: 999px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1.5px solid var(--btn-bd);
  font-family: var(--font-body);
  font-size: 0.96rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  white-space: nowrap;
  transition:
    transform var(--t) var(--ease),
    box-shadow var(--t) var(--ease),
    background-color var(--t) var(--ease),
    color var(--t) var(--ease),
    border-color var(--t) var(--ease);
}
.k-btn > span,
.k-btn > svg {
  position: relative;
  z-index: 1;
}
.k-btn::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -60%;
  width: 45%;
  background: linear-gradient(
    110deg,
    transparent,
    rgba(255, 255, 255, 0.42),
    transparent
  );
  transform: skewX(-18deg);
  pointer-events: none;
  transition: left 0.62s var(--ease);
}
/* Buttons stay put on hover. The lift and the cursor-magnet that used to move
   them were both distracting on a page with this many calls to action, so the
   sheen sweep, the arrow nudge and each variant's own colour change carry the
   state instead. (--sh-2 is `none` while elevation is switched off above; the
   declaration stays so the tokens remain the single place to restore depth.) */
.k-btn:hover {
  box-shadow: var(--sh-2);
}
.k-btn:hover::after {
  left: 130%;
}
.k-btn .k-icon {
  transition: transform var(--t) var(--ease);
}
.k-btn:hover .k-icon {
  transform: translateX(3px);
}

.k-btn--sm {
  padding: 10px 18px;
  font-size: 0.89rem;
}
.k-btn--lg {
  padding: 12px 20px;
  font-size: 1rem;
}

.k-btn--dark {
  --btn-bg: var(--ink);
  --btn-fg: #fff;
}
.k-btn--cyan {
  --btn-bg: var(--cyan);
  --btn-fg: #fff;
}
.k-btn--cyan:hover {
  box-shadow: var(--sh-cyan);
}
.k-btn--blue {
  --btn-bg: var(--blue);
  --btn-fg: #fff;
}
.k-btn--accent {
  --btn-bg: var(--accent);
  --btn-fg: #fff;
}
/* The secondary call to action. White-on-white left it with no hover at all, the sheen is invisible against its own background, so it fills instead: the
   brand gradient wipes up from the bottom and the label inverts to white. The
   button itself never moves; only the fill does. */
/* The secondary call to action. White-on-white left it with no hover at all, the sheen is invisible against its own background, so the brand gradient
   crossfades in instead.

   The gradient is the element's own background and a white curtain sits over
   it, rather than the other way round. That ordering matters: a negative
   z-index layer paints above the element background but below its text, so the
   curtain can fade without any edge sweeping across the label, and it carries
   the resting outline as an inset shadow so no grey border is ever drawn on
   top of the gradient. Both were visible in the wipe this replaced. */
.k-btn--white {
  --btn-bg: #fff;
  --btn-fg: var(--ink);
  --btn-bd: transparent;
  /* The resting outline is an inset shadow rather than a border, so the fill
     above can cover it completely, a real border would keep drawing a ring on
     top of the gradient. */
  box-shadow: inset 0 0 0 1.5px var(--line-2);
  /* Required: without its own stacking context the negative-z fill paints
     behind this element's background instead of over it. */
  isolation: isolate;
  transition:
    color var(--t) var(--ease),
    box-shadow 0.34s var(--ease-out-soft);
}
/* Reuses the sheen slot, which a white button cannot show anyway. Nothing of
   the gradient exists at rest, so there is no colour to leak around the edge. */
.k-btn--white::after {
  content: "";
  position: absolute;
  inset: -2px; /* covers the outline; .k-btn clips the overhang */
  left: -2px;
  width: auto;
  z-index: -1;
  transform: none;
  border-radius: inherit;
  background: linear-gradient(
    100deg,
    var(--blue) 0%,
    var(--cyan) 52%,
    #7ad0ff 100%
  );
  opacity: 0;
  transition: opacity 0.34s var(--ease-out-soft);
}
.k-btn--white:hover,
.k-btn--white:focus-visible {
  --btn-fg: #fff;
  box-shadow: inset 0 0 0 1.5px transparent;
}
.k-btn--white:hover::after,
.k-btn--white:focus-visible::after {
  left: -2px;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .k-btn--white::after {
    transition: none;
  }
}
.k-btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-bd: var(--line-2);
}
.k-btn--ghost:hover {
  --btn-bd: var(--ink);
}
.k-btn--outline-light {
  --btn-bg: transparent;
  --btn-fg: #fff;
  --btn-bd: rgba(255, 255, 255, 0.32);
}
.k-btn--outline-light:hover {
  --btn-bg: rgba(255, 255, 255, 0.1);
  --btn-bd: rgba(255, 255, 255, 0.6);
}

.k-link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--accent);
  transition: gap var(--t) var(--ease);
}
.k-link-arrow .k-icon {
  transition: transform var(--t) var(--ease);
}
.k-link-arrow:hover {
  gap: 12px;
}
.k-link-arrow:hover .k-icon {
  transform: translateX(2px);
}

.k-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid var(--line-2);
  color: var(--ink-2);
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  transition: all var(--t) var(--ease);
}
.k-chip:hover {
  border-color: var(--ink);
  transform: translateY(-1px);
  box-shadow: var(--sh-1);
}

.k-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 13px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  background: color-mix(in srgb, var(--accent) 9%, #fff);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 16%, #fff);
}

/* ---------- Decorative background field ---------- */
/* The blobs are a fixed 520–620px, but the sections hosting them are not, the contact hero is only ~350px tall. overflow:hidden then cuts a blob
   through its bright middle, and a clipped blur has a hard edge, so it reads
   as a grey rectangle rather than a glow. Masking the layer fades every blob
   out before it reaches an edge, whatever the section's size. */
.k-orbs {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: radial-gradient(
    118% 96% at 50% 42%,
    #000 38%,
    transparent 88%
  );
  mask-image: radial-gradient(118% 96% at 50% 42%, #000 38%, transparent 88%);
}
.k-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.5;
  will-change: transform;
  animation: orbDrift 22s ease-in-out infinite alternate;
}
.k-orb--a {
  width: 520px;
  height: 520px;
  top: -180px;
  left: -120px;
  background: radial-gradient(circle, rgba(0, 180, 229, 0.4), transparent 70%);
}
.k-orb--b {
  width: 620px;
  height: 620px;
  top: 60px;
  right: -220px;
  background: radial-gradient(circle, rgba(16, 83, 126, 0.3), transparent 70%);
  animation-delay: -7s;
}
.k-orb--c {
  width: 420px;
  height: 420px;
  bottom: -160px;
  left: 34%;
  background: radial-gradient(circle, rgba(255, 187, 0, 0.22), transparent 70%);
  animation-delay: -13s;
}
@keyframes orbDrift {
  0% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  50% {
    transform: translate3d(40px, -30px, 0) scale(1.08);
  }
  100% {
    transform: translate3d(-30px, 26px, 0) scale(0.96);
  }
}

.k-grid-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    linear-gradient(to right, rgba(11, 21, 36, 0.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(11, 21, 36, 0.045) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(
    ellipse 80% 60% at 50% 0%,
    #000 20%,
    transparent 78%
  );
  mask-image: radial-gradient(
    ellipse 80% 60% at 50% 0%,
    #000 20%,
    transparent 78%
  );
}

.k-noise::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.028;
  z-index: 1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ==========================================================================
   Animation engine
   Everything below is driven by IntersectionObserver / rAF in assets/js/main.js
   ========================================================================== */

/* Scroll reveal, the workhorse. Add .reveal, optionally --up/--left/--right/--zoom
   Longer durations on a softer decelerate curve read as smooth; the generous
   overlap between elements is what keeps it from also feeling slow. */
.js .reveal {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
  transition:
    opacity 0.62s var(--ease-out-soft),
    transform 0.68s var(--ease-out-soft);
  /* Per-element delays are authored inline as --rd. Scaling them here keeps
     the whole sequence in proportion when the pace is retuned, rather than
     editing the value on every hero in the site. */
  transition-delay: calc(var(--rd, 0ms) * 0.62);
  will-change: opacity, transform;
}
.js .reveal--left {
  transform: translate3d(-42px, 0, 0);
}
.js .reveal--right {
  transform: translate3d(42px, 0, 0);
}
.js .reveal--zoom {
  transform: scale(0.95);
}
.js .reveal--blur {
  filter: blur(10px);
  transition:
    opacity 0.62s var(--ease-out-soft),
    transform 0.68s var(--ease-out-soft),
    filter 0.62s var(--ease-out-soft);
}
.js .reveal--fade {
  transform: none;
}
.js .reveal.is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* Stagger children: parent gets .stagger, each child animates in sequence.
   This runs as an animation rather than a transition on purpose. A transition
   has to leave `transform: none` declared on the finished state, and that
   declaration outranks any `.card:hover { transform: … }`, which silently
   killed the hover lift on every card grid on the site. JS adds .is-done once
   the sequence has played, which drops the animation and leaves the children
   with no transform of their own for hover styles to fight. */
.js .stagger > * {
  opacity: 0;
}
.js .stagger.is-in > * {
  animation: staggerIn 0.58s var(--ease-out-soft) both;
}
@keyframes staggerIn {
  from {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.js .stagger.is-done > * {
  animation: none;
  opacity: 1;
}

.js .stagger.is-in > *:nth-child(1) {
  animation-delay: 0.02s;
}
.js .stagger.is-in > *:nth-child(2) {
  animation-delay: 0.07s;
}
.js .stagger.is-in > *:nth-child(3) {
  animation-delay: 0.12s;
}
.js .stagger.is-in > *:nth-child(4) {
  animation-delay: 0.17s;
}
.js .stagger.is-in > *:nth-child(5) {
  animation-delay: 0.22s;
}
.js .stagger.is-in > *:nth-child(6) {
  animation-delay: 0.27s;
}
.js .stagger.is-in > *:nth-child(7) {
  animation-delay: 0.32s;
}
.js .stagger.is-in > *:nth-child(8) {
  animation-delay: 0.37s;
}
.js .stagger.is-in > *:nth-child(9) {
  animation-delay: 0.42s;
}
.js .stagger.is-in > *:nth-child(10) {
  animation-delay: 0.47s;
}
.js .stagger.is-in > *:nth-child(11) {
  animation-delay: 0.52s;
}
.js .stagger.is-in > *:nth-child(12) {
  animation-delay: 0.57s;
}

/* Split-text headline reveal: JS wraps each word in .word > span.
   The words rise out of a mask. The step between words is short enough that a
   long headline still lands as one movement rather than a visible queue, the
   last word of a nine-word line arrives inside a second. */
.js [data-split] .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.08em;
}
.js [data-split] .word > span {
  display: inline-block;
  transform: translate3d(0, 110%, 0) rotate(2.5deg);
  opacity: 0.001;
  transition:
    transform 0.66s var(--ease-out-soft),
    opacity 0.46s var(--ease-out-soft);
  transition-delay: calc(50ms + var(--i) * 30ms);
}
.js [data-split].is-in .word > span {
  transform: none;
  opacity: 1;
}

/* ---------- Hero entrance ----------
   Every hero on the site (home, product, service, industry, and the simple
   page heroes) arrives as one choreographed sequence rather than several
   independent fades. The product shot gets the longest, softest travel:
   scaling a large screenshot reads as soft focus, so it rises instead. */
.js .p-hero__media.reveal {
  transition-duration: 0.86s, 0.92s;
  transition-delay: 180ms;
}
.js .p-hero__media.reveal:not(.is-in) {
  transform: translate3d(0, 52px, 0) scale(0.99);
}

/* The copy block above it: each line follows the last closely enough to feel
   like one movement. Delays are set inline per element as --rd. */
.js .home-hero__head .reveal,
.js .p-hero__head .reveal,
.js .s-hero__inner .reveal {
  transition-duration: 0.66s, 0.7s;
}

/* Eyebrow pills and breadcrumbs lead the sequence with a shorter travel. */
.js .home-hero__badge.reveal,
.js .p-hero__crumbs.reveal,
.js .s-hero__inner > .k-eyebrow.reveal,
.js .p-hero__head > .k-eyebrow.reveal {
  transition-duration: 0.5s;
}
.js .home-hero__badge.reveal:not(.is-in),
.js .p-hero__crumbs.reveal:not(.is-in),
.js .s-hero__inner > .k-eyebrow.reveal:not(.is-in),
.js .p-hero__head > .k-eyebrow.reveal:not(.is-in) {
  transform: translate3d(0, 14px, 0);
}

/* Floating cards fade in after the shot they sit on has arrived. Opacity only:
   these already run the idle float animation, and a transform-based entrance
   would fight it. */
.js .p-hero__media .p-hero__chip {
  opacity: 0;
  transition: opacity 0.58s var(--ease-out-soft);
}
.js .p-hero__media.is-in .p-hero__chip {
  opacity: 1;
}
.js .p-hero__media.is-in .p-hero__chip--1 {
  transition-delay: 0.85s;
}
.js .p-hero__media.is-in .p-hero__chip--2 {
  transition-delay: 1s;
}

/* Character shimmer for eyebrow lines */
@keyframes shimmer {
  to {
    background-position: 200% center;
  }
}
.k-shimmer {
  background: linear-gradient(
    90deg,
    var(--muted-2) 0%,
    var(--ink) 45%,
    var(--muted-2) 70%
  );
  background-size: 200% auto;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shimmer 4.5s linear infinite;
}

/* Marquee */
.k-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent,
    #000 8%,
    #000 92%,
    transparent
  );
  mask-image: linear-gradient(
    90deg,
    transparent,
    #000 8%,
    #000 92%,
    transparent
  );
}
.k-marquee__track {
  display: flex;
  align-items: center;
  gap: 68px;
  width: max-content;
  animation: marquee var(--dur, 46s) linear infinite;
}
.k-marquee:hover .k-marquee__track {
  animation-play-state: paused;
}
.k-marquee--rev .k-marquee__track {
  animation-direction: reverse;
}
@keyframes marquee {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

/* Tilt (JS sets --rx/--ry) */
[data-tilt] {
  transform-style: preserve-3d;
  transition: transform 0.5s var(--ease);
}
[data-tilt].is-tilting {
  transition: transform 0.12s linear;
}
[data-tilt] {
  transform: perspective(1100px) rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg));
}

/* Spotlight, a soft light follows the cursor across a card */
[data-spotlight] {
  position: relative;
  overflow: hidden;
}
[data-spotlight]::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  background: radial-gradient(
    420px circle at var(--sx, 50%) var(--sy, 50%),
    color-mix(in srgb, var(--accent) 16%, transparent),
    transparent 62%
  );
}
[data-spotlight]:hover::before {
  opacity: 1;
}
[data-spotlight] > * {
  position: relative;
  z-index: 1;
}

/* Parallax layers (JS sets --py) */
[data-parallax] {
  will-change: transform;
  transform: translate3d(0, var(--py, 0px), 0);
}

/* Counters land with a subtle pop */
[data-count].is-in {
  animation: countPop 0.7s var(--ease) both;
}
@keyframes countPop {
  0% {
    transform: scale(0.9);
    opacity: 0.4;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}

/* Draw-on SVG underline */
.k-underline {
  position: relative;
  display: inline-block;
}
.k-underline svg {
  position: absolute;
  left: 0;
  bottom: -0.16em;
  width: 100%;
  height: 0.34em;
  overflow: visible;
}
.k-underline path {
  stroke: var(--cyan);
  stroke-width: 7;
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: 420;
  stroke-dashoffset: 420;
  transition: stroke-dashoffset 1.1s var(--ease) 0.25s;
}
.is-in .k-underline path,
.k-underline.is-in path {
  stroke-dashoffset: 0;
}

/* Scroll progress bar */
.k-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  z-index: 1200;
  pointer-events: none;
  background: transparent;
}
.k-progress__bar {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: linear-gradient(
    90deg,
    var(--blue),
    var(--cyan) 70%,
    var(--yellow)
  );
}

/* Floating idle motion for hero props */
@keyframes floatY {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-14px);
  }
}
.k-float {
  animation: floatY 6.5s ease-in-out infinite;
}
.k-float--2 {
  animation: floatY 8s ease-in-out infinite;
  animation-delay: -2.5s;
}
.k-float--3 {
  animation: floatY 7.2s ease-in-out infinite;
  animation-delay: -4.4s;
}

@keyframes spinSlow {
  to {
    transform: rotate(360deg);
  }
}
.k-spin {
  animation: spinSlow 18s linear infinite;
}

@keyframes pulseRing {
  0% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 42%, transparent);
  }
  70% {
    box-shadow: 0 0 0 16px transparent;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}
.k-pulse {
  animation: pulseRing 2.6s var(--ease) infinite;
}

/* Card hover lift, shared */
.k-lift {
  transition:
    transform var(--t) var(--ease),
    box-shadow var(--t) var(--ease),
    border-color var(--t) var(--ease);
}
.k-lift:hover {
  transform: translateY(-6px);
  box-shadow: var(--sh-3);
  border-color: color-mix(in srgb, var(--accent) 32%, var(--line));
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .js .reveal,
  .js .stagger > *,
  .js [data-split] .word > span,
  .js .p-hero__media .p-hero__chip {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}

/* ---------- Hero headline that changes line ----------
   Both lines occupy the same grid cell, so the block is as tall as the tallest
   and nothing under it shifts when they trade. Same word mask as [data-split],
   but with an exit: words leave upward, the next line arrives from below. */
.hrot {
  display: grid;
}
.hrot__line {
  grid-area: 1 / 1;
  /* The cell is as tall as the longest line, so a shorter one is centred in
     it rather than pinned to the top with the spare height hanging beneath. */
  align-self: center;
  /* Out of the way for the pointer and for selection until it is the one on
     screen; visibility is still animatable, unlike display. */
  visibility: hidden;
}
.hrot__line.is-current,
.hrot__line.is-in {
  visibility: visible;
}
/* The line on its way out has dropped .is-current, so without this it would
   snap to hidden and the exit would never be seen. Zero-duration change,
   delayed until the words have finished leaving. */
.hrot__line.is-out {
  visibility: hidden;
  transition: visibility 0s linear 0.62s;
}
.js .hrot__line .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.08em;
}
.js .hrot__line .word > span {
  display: inline-block;
  transform: translate3d(0, 110%, 0) rotate(2.5deg);
  opacity: 0.001;
  transition:
    transform 0.66s var(--ease-out-soft),
    opacity 0.46s var(--ease-out-soft);
  transition-delay: calc(50ms + var(--i) * 30ms);
}
.js .hrot__line.is-in .word > span {
  transform: none;
  opacity: 1;
}
/* Leaving is quicker than arriving, and the stagger is tighter: a slow exit
   reads as the line being dragged off rather than making way. */
.js .hrot__line.is-out .word > span {
  transform: translate3d(0, -110%, 0) rotate(-2.5deg);
  opacity: 0.001;
  transition:
    transform 0.5s var(--ease-in-soft, cubic-bezier(0.55, 0, 0.85, 0.35)),
    opacity 0.34s ease-in;
  transition-delay: calc(var(--i) * 18ms);
}

@media (prefers-reduced-motion: reduce) {
  /* No swap at all: initHeroLines lands on the final line and this makes sure
     it is simply there. */
  .js .hrot__line .word > span,
  .js .hrot__line.is-out .word > span {
    transform: none;
    opacity: 1;
    transition: none;
  }
  .hrot__line:not(.is-current) { visibility: hidden; }
}

/* The hero already leaves space under itself, so the section that follows it
   does not need to leave the same space again. Same reasoning as the rule
   between two sections above; .s-hero is not a .k-section, so it needs saying
   separately. */
.s-hero + .k-section:not(.k-section--paper) {
  padding-top: 18px;
}

/* The feature explorer leaves its own space underneath, so the section after
   it does not need to leave that space a second time. */
.reservations-features + .k-section:not(.k-section--paper) {
  padding-top: 0;
}

/* The sticky sub-nav on product pages is a bar, not a section: it has its own
   border underneath and needs no gap held open beneath it as well. */
.p-nav + .k-section:not(.k-section--paper) {
  padding-top: 22px;
}
