/* ==========================================================================
   Nxtcell Mobility, signal layer
   Loads last. One idea: data moving through the page.
   Every rule animates transform, opacity, filter or clip-path only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. THE SIGNAL FIELD
   Canvas sits behind the hero and the dark bands, never above content.
   -------------------------------------------------------------------------- */
.sigfield{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  pointer-events:none;
  z-index:0;
}
/* The falloff that used to live here as a mask-image is now baked into
   each dot's alpha in signal.js: masking a full-width layer cost more per
   frame than drawing the dots. See Field.prototype.maskAt. */
.banner .wrap,.teaser__grid,.band .wrap,.statement,
.globe .wrap,.upcoming .wrap{position:relative;z-index:1}
.band,.teaser,.globe,.upcoming{position:relative}
.banner__slide{position:relative}

/* the hero art and copy sit above the field */
.banner__inner{position:relative;z-index:1}

/* --------------------------------------------------------------------------
   2. LOAD CHOREOGRAPHY
   The masthead and the first slide arrive in sequence rather than all at once.
   Driven by a class on <html> so nothing flashes before the stylesheet lands.
   -------------------------------------------------------------------------- */
.js-load .site-header{
  opacity:0;
  transform:translate3d(0,-100%,0);
}
.js-load .banner__slide[aria-hidden="false"] .banner__inner > div > *,
.js-load .banner__slide:first-child .banner__inner > div > *{opacity:0}
.js-load .banner__media img{opacity:0}

.is-ready .site-header{
  opacity:1;
  transform:none;
  transition:opacity .5s var(--ease-out),transform .72s var(--ease-drawer);
}
.is-ready .banner__media img{
  opacity:1;
  transition:opacity .8s var(--ease-out) .34s,transform .9s var(--ease-drawer) .34s;
}
.is-ready .banner__slide:first-child .banner__inner > div > *{
  opacity:1;
  animation:liftIn .7s var(--ease-out) both;
}
.is-ready .banner__slide:first-child .banner__inner > div > *:nth-child(1){animation-delay:.16s}
.is-ready .banner__slide:first-child .banner__inner > div > *:nth-child(2){animation-delay:.23s}
.is-ready .banner__slide:first-child .banner__inner > div > *:nth-child(3){animation-delay:.30s}
.is-ready .banner__slide:first-child .banner__inner > div > *:nth-child(4){animation-delay:.37s}
.is-ready .banner__slide:first-child .banner__inner > div > *:nth-child(5){animation-delay:.44s}
@keyframes liftIn{
  from{opacity:0;transform:translate3d(0,22px,0)}
  to{opacity:1;transform:none}
}

/* --------------------------------------------------------------------------
   3. KINETIC STATEMENT
   The purpose line resolves word by word as the section crosses the viewport,
   scrubbed by scroll position rather than played on a timer.
   -------------------------------------------------------------------------- */
.kin{display:inline}
.kin__w{
  display:inline-block;
  opacity:.32;
  transform:translate3d(0,.14em,0);
  transition:opacity .5s var(--ease-out),transform .5s var(--ease-out);
}
.kin.in .kin__w{opacity:1;transform:none}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .kin__w{
      opacity:.32;
      animation:wordIn linear both;
      animation-timeline:view();
      animation-range:entry 22% cover 46%;
      transition:none;
    }
    @keyframes wordIn{
      from{opacity:.32;transform:translate3d(0,.14em,0);filter:blur(2px)}
      to{opacity:1;transform:none;filter:blur(0)}
    }
    /* each word starts fractionally later, so the line resolves left to right */
    .kin__w:nth-child(4n+2){animation-range:entry 26% cover 50%}
    .kin__w:nth-child(4n+3){animation-range:entry 30% cover 54%}
    .kin__w:nth-child(4n+4){animation-range:entry 34% cover 58%}
  }
}

/* --------------------------------------------------------------------------
   4. STACKING BRAND CARDS
   The two brands hold at the top of the viewport and the second settles over
   the first, so the comparison is felt rather than just read.
   -------------------------------------------------------------------------- */
/* Enabled on phones too: the cards measure about 470px against a 771px
   usable viewport at 390px wide, so the stack has the room it needs. */
@media (min-width:380px) and (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    .stack{display:block}
    .stack .brandcard{
      position:sticky;
      top:var(--stack-top,96px);
      margin:0;
    }
    .stack .brandcard:nth-child(1){
      z-index:1;
      animation:stackBack linear both;
      animation-timeline:view();
      animation-range:exit -10% exit 90%;
    }
    .stack .brandcard:nth-child(2){
      z-index:2;
      margin-top:clamp(28px,4vw,60px);
      box-shadow:0 -30px 70px -40px rgba(10,24,38,.55);
    }
    @keyframes stackBack{
      from{transform:none;filter:none}
      to{transform:scale(.955) translate3d(0,-14px,0);filter:brightness(.94)}
    }
    @media (max-width:820px){
      .stack .brandcard{top:var(--stack-top-m,84px)}
      .stack .brandcard:nth-child(2){margin-top:22px}
    }
  }
}

/* --------------------------------------------------------------------------
   5. MAP PULSES
   A signal travels each arc continuously, so the network reads as live.
   -------------------------------------------------------------------------- */
.pulse{
  fill:var(--cyan);
  opacity:0;
}
.in .pulse{
  animation:pulseRun 3.2s var(--ease-in-out) infinite;
  offset-rotate:0deg;
}
.in .pulse:nth-of-type(2){animation-delay:.8s}
.in .pulse:nth-of-type(3){animation-delay:1.6s}
.in .pulse:nth-of-type(4){animation-delay:2.4s}
@keyframes pulseRun{
  0%{offset-distance:0%;opacity:0}
  12%{opacity:1}
  85%{opacity:1}
  100%{offset-distance:100%;opacity:0}
}

/* --------------------------------------------------------------------------
   6. MAGNETIC CONTROLS
   The button leans toward the cursor. --mgx / --mgy are written by site.js.
   Fine pointers only: there is nothing to attract on a touchscreen.
   -------------------------------------------------------------------------- */
@media (hover:hover) and (pointer:fine){
  .magnet{
    transform:translate3d(var(--mgx,0px),var(--mgy,0px),0);
    transition:transform .34s var(--ease-out);
  }
  .magnet.is-pulled{transition:transform .12s linear}
}

/* --------------------------------------------------------------------------
   7. SCAN
   A hairline sweeps a stat block once as it arrives, like a reading head.
   -------------------------------------------------------------------------- */
.scan{position:relative;overflow:hidden}
.scan::after{
  content:"";
  position:absolute;top:0;bottom:0;width:38%;
  background:linear-gradient(90deg,transparent,rgba(29,156,215,.14),transparent);
  transform:translate3d(-140%,0,0);
  pointer-events:none;
}
.reveal.in .scan::after,.scan.in::after{
  animation:scanSweep 1.5s var(--ease-out) .25s both;
}
.band .scan::after,.globe .scan::after{
  background:linear-gradient(90deg,transparent,rgba(111,198,236,.16),transparent);
}
@keyframes scanSweep{
  from{transform:translate3d(-140%,0,0)}
  to{transform:translate3d(340%,0,0)}
}

/* --------------------------------------------------------------------------
   8. HERO PRODUCT DRIFT
   The product holds a touch longer than the page as you scroll away from it.
   -------------------------------------------------------------------------- */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference) and (min-width:881px){
    .banner__slide[aria-hidden="false"] .banner__media img{
      animation:heroDrift linear both;
      animation-timeline:view();
      animation-range:exit 0% exit 100%;
    }
    @keyframes heroDrift{
      from{transform:translate3d(0,0,0) rotate(0deg)}
      to{transform:translate3d(0,-52px,0) rotate(-2.2deg)}
    }
  }
}

/* --------------------------------------------------------------------------
   9. LINK UNDERLINE
   Plain prose links get a line that wipes in from the left.
   -------------------------------------------------------------------------- */
.prose a:not(.btn),.split p a:not(.btn),.lede a:not(.btn){
  background-image:linear-gradient(currentColor,currentColor);
  background-size:0% 1px;
  background-repeat:no-repeat;
  background-position:0 100%;
  transition:background-size .34s var(--ease-out);
}
@media (hover:hover) and (pointer:fine){
  .prose a:not(.btn):hover,.split p a:not(.btn):hover,.lede a:not(.btn):hover{background-size:100% 1px}
}

/* --------------------------------------------------------------------------
   10. REDUCED MOTION
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .js-load .site-header,.js-load .banner__media img,
  .js-load .banner__slide:first-child .banner__inner > div > *{opacity:1;transform:none}
  .is-ready .banner__slide:first-child .banner__inner > div > *{animation:none}
  .kin__w{opacity:1;transform:none;filter:none;animation:none;transition:none}
  .stack .brandcard{position:static;animation:none;transform:none;filter:none}
  .in .pulse{animation:none;opacity:0}
  .magnet{transform:none!important}
  .scan::after{animation:none!important;display:none}
  .banner__slide .banner__media img{animation:none}
}
