/* ===========================================================================
   MOTION
   ---------------------------------------------------------------------------
   One easing curve for the whole site. A page reads as one object when
   everything decelerates the same way; mixing curves is what makes a site feel
   assembled from parts.

   Nothing here is required for content to exist. Every animated element is
   readable with JavaScript off and with reduced motion on, which is enforced at
   the bottom of this file.
   =========================================================================== */

:root {
  --ease: cubic-bezier(.22, 1, .36, 1);   /* the site's only easing curve */
  --dur: .82s;
}

/* ------------------------------------------------------------- the rings
   A boomerang pair: a forward clip and a frame-exact reversed encode. When the
   forward pass ends we cross-dissolve into the reversed copy and back, so the
   object turns forever with no visible seam and no rewind stutter. */
.rings { position: relative; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; }
.rings-v,
.rings-still {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* The clip is 16:9 with the object sitting in the LEFT THIRD and flat stage
     to the right. `contain` therefore drew the object at about a third of the
     box with a wide empty field beside it, which is why it looked small and
     boxed. `cover` plus this origin crops to the object so it fills the frame. */
  object-fit: cover;
  /* Measured, not guessed. The object sits at 29.6% across the 3828x2164 frame.
     object-position aligns the image X% point with the BOX X% point, it does
     not centre the view on X%, so putting the object in the middle of a square
     box solves to about 3%, not 30%. Getting that wrong clipped the left arm. */
  object-position: 3% 50%;
  /* NO OPACITY TRANSITION. The loop hands over on a frame the two encodes
     share, so the swap is a hard cut and is invisible. Fading across it blends
     two different moments of the motion, which reads as the object dissolving
     every time the loop turns. This was removed on the live site once already
     and adding it back here is what made the launch feel wrong. */
  /* Decorative. It must never take a click, a tab stop, or Chrome's
     picture-in-picture overlay. */
  pointer-events: none;
}
.rings-v::-webkit-media-controls,
.rings-v::-webkit-media-controls-enclosure { display: none !important; }
/* The clip carries its own flat near-white stage. `multiply` drops that stage
   into whatever is behind it: against a lighter page it disappears, and it can
   never lighten, so the chrome keeps its darks. Cropping to the object above
   means there is very little stage left to blend in the first place. */
/* The grey box, properly.
   Multiply alone could not remove it: the clip's stage is not a flat white, it
   carries a subtle vignette, so blending left a visibly darker rectangle no
   matter what single colour we keyed against. Chasing the stage colour was the
   wrong approach.
   A radial mask deletes the rectangle instead. The object lives in the middle of
   the frame, so fading the outer third to nothing removes every straight edge
   while touching none of the chrome. Multiply stays on top of it to keep the
   remaining stage from lightening the page. */
.rings-v,
.rings-still {
  mix-blend-mode: multiply;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 46%, rgba(0,0,0,.55) 62%, transparent 74%);
          mask-image: radial-gradient(circle at 50% 50%, #000 46%, rgba(0,0,0,.55) 62%, transparent 74%);
}
/* The still and the video must be the SAME PICTURE or the handover is a jump.
   It used to be chrome-knot.png, a tight cut-out of the object drawn `contain`,
   so it filled the whole square box. The video crops a 16:9 frame where the
   object is a good deal smaller. The object therefore came up huge and shrank
   the instant the clip took over.
   The still is now literally frame zero of the forward clip, carrying the same
   object-fit, object-position, blend and mask as the video above. There is
   nothing left to disagree about, so the crossfade has nothing to reveal. */
.rings-v { opacity: 0; }
.rings-v.on { opacity: 1; }

/* The still is NOT a layer that hands over to the video. That was the mistake:
   an image sitting on top, cross-dissolving into a clip that had already moved
   on by the time it buffered, so the object appeared to swim into place.
   The <video> carries `poster` instead, which the browser paints and replaces
   with the real first frame itself, with no fade of ours in between.
   This element only appears when there will be no video at all: reduced motion,
   or autoplay refused by Low Power Mode. */
.rings-still { display: none; }
.rings.still .rings-still { display: block; opacity: 1; }

/* No drift keyframe. The clip already rotates; adding a second, slower motion
   on the same element made the object appear to wobble rather than turn. One
   motion per object. */

/* --------------------------------------------------------------- reveals
   Directional, and staggered by position rather than by a hard-coded delay, so
   a row of four cards cascades instead of arriving as a block. */
.reveal { opacity: 0; transform: translate3d(0, 26px, 0); }
.reveal.visible { opacity: 1; transform: none; }
.reveal { transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }

.hero-copy.reveal        { transform: translate3d(-22px, 14px, 0); }
.hero-knot.reveal        { transform: translate3d(22px, 14px, 0) scale(.96); }
.hero-knot.reveal.visible { transform: none; }

.evidence-panel .evidence,
.ownership-terms > *,
.chip-list li {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition: opacity .62s var(--ease), transform .62s var(--ease);
}
/* The `.visible` class only ever lands on a `.reveal`. A chip list nested inside
   one is not itself a reveal, so keying purely off `.chip-list.visible` left the
   metro chips permanently invisible on the contact page. An ancestor reveal now
   counts too. */
.evidence-panel.visible .evidence, .reveal.visible .evidence-panel .evidence,
.ownership-terms.visible > *,     .reveal.visible .ownership-terms > *,
.chip-list.visible li,            .reveal.visible .chip-list li { opacity: 1; transform: none; }

/* The cascade. Set in CSS rather than JS so it cannot desync from the reveal. */
.evidence-panel.visible .evidence:nth-child(1),
.ownership-terms.visible > :nth-child(1),
.chip-list.visible li:nth-child(1), .reveal.visible .chip-list li:nth-child(1) { transition-delay: .04s }
.evidence-panel.visible .evidence:nth-child(2),
.ownership-terms.visible > :nth-child(2),
.chip-list.visible li:nth-child(2), .reveal.visible .chip-list li:nth-child(2) { transition-delay: .10s }
.evidence-panel.visible .evidence:nth-child(3),
.ownership-terms.visible > :nth-child(3),
.chip-list.visible li:nth-child(3), .reveal.visible .chip-list li:nth-child(3) { transition-delay: .16s }
.evidence-panel.visible .evidence:nth-child(4),
.chip-list.visible li:nth-child(4), .reveal.visible .chip-list li:nth-child(4) { transition-delay: .22s }
.evidence-panel.visible .evidence:nth-child(5),
.chip-list.visible li:nth-child(5), .reveal.visible .chip-list li:nth-child(5) { transition-delay: .28s }
.chip-list.visible li:nth-child(n+6), .reveal.visible .chip-list li:nth-child(n+6) { transition-delay: .34s }

/* -------------------------------------------------------- the loop stages
   Inactive copy recedes rather than disappearing, so the whole argument stays
   on the page while one part of it is being read. */
.stage {
  opacity: .3;
  transform: translate3d(0, 0, 0) scale(.985);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.stage.on { opacity: 1; transform: none; }

/* ------------------------------------------------------------- the numbers
   A count-up on the research figures. The digits are already in the markup, so
   with JS off the real number is simply there. */
.research-number { display: inline-block; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- pointer
   Buttons and links answer the cursor. Short durations: interaction feedback
   should feel immediate, scroll reveals can take their time. */
.button, .text-link, .header-cta, .evidence, .price-card, .demo-frame-wrap {
  transition: transform .34s var(--ease), box-shadow .34s var(--ease), opacity .34s var(--ease);
}
.button:hover, .header-cta:hover { transform: translate3d(0, -2px, 0) scale(1.015); }
.button:active, .header-cta:active { transform: translate3d(0, 0, 0) scale(.985); }
.button-primary:hover { box-shadow: 0 18px 44px rgba(109, 40, 217, .34); }
.text-link:hover { transform: translate3d(3px, 0, 0); }
.evidence:hover, .price-card:hover { transform: translate3d(0, -3px, 0); }
.demo-frame-wrap:hover { transform: translate3d(0, -4px, 0); box-shadow: 0 30px 70px rgba(11, 7, 19, .16); }

/* The arrow in a button leans into the hover rather than the whole label
   sliding, which reads as intent instead of drift. */
.button span[aria-hidden], .text-link span[aria-hidden] {
  display: inline-block;
  transition: transform .34s var(--ease);
}
.button:hover span[aria-hidden], .text-link:hover span[aria-hidden] { transform: translate3d(5px, 0, 0); }

/* --------------------------------------------------------------- header
   Condenses once the page has moved, so the nav stops competing with the hero. */
.site-header { transition: height .4s var(--ease), background-color .4s var(--ease), box-shadow .4s var(--ease); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(11, 7, 19, .07), 0 10px 30px rgba(11, 7, 19, .05); }

/* ----------------------------------------------------------- scroll cue
   A hairline that fills across the top as the page is read. Cheap, and it makes
   a long page feel finite. */
.progress-rail {
  position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 60;
  background: linear-gradient(90deg, #7C3AED, #22D3EE, #F472B6);
  transform: scaleX(0); transform-origin: 0 50%;
  will-change: transform; pointer-events: none;
}

/* -------------------------------------------------------------- reduced
   Everything above is decoration. With reduced motion requested, content is
   simply present: no transforms, no transitions, no video. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .reveal, .stage, .evidence, .chip-list li, .ownership-terms > * { opacity: 1 !important; transform: none !important; }
  .hero-knot { animation: none !important; }
  .rings-v { display: none; }
  .rings-still { display: block !important; opacity: 1 !important; }
  .progress-rail { display: none; }
}
