/*
  KLEIN Foundry Systems — Motion layer (v16)

  Marketing pages, seen occasionally: entrances may take a little longer
  than UI feedback. Everything that travels uses the long, soft settle
  (--ease-soft); on-screen wipes use --ease-in-out.
  Only transform-type properties (transform, translate, scale), opacity and
  clip-path are animated — nothing here causes layout.

  The hidden start states apply only under html.motion, which an inline
  script in <head> sets (and takes back after 2.5 s if motion.js never
  arrives) — without JavaScript everything is simply visible.
  motion.js adds .rv / .rv-img to the elements and .is-in once they scroll
  into view (once, never re-hidden).
*/

/* ============ Scroll reveal ============ */
/* uses the individual translate property, so an element's own transform
   (hover lift, pressed state) is never touched */

html.motion .rv {
  opacity: 0;
  translate: 0 22px;
  transition:
    opacity var(--duration-reveal) var(--ease-soft),
    translate var(--duration-reveal) var(--ease-soft);
  /* elements that come into view together follow one another; the order
     is set by motion.js when they are revealed (0–8) */
  transition-delay: calc(var(--rv-i, 0) * 65ms);
}

html.motion .rv.is-in {
  opacity: 1;
  translate: 0 0;
}

/* ============ Image curtain ============ */
/* a panel in the page colour lifts off the photo while the photo settles
   from 110 %. (A clip-path on the frame itself would hide it from the
   IntersectionObserver and from lazy loading, so the curtain is a pseudo
   element moved with transform.) */

html.motion .rv-img {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
}

/* the product photo is sticky next to long text (layout.css) – the curtain
   needs a positioned box, and sticky is one, so keep it sticky */
@media (min-width: 900px) {
  html.motion .product-detail__media.rv-img {
    position: sticky;
  }
}

html.motion .rv-img::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--color-bg);
  transform-origin: center top;
  transition: transform 800ms var(--ease-in-out);
}

html.motion .rv-img.is-in::after {
  transform: scaleY(0);
}

html.motion .rv-img img {
  scale: 1.1;
  transition: scale 1200ms var(--ease-soft);
}

html.motion .rv-img.is-in img {
  scale: 1;
}

/* ============ Process band: copper line draws across each step ============ */

html.motion .process-band .process__step::after {
  transform: scaleX(0);
  transition: transform 900ms var(--ease-in-out);
  transition-delay: calc(var(--rv-i, 0) * 120ms + 150ms);
}

html.motion .process-band .process__step.is-in::after {
  transform: scaleX(1);
}

/* ============ Home hero ============ */

/* no fade here: the lines are hidden by their masks anyway, and text that
   starts at opacity 0 would count later as the page's largest paint (LCP) */
@keyframes hero-rise {
  from {
    transform: translateY(105%);
  }
  to {
    transform: none;
  }
}

/* the footage settles from a slightly closer crop while the type comes in */
@keyframes hero-settle {
  from {
    scale: 1.09;
  }
  to {
    scale: 1;
  }
}

.hero-section__poster,
.hero-section__video {
  animation: hero-settle 2400ms var(--ease-soft) both;
}

/* the title's two lines rise out of their masks; replaces the old
   whole-title fade */
.hero-section__title {
  animation: none;
}

.hero-line > span {
  animation: hero-rise 1100ms var(--ease-soft) both;
}

.hero-line:nth-child(1) > span {
  animation-delay: 140ms;
}

.hero-line:nth-child(2) > span {
  animation-delay: 260ms;
}

.hero-badge {
  animation: fade-in 900ms var(--ease-soft) both;
}

.hero-section__subtitle {
  animation-duration: 900ms;
  animation-timing-function: var(--ease-soft);
  animation-delay: 460ms;
}

.hero-section__actions {
  animation-duration: 900ms;
  animation-timing-function: var(--ease-soft);
  animation-delay: 580ms;
}

.hero-scroll {
  animation: fade-in 900ms var(--ease-soft) both;
  animation-delay: 1000ms;
}

@keyframes scroll-cue {
  from {
    transform: translateY(-14px);
  }
  to {
    transform: translateY(44px);
  }
}

.hero-scroll__dot {
  animation: scroll-cue 1800ms var(--ease-in-out) infinite;
}

/* ============ Scroll-driven: hero exit and drifting photos ============ */
/* As the hero scrolls away the footage lags behind (slight depth) and the
   copy drifts up and fades, handing over to the next section. The large
   foundry photos drift a few percent inside their frames while they cross
   the screen. Both are tied to the scroll position, so linear; they run on
   the compositor, no JS. Browsers without scroll timelines simply keep
   the static picture. */

@keyframes hero-media-exit {
  to {
    transform: translateY(18%) scale(1.04);
  }
}

@keyframes hero-copy-exit {
  to {
    opacity: 0;
    transform: translateY(-48px);
  }
}

@keyframes photo-drift {
  from {
    translate: 0 -4.5%;
  }
  to {
    translate: 0 4.5%;
  }
}

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-section__media {
      animation: hero-media-exit linear both;
      animation-timeline: scroll(root);
      animation-range: 0 100vh;
    }

    .hero-section__inner {
      animation: hero-copy-exit linear both;
      animation-timeline: scroll(root);
      /* holds still for the first bit of scrolling, then hands over */
      animation-range: 8vh 75vh;
    }

    /* the photo is shown 12 % larger so it can drift without ever showing
       an edge; the curtain (above) settles onto that size */
    .about-media img,
    .why__media img {
      scale: 1.12;
      animation: photo-drift linear both;
      animation-timeline: view();
      animation-range: cover;
    }

    html.motion .about-media.rv-img img,
    html.motion .why__media.rv-img img {
      scale: 1.22;
    }

    html.motion .about-media.rv-img.is-in img,
    html.motion .why__media.rv-img.is-in img {
      scale: 1.12;
    }
  }
}

/* ============ Page change (cross-document view transition) ============ */
/* Following a link cross-fades the two pages instead of flashing; the
   header keeps its place (view-transition-name in layout.css). Browsers
   without support load the next page as usual. */

@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

@keyframes page-out {
  to {
    opacity: 0;
  }
}

@keyframes page-in {
  from {
    opacity: 0;
    translate: 0 10px;
  }
}

::view-transition-old(root) {
  animation: page-out 180ms var(--ease-standard) both;
}

::view-transition-new(root) {
  animation: page-in 380ms var(--ease-soft) both;
}

::view-transition-group(site-header) {
  animation-duration: 380ms;
  animation-timing-function: var(--ease-soft);
}

/* ============ Colour scheme switch ============ */
/* theme.js lets the new scheme spread in a circle from the switch
   (clip-path on the new snapshot); the default cross-fade is off for
   that moment and the header is part of the picture. */

html.theme-vt::view-transition-old(root),
html.theme-vt::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

html.theme-vt::view-transition-new(root) {
  z-index: 1;
}

html.theme-vt .site-header {
  view-transition-name: none;
}

/* ============ Reduced motion ============ */
/* base.css cuts every animation to ~0 ms. Reveals keep a short fade (it
   helps reading order) but no movement; the image wipe and the drawing
   line are simply shown; the looping scroll cue is removed. */
@media (prefers-reduced-motion: reduce) {
  html.motion .rv {
    translate: none;
    transition-property: opacity !important;
    transition-duration: 400ms !important;
    transition-timing-function: ease !important;
  }

  html.motion .rv-img::after {
    display: none;
  }

  html.motion .rv-img img {
    scale: 1;
  }

  html.motion .process-band .process__step::after {
    transform: none;
  }

  .hero-scroll__track {
    display: none;
  }
}

/* ==================================================================
   More motion (11.11.2026) — Philipp: "die ganze Seite deutlich mehr
   animieren". Still only transform-type properties, opacity and
   stroke-dashoffset (tiny icons); everything is switched off again in
   the reduced-motion block at the end.
   ================================================================== */

/* sideways reveals must not widen the page */
html.motion main {
  overflow-x: clip;
}

/* ---- reveals travel further; cards also grow into place ---- */
html.motion .rv {
  translate: 0 44px;
  transition:
    opacity var(--duration-reveal) var(--ease-soft),
    translate var(--duration-reveal) var(--ease-soft),
    scale var(--duration-reveal) var(--ease-soft);
  transition-delay: calc(var(--rv-i, 0) * 80ms);
}

html.motion .rv.card,
html.motion .rv.product-carousel__slide,
html.motion .rv.category-card,
html.motion .rv.variant-card,
html.motion .rv.trust-bar__item,
html.motion .rv.chip {
  scale: 0.94;
}

html.motion .rv.is-in {
  translate: 0 0;
  scale: 1;
}

/* two-column blocks: left column from the left, right one from the right */
@media (min-width: 900px) {
  html.motion .rv.rv--l {
    translate: -36px 0;
  }

  html.motion .rv.rv--r {
    translate: 36px 0;
  }

  html.motion .rv.rv--l.is-in,
  html.motion .rv.rv--r.is-in {
    translate: 0 0;
  }
}

/* ---- headings: every word rises out of its own mask ---- */
html.motion .rv-words .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* room for descenders (g, p, ß) inside the mask */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

html.motion .rv-words .w > span {
  display: inline-block;
  translate: 0 115%;
  transition: translate 950ms var(--ease-soft);
  transition-delay: calc(var(--rv-i, 0) * 80ms + var(--w-i, 0) * 55ms);
}

html.motion .rv-words.is-in .w > span {
  translate: 0 0;
}

/* ---- small copper lines draw themselves ---- */
html.motion .eyebrow::before,
html.motion .footer__heading::before,
html.motion .footer__heading::after {
  transform-origin: left center;
  transition: scale 800ms var(--ease-in-out) 300ms;
}

html.motion .eyebrow.rv:not(.is-in)::before,
html.motion .rv:not(.is-in) .footer__heading::before,
html.motion .rv:not(.is-in) .footer__heading::after {
  scale: 0 1;
}

/* ---- process steps: the number pops in ---- */
html.motion .process__step::before {
  transition:
    scale 700ms cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 400ms var(--ease-soft);
  transition-delay: calc(var(--rv-i, 0) * 120ms + 250ms);
}

html.motion .process__step.rv:not(.is-in)::before {
  scale: 0.3;
  opacity: 0;
}

/* ---- line icons are drawn (motion.js sets pathLength="1") ---- */
html.motion svg.draw * {
  stroke-dasharray: 1.02;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1300ms var(--ease-in-out);
  transition-delay: calc(var(--rv-i, 0) * 80ms + 350ms);
}

html.motion .rv:not(.is-in) svg.draw * {
  stroke-dashoffset: 1.02;
}

/* ---- reading progress: copper line along the top edge ---- */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto;
  z-index: 200;
  height: 3px;
  background: var(--color-accent);
  transform-origin: left center;
  transform: scaleX(0);
  pointer-events: none;
}

/* motion.js writes the scroll position into the transform; the short
   linear transition runs on the compositor and evens out the steps, so
   the line glides even where scroll events arrive unevenly (phones) */
.scroll-progress {
  transition: transform 160ms linear;
  will-change: transform;
}

/* ---- the copper glow in the dark page head breathes ---- */
@keyframes glow-drift {
  from {
    translate: 0 0;
    scale: 1;
    opacity: 0.85;
  }
  to {
    translate: -9% 26px;
    scale: 1.14;
    opacity: 1;
  }
}

/* ---- product photo floats a little ---- */
@keyframes float {
  from {
    translate: 0 -7px;
  }
  to {
    translate: 0 7px;
  }
}

/* ---- light that follows the pointer in the dark page head ---- */
.page-header.has-spot {
  overflow: clip;
}

.spot {
  position: absolute;
  left: 0;
  top: 0;
  z-index: -1;
  width: 560px;
  height: 560px;
  margin: -280px 0 0 -280px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--color-accent) 30%, transparent), transparent);
  opacity: 0;
  transition: opacity 500ms var(--ease-soft);
  pointer-events: none;
  will-change: transform;
}

.has-spot.is-lit .spot {
  opacity: 1;
}

/* ---- primary button: a light sweeps across on hover ---- */
.btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, 0.28) 50%, transparent 65%);
  translate: -110% 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .page-header::after,
  .process-band::after {
    animation: glow-drift 9s var(--ease-in-out) infinite alternate;
  }

  html.motion .product-detail__media.is-in img {
    animation: float 5.5s var(--ease-in-out) 1.4s infinite alternate;
  }


  @media (hover: hover) and (pointer: fine) {
    .btn--primary:hover::after {
      translate: 110% 0;
      transition: translate 800ms var(--ease-soft);
    }

    .variant-card,
    .category-card,
    .why__item,
    .chip,
    .contact-block {
      transition:
        transform 420ms var(--ease-soft),
        box-shadow 420ms var(--ease-soft);
    }

    .variant-card:hover {
      transform: translateX(8px);
      box-shadow: var(--shadow-md);
    }

    .category-card:hover {
      transform: translateY(-6px);
      box-shadow: var(--shadow-lg);
    }

    .why__item:hover,
    .contact-block:hover {
      transform: translateX(6px);
    }

    .chip:hover {
      transform: translateY(-3px);
    }

    .why__icon svg,
    .category-card__icon,
    .contact-block__icon {
      transition: rotate 600ms var(--ease-soft), scale 600ms var(--ease-soft);
    }

    .why__item:hover .why__icon svg,
    .category-card:hover .category-card__icon,
    .contact-block:hover .contact-block__icon {
      rotate: -8deg;
      scale: 1.15;
    }
  }
}

/* page change: the new page rises further */
@keyframes page-out {
  to {
    opacity: 0;
    translate: 0 -14px;
  }
}

@keyframes page-in {
  from {
    opacity: 0;
    translate: 0 30px;
  }
}

::view-transition-old(root) {
  animation-duration: 220ms;
}

::view-transition-new(root) {
  animation-duration: 520ms;
}

@media (prefers-reduced-motion: reduce) {
  html.motion .rv {
    scale: 1;
  }

  html.motion .rv-words .w > span {
    translate: none;
  }

  html.motion .eyebrow::before,
  html.motion .footer__heading::before,
  html.motion .footer__heading::after,
  html.motion .process__step::before {
    scale: 1 !important;
    opacity: 1 !important;
  }

  html.motion svg.draw * {
    stroke-dashoffset: 0 !important;
  }

  .scroll-progress,
  .spot {
    display: none;
  }
}

/* ============ Smooth on phones (12.11.2026) ============ */
/* Touch devices scroll on the compositor while scroll-linked effects and
   backdrop blur are redone for every frame – on phones that shows up as
   stutter. They get the calm version: no parallax, no drifting glow, no
   blur behind the small tags. Reveals stay. */
@media (hover: none), (pointer: coarse) {
  .hero-section__media,
  .hero-section__inner {
    animation: none;
  }

  .about-media img,
  .why__media img {
    animation: none;
    scale: 1;
  }

  html.motion .about-media.rv-img img,
  html.motion .why__media.rv-img img {
    scale: 1.1;
  }

  html.motion .about-media.rv-img.is-in img,
  html.motion .why__media.rv-img.is-in img {
    scale: 1;
  }

  .page-header::after,
  .process-band::after {
    animation: none;
  }

  .about-media__tag,
  .product-carousel__badge,
  .hero-badge,
  .hero-section__toggle {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .hero-badge {
    background: rgba(18, 20, 23, 0.5);
  }

  .hero-section__toggle {
    background: rgba(18, 20, 23, 0.55);
  }
}
