:root {
  /* Long, decelerating curves. Motion should feel like something heavy
     coming to rest, never like something springing or bouncing. */
  --kb-ease-settle: cubic-bezier(0.16, 1, 0.30, 1);
  --kb-ease-soft: cubic-bezier(0.32, 0.72, 0, 1);
  --kb-ease-hover: cubic-bezier(0.4, 0, 0.2, 1);

  --kb-dur-reveal: 900ms;
  --kb-dur-hover: 250ms;
  --kb-dur-drift: 24s;

  --kb-reveal-rise: 24px;
  --kb-stagger-step: 90ms;
}

/* Scroll reveal. Sections fade and rise once as they enter view.
   Only transform and opacity animate. The armed state is applied by JS,
   so if the script never runs the content is simply visible. */
.kb-reveal.is-armed {
  opacity: 0;
  transform: translate3d(0, var(--kb-reveal-rise), 0);
}

.kb-reveal.is-armed.is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  transition:
    opacity var(--kb-dur-reveal) var(--kb-ease-settle),
    transform var(--kb-dur-reveal) var(--kb-ease-settle);
  transition-delay: calc(var(--kb-stagger, 0) * var(--kb-stagger-step));
}

/* Images settle rather than rise: they resolve from a fractionally larger
   scale, which reads as the photograph coming into focus. */
.kb-settle.is-armed img,
.kb-settle.is-armed .kb-media {
  opacity: 0;
  transform: scale(1.04);
}

.kb-settle.is-armed.is-revealed img,
.kb-settle.is-armed.is-revealed .kb-media {
  opacity: 1;
  transform: scale(1);
  transition:
    opacity 1200ms var(--kb-ease-settle),
    transform 1400ms var(--kb-ease-settle);
}

/* Hero drift. Full-bleed imagery scales 1.00 to 1.05 across 24s.
   This is a slow ambient move, not parallax: it is not tied to scroll. */
.kb-drift {
  animation: kb-drift var(--kb-dur-drift) var(--kb-ease-soft) forwards;
  will-change: transform;
}

@keyframes kb-drift {
  from { transform: scale(1); }
  to   { transform: scale(1.05); }
}

/* Top-level Elementor sections inherit the reveal without per-element wiring. */
.kb-page .elementor > .e-con.is-armed {
  opacity: 0;
  transform: translate3d(0, var(--kb-reveal-rise), 0);
}

.kb-page .elementor > .e-con.is-armed.is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  transition:
    opacity var(--kb-dur-reveal) var(--kb-ease-settle),
    transform var(--kb-dur-reveal) var(--kb-ease-settle);
}

/* Full-bleed plates drift slowly. The section clips, the image scales. */
.kb-page .s-hero-plate img,
.kb-page .kb-plate img {
  animation: kb-drift var(--kb-dur-drift) var(--kb-ease-soft) forwards;
  will-change: transform;
}

/* Hovers are colour shifts only. No lifts, no shadows, no scale on hover. */
.kb-page a,
.kb-page button {
  transition:
    color var(--kb-dur-hover) var(--kb-ease-hover),
    background-color var(--kb-dur-hover) var(--kb-ease-hover),
    border-color var(--kb-dur-hover) var(--kb-ease-hover);
}

/* Inline links draw their underline rather than switching it on.
   Transform only, so it stays cheap and smooth. */
.kb-link {
  position: relative;
  color: var(--kb-accent);
  text-decoration: none;
}

.kb-link::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -2px;
  block-size: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 400ms var(--kb-ease-settle);
}

.kb-link:hover {
  color: var(--kb-link-hover);
}

.kb-link:hover::after,
.kb-link:focus-visible::after {
  transform: scaleX(1);
}

/* Focus is always visible and always meets contrast. */
.kb-page :focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 3px;
}

/* The design holds completely with animation removed. */
@media (prefers-reduced-motion: reduce) {
  .kb-reveal.is-armed,
  .kb-settle.is-armed img,
  .kb-settle.is-armed .kb-media {
    opacity: 1 !important;
    transform: none !important;
  }

  .kb-drift {
    animation: none !important;
    transform: none !important;
  }

  .kb-page *,
  .kb-page *::before,
  .kb-page *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}


/* ======================================================================
   Phase 2  ·  element-level reveals, arrival choreography, hover
   language, scrolled header, page transitions.
   Same rules as above: only opacity and transform animate, everything
   holds with the script absent, everything collapses under
   prefers-reduced-motion.
   ====================================================================== */

/* --- 1. Element-level reveal --------------------------------------------
   motion.js arms individual widgets below the fold (headings, paragraphs,
   images, buttons) and whole cards as single units, then staggers siblings
   through --kb-stagger. Sections are no longer armed as blocks. */
.kb-page .kb-el.is-armed {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
}
.kb-page .kb-el.is-armed.is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  transition:
    opacity var(--kb-dur-reveal) var(--kb-ease-settle),
    transform var(--kb-dur-reveal) var(--kb-ease-settle);
  transition-delay: calc(var(--kb-stagger, 0) * var(--kb-stagger-step));
}

/* Photographs come into focus rather than rising. */
.kb-page img.kb-el.is-armed {
  transform: scale(1.03);
}
.kb-page img.kb-el.is-armed.is-revealed {
  transform: scale(1);
  transition-duration: 1200ms, 1400ms;
}

/* --- 2. Arrival choreography ---------------------------------------------
   Pure CSS, so it needs no script and cannot flash. Fill-mode "both" holds
   each piece invisible until its turn. The page H1 rises first, then the
   paragraphs and button row that follow it. On the homepage the video
   settles from a slight zoom, then drifts for the next 24 seconds. */
@keyframes kb-rise {
  from { opacity: 0; transform: translate3d(0, 22px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@keyframes kb-media-in {
  from { opacity: 0; transform: scale(1.06); }
  to   { opacity: 1; transform: scale(1); }
}

.kb-page h1.e-heading-base {
  animation: kb-rise 1000ms var(--kb-ease-settle) 260ms both;
}
.kb-page h1.e-heading-base ~ .e-paragraph-base {
  animation: kb-rise 1000ms var(--kb-ease-settle) 440ms both;
}
.kb-page h1.e-heading-base ~ .e-flexbox-base {
  animation: kb-rise 1000ms var(--kb-ease-settle) 620ms both;
}

.kb-page .s-home-band {
  overflow: hidden;
}
.kb-page .s-band-video {
  animation:
    kb-media-in 1600ms var(--kb-ease-settle) both,
    kb-drift var(--kb-dur-drift) var(--kb-ease-soft) 1600ms forwards;
  will-change: transform;
}

/* --- 3. Hover language ---------------------------------------------------
   Card titles draw a hairline and shift to olive. Buttons fill from the
   left. Still colour and one-dimensional movement only: no lift, no
   shadow, no scale. */
.kb-page a.e-con h3.e-heading-base {
  width: fit-content;
  align-self: flex-start;
  background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0 1px;
  transition:
    background-size 420ms var(--kb-ease-settle),
    color var(--kb-dur-hover) var(--kb-ease-hover);
}
.kb-page a.e-con:hover h3.e-heading-base,
.kb-page a.e-con:focus-visible h3.e-heading-base {
  color: var(--kb-link-hover);
  background-size: 100% 1px;
}

.kb-page a.e-button-base {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* overflow:hidden lets a flex item shrink below its label and wrap it
     letter by letter (seen on the mobile header CTA). Pin the size. */
  white-space: nowrap;
  flex-shrink: 0;
}
.kb-page a.e-button-base::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--kb-olive);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 380ms var(--kb-ease-settle);
}
.kb-page a.e-button-base:hover::before,
.kb-page a.e-button-base:focus-visible::before {
  transform: scaleX(1);
}
.kb-page a.e-button-base:hover,
.kb-page a.e-button-base:focus-visible {
  color: var(--kb-ivory) !important;
  border-color: var(--kb-olive) !important;
}

/* --- 4. Scrolled header --------------------------------------------------
   The header is already sticky. Once the page has moved it slims, the
   logo steps down and the ivory turns translucent over a blur. */
.kb-page .s-site-header {
  transition:
    padding 350ms var(--kb-ease-hover),
    background-color 350ms var(--kb-ease-hover);
}
.kb-page .s-brand-logo {
  transform-origin: left center;
  transition: transform 350ms var(--kb-ease-hover);
}
html.kb-scrolled .kb-page .s-site-header {
  padding-block-start: 12px;
  padding-block-end: 12px;
  background-color: rgba(247, 243, 236, 0.86);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
}
html.kb-scrolled .kb-page .s-brand-logo {
  transform: scale(0.86);
}

/* --- 5. Page transitions -------------------------------------------------
   Arrival is a plain fade with fill-mode "backwards", so once it ends the
   cascade owns opacity again and the departure fade can take over. */
@keyframes kb-page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
body.kb-page:not(.elementor-editor-active) {
  animation: kb-page-in 360ms var(--kb-ease-soft) backwards;
}
html.kb-leaving body.kb-page {
  opacity: 0;
  transition: opacity 180ms var(--kb-ease-hover);
}

/* --- 6. Type details -----------------------------------------------------*/
.kb-page p {
  text-wrap: pretty;
}
.kb-page,
.kb-page h1,
.kb-page h2,
.kb-page h3 {
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  -moz-osx-font-smoothing: grayscale;
}

/* --- Reduced motion: phase 2 holds still too --------------------------- */
@media (prefers-reduced-motion: reduce) {
  .kb-page .kb-el.is-armed,
  .kb-page img.kb-el.is-armed {
    opacity: 1 !important;
    transform: none !important;
  }
  .kb-page .s-band-video,
  .kb-page h1.e-heading-base,
  .kb-page h1.e-heading-base ~ .e-paragraph-base,
  .kb-page h1.e-heading-base ~ .e-flexbox-base,
  body.kb-page {
    animation: none !important;
  }
  html.kb-leaving body.kb-page {
    opacity: 1;
  }
}
