/* ==========================================================================
   Nxtcell Mobility, elevation layer
   Loads after site.css. Depth, texture, and the motion system.
   Everything here animates transform / opacity / filter only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. BROWSER SURFACES
   The parts we did not draw still carry the design.
   -------------------------------------------------------------------------- */
::selection{background:var(--blue);color:#fff}
::-moz-selection{background:var(--blue);color:#fff}
html{scrollbar-color:var(--ink-40) transparent;scrollbar-width:thin;caret-color:var(--blue-700)}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:var(--bg-soft)}
::-webkit-scrollbar-thumb{background:var(--line);border:3px solid var(--bg-soft)}
::-webkit-scrollbar-thumb:hover{background:var(--ink-40)}
a{text-underline-offset:.22em;text-decoration-thickness:1px}
.band ::selection,.globe ::selection,.teaser ::selection,.upcoming ::selection{background:var(--cyan);color:var(--navy)}

/* --------------------------------------------------------------------------
   2. SCROLL PROGRESS
   Anchored to the document timeline, so it costs the main thread nothing.
   -------------------------------------------------------------------------- */
.progress{
  position:fixed;top:0;left:0;right:0;height:2px;z-index:120;
  background:linear-gradient(90deg,var(--blue-700),var(--cyan));
  transform-origin:0 50%;
  transform:scaleX(0);
  pointer-events:none;
}
@supports (animation-timeline:scroll()){
  .progress{
    animation:progressGrow linear both;
    animation-timeline:scroll(root block);
  }
}
@keyframes progressGrow{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* --------------------------------------------------------------------------
   3. SCROLL REVEAL
   Native scroll-driven animation where supported, IntersectionObserver
   elsewhere. Blur resolves as the element settles, which reads as depth of
   field rather than a plain fade.
   -------------------------------------------------------------------------- */
.reveal{
  opacity:0;
  transform:translate3d(0,20px,0);
  transition:opacity var(--dur-reveal) var(--ease-out),
             transform var(--dur-reveal) var(--ease-out);
  will-change:transform,opacity;
}
.reveal.in{opacity:1;transform:none}
.reveal.in{will-change:auto}

/* stagger children of a revealed group */
.stagger.in > *{animation:riseIn .5s var(--ease-out) both}
.stagger.in > *:nth-child(1){animation-delay:.02s}
.stagger.in > *:nth-child(2){animation-delay:.09s}
.stagger.in > *:nth-child(3){animation-delay:.16s}
.stagger.in > *:nth-child(4){animation-delay:.23s}
.stagger.in > *:nth-child(5){animation-delay:.30s}
.stagger.in > *:nth-child(6){animation-delay:.37s}
@keyframes riseIn{
  from{opacity:0;transform:translate3d(0,18px,0)}
  to{opacity:1;transform:none}
}

/* --------------------------------------------------------------------------
   4. HEADLINE MASK REVEAL
   Each line sits in a clipped box and slides up from behind it. Applied by
   site.js, which splits the heading into lines first.
   -------------------------------------------------------------------------- */
.lsplit__line{display:block;overflow:hidden;padding-bottom:.08em;margin-bottom:-.08em}
.lsplit__line > span{
  display:block;
  transform:translate3d(0,110%,0);
  transition:transform .62s var(--ease-out);
}
.lsplit.in .lsplit__line > span{transform:none}
.lsplit.in .lsplit__line:nth-child(2) > span{transition-delay:.05s}
.lsplit.in .lsplit__line:nth-child(3) > span{transition-delay:.10s}
.lsplit.in .lsplit__line:nth-child(4) > span{transition-delay:.15s}



/* --------------------------------------------------------------------------
   5. PARALLAX
   Driven by the view timeline where available: the element drifts as it
   crosses the viewport. No scroll listener, no layout thrash.
   -------------------------------------------------------------------------- */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .parallax{
      animation:drift linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
    @keyframes drift{
      from{transform:translate3d(0,var(--drift,38px),0)}
      to{transform:translate3d(0,calc(var(--drift,38px) * -1),0)}
    }
    .parallax--slow{--drift:20px}
    .parallax--fast{--drift:62px}

  }
}

/* --------------------------------------------------------------------------
   6. HEADER
   Condenses on scroll into a tighter, more opaque bar.
   -------------------------------------------------------------------------- */
.site-header{
  transition:background-color var(--dur-panel) var(--ease-out),
             border-color var(--dur-panel) var(--ease-out);
}
.site-header.is-stuck{background:rgba(255,255,255,.86);border-bottom-color:var(--line)}
/* The stuck state is expressed through material, not geometry. Transforming
   children inside a backdrop-filter element makes Chromium mis-sample the
   backdrop and paint white blocks behind them. */
.site-header.is-stuck{border-bottom-color:transparent;box-shadow:0 10px 30px -22px rgba(10,24,38,.55)}

/* nav underline grows from the centre */
.nav__links a::after{
  left:.78rem;right:.78rem;
  transform:scaleX(0);
  transform-origin:50% 50%;
  transition:transform .42s var(--ease-out);
}
@media (hover:hover) and (pointer:fine){
  .nav__links a:hover::after{transform:scaleX(1)}
}
.nav__links a[aria-current="page"]::after{transform:scaleX(1)}

/* --------------------------------------------------------------------------
   7. BUTTONS
   Physical press, and a trailing icon that carries its own kinetic tension.
   -------------------------------------------------------------------------- */
.btn{
  position:relative;
  transition:background-color var(--dur-hover) var(--ease-out),
             color var(--dur-hover) var(--ease-out),
             border-color var(--dur-hover) var(--ease-out),
             transform var(--dur-press) var(--ease-out),
             box-shadow var(--dur-hover) var(--ease-out);
}
.btn:active{transform:scale(.975)}
@media (hover:hover) and (pointer:fine){
  .btn--primary:hover{box-shadow:var(--shadow-blue)}
  .btn--onDark:hover{box-shadow:0 16px 40px -18px rgba(111,198,236,.7)}
}
.btn svg{transition:transform .38s var(--ease-out)}
@media (hover:hover) and (pointer:fine){
  .btn:hover svg{transform:translate3d(4px,0,0)}
}

.arrowlink{transition:color var(--dur-hover) var(--ease-out),border-color var(--dur-hover) var(--ease-out)}
.arrowlink svg{transition:transform .38s var(--ease-out)}

/* --------------------------------------------------------------------------
   8. CARDS
   A cursor-tracked sheen, and a lift with a navy-tinted shadow.
   --mx / --my are set by site.js on pointer move.
   -------------------------------------------------------------------------- */
.card,.mcard,.partner-col,.service__card,.nxtp__item,.upcard{
  position:relative;
  transition:transform .5s var(--ease-out),
             border-color .4s var(--ease-out),
             box-shadow .5s var(--ease-out),
             background-color .4s var(--ease-out);
}
/* .card and .mcard are <article>, not links, so they do not lift as if they
   were clickable. The edge and the spotlight carry the hover instead. */
@media (hover:hover) and (pointer:fine){
  .card:hover,.mcard:hover{border-color:var(--ink-40)}
}

/* spotlight that follows the cursor across a card */
.spot::before{
  content:"";
  position:absolute;inset:0;
  pointer-events:none;
  opacity:0;
  transition:opacity .45s var(--ease-out);
  background:radial-gradient(360px circle at var(--mx,50%) var(--my,50%),
             rgba(29,156,215,.13), transparent 62%);
}
@media (hover:hover) and (pointer:fine){
  .spot:hover::before{opacity:1}
}
.band .spot::before,.upcoming .spot::before,.globe .spot::before,.teaser .spot::before{
  background:radial-gradient(360px circle at var(--mx,50%) var(--my,50%),
             rgba(111,198,236,.16), transparent 62%);
}

/* --------------------------------------------------------------------------
   9. MEDIA
   Images sit in a clipped frame and scale under it, so the frame stays put.
   -------------------------------------------------------------------------- */
.split__media,.mcard__media,.hero__media{overflow:hidden}
.split__media img{transition:transform .6s var(--ease-out)}
@media (hover:hover) and (pointer:fine){
  .split__media:hover img{transform:scale(1.035)}
}
.mcard__media img{transition:transform .8s var(--ease-out)}
@media (hover:hover) and (pointer:fine){
  .mcard:hover .mcard__media img{transform:scale(1.06)}
}

/* --------------------------------------------------------------------------
   10. BANNER
   The carousel gets depth: the active slide's art settles forward while the
   copy rises behind it.
   -------------------------------------------------------------------------- */
.banner__track{transition:transform .55s var(--ease-drawer)}
/* Transitions, not keyframes: a slide change can be fired twice a second and
   a transition retargets from wherever the value currently is. */
.banner__slide .banner__media img,
.banner__slide .banner__eyebrow,
.banner__slide .banner__inner h2,
.banner__slide .banner__inner > div > p,
.banner__slide .banner__specs,
.banner__slide .btn-row{
  opacity:1;transform:none;
  transition:opacity .42s var(--ease-out),transform .42s var(--ease-out);
}
.banner__slide[aria-hidden="true"] .banner__media img,
.banner__slide[aria-hidden="true"] .banner__eyebrow,
.banner__slide[aria-hidden="true"] .banner__inner h2,
.banner__slide[aria-hidden="true"] .banner__inner > div > p,
.banner__slide[aria-hidden="true"] .banner__specs,
.banner__slide[aria-hidden="true"] .btn-row{opacity:0;transform:translate3d(0,18px,0)}
.banner__slide[aria-hidden="true"] .banner__media img{transform:translate3d(0,22px,0) scale(.975)}
.banner__slide[aria-hidden="false"] .banner__inner h2{transition-delay:.04s}
.banner__slide[aria-hidden="false"] .banner__inner > div > p{transition-delay:.08s}
.banner__slide[aria-hidden="false"] .banner__specs{transition-delay:.12s}
.banner__slide[aria-hidden="false"] .btn-row{transition-delay:.16s}

/* the dots become a progress rail that fills over the slide's dwell time */
.banner__dots button{position:relative;overflow:hidden}
.banner__dots button[aria-selected="true"]::after{
  content:"";position:absolute;inset:auto 0;height:2px;
  background:var(--blue);
  transform-origin:0 50%;
  animation:dwell 5s linear both;
}
.banner__dots.on-dark button[aria-selected="true"]::after{background:var(--cyan)}
@keyframes dwell{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.banner.is-paused .banner__dots button[aria-selected="true"]::after{animation-play-state:paused}

.banner__arrow{transition:transform var(--dur-press) var(--ease-out),
                          background-color var(--dur-hover) var(--ease-out),
                          border-color var(--dur-hover) var(--ease-out),
                          color var(--dur-hover) var(--ease-out)}
.banner__arrow:active{transform:translateY(-50%) scale(.92)}

/* --------------------------------------------------------------------------
   11. STATS
   Tabular figures so counting numbers never reflow their neighbours.
   -------------------------------------------------------------------------- */
.stat b,.service__value,.banner__spec b{font-variant-numeric:tabular-nums}
.stat.counting b{color:var(--blue-700)}
.band .stat.counting b{color:var(--cyan)}
.stat{position:relative;overflow:hidden}
.stat::before{
  content:"";position:absolute;top:0;left:0;right:0;height:1px;background:var(--blue);
  transform:scaleX(0);transform-origin:0 50%;
  transition:transform .9s var(--ease-out);
}
.reveal.in .stat::before,.stagger.in .stat::before{transform:scaleX(1)}
.band .stat::before{background:var(--cyan)}

/* --------------------------------------------------------------------------
   12. MARQUEE
   A continuous partner ticker. Duplicated content in the markup keeps the
   loop seamless; the animation is linear because constant motion should be.
   -------------------------------------------------------------------------- */
.marquee{
  position:relative;
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent);
}
.marquee__track{
  display:flex;
  width:max-content;
  animation:slide 38s linear infinite;
}
@media (hover:hover) and (pointer:fine){
  .marquee:hover .marquee__track{animation-play-state:paused}
}
@keyframes slide{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
.marquee__item{
  display:flex;align-items:center;gap:.7rem;
  padding:0 clamp(1.6rem,3vw,3rem);
  font-family:var(--font-head);font-weight:600;font-stretch:104%;
  font-size:clamp(1rem,.9rem + .4vw,1.25rem);
  color:var(--ink-55);
  white-space:nowrap;
}
.marquee__item::before{
  content:"";width:5px;height:5px;background:var(--blue);flex:none;
}
.band .marquee__item,.upcoming .marquee__item{color:rgba(255,255,255,.6)}
.band .marquee__item::before{background:var(--cyan)}
@media (prefers-reduced-motion:reduce){
  .marquee__track{animation:none;width:100%;justify-content:center;flex-wrap:wrap;gap:.5rem 0}
  .marquee{-webkit-mask-image:none;mask-image:none}
}

/* --------------------------------------------------------------------------
   13. SECTION RULE
   A hairline that draws itself across the section as it enters.
   -------------------------------------------------------------------------- */
.ruleline{height:1px;background:var(--line);transform:scaleX(0);transform-origin:0 50%;
  transition:transform .7s var(--ease-out)}
.reveal.in .ruleline,.ruleline.in{transform:scaleX(1)}
.band .ruleline,.globe .ruleline,.upcoming .ruleline{background:var(--navy-line)}

/* --------------------------------------------------------------------------
   14. EYEBROW
   The leading rule extends as the section arrives.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   15. FOCUS
   A visible, branded focus ring that never depends on colour alone.
   -------------------------------------------------------------------------- */
:focus-visible{
  outline:2px solid var(--blue);
  outline-offset:3px;
  border-radius:1px;
}

/* --------------------------------------------------------------------------
   16. REDUCED MOTION
   Fewer and gentler, not zero. Comprehension-aiding fades stay; travel goes.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .reveal,.reveal.in{opacity:1;transform:none;transition:opacity .2s var(--ease-out)}
  .stagger.in > *{animation:none}
  .lsplit__line > span{transform:none;transition:none}
  .parallax{animation:none!important}
  .banner__slide[aria-hidden="false"] .banner__media img,
  .banner__slide[aria-hidden="false"] .banner__eyebrow,
  .banner__slide[aria-hidden="false"] .banner__inner h2,
  .banner__slide[aria-hidden="false"] .banner__inner > div > p,
  .banner__slide[aria-hidden="false"] .banner__specs,
  .banner__slide[aria-hidden="false"] .btn-row{animation:none}
  .banner__dots button[aria-selected="true"]::after{animation:none;transform:scaleX(1)}
  .card:hover,.mcard:hover{transform:none}

  .ruleline,.stat::before{transform:scaleX(1)!important;transition:none}
}

/* --------------------------------------------------------------------------
   17. FORM FEEDBACK
   The two pages that convert. Errors and status used to pop into existence
   and shove the form down; now they arrive and the eye is led to them.
   -------------------------------------------------------------------------- */
.field .err{
  display:none;
  opacity:0;
  transform:translate3d(0,-4px,0);
  transition:opacity .18s var(--ease-out),transform .18s var(--ease-out);
}
.field .err.show{display:block;opacity:1;transform:none}
@supports (transition-behavior:allow-discrete){
  @starting-style{ .field .err.show{opacity:0;transform:translate3d(0,-4px,0)} }
}

.form-status{
  display:none;
  opacity:0;
  transform:translate3d(0,8px,0);
  transition:opacity .24s var(--ease-out),transform .24s var(--ease-out);
}
.form-status.show{display:block;opacity:1;transform:none}
@supports (transition-behavior:allow-discrete){
  @starting-style{ .form-status.show{opacity:0;transform:translate3d(0,8px,0)} }
}

/* one-shot nudge on the first field that failed, on submit only */
@keyframes nudge{
  0%,100%{transform:none}
  25%{transform:translate3d(-3px,0,0)}
  75%{transform:translate3d(3px,0,0)}
}
.field input.just-failed,
.field textarea.just-failed,
.field select.just-failed{animation:nudge .18s var(--ease-out)}

/* in-flight submit: the label yields, a ring spins in its place */
.btn[data-state="sending"]{pointer-events:none}
.btn[data-state="sending"] .btn__label{opacity:0;transform:translate3d(0,-6px,0)}
.btn__label{
  display:inline-flex;align-items:center;gap:.6rem;
  transition:opacity .16s var(--ease-out),transform .16s var(--ease-out);
}
.btn[data-state="sending"]::after{
  content:"";
  position:absolute;inset:0;margin:auto;
  width:15px;height:15px;
  border:1.5px solid currentColor;
  border-top-color:transparent;
  border-radius:50%;
  animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}

@media (prefers-reduced-motion:reduce){
  .field .err,.form-status{transform:none;transition:opacity .2s var(--ease-out)}
  .field .err.show,.form-status.show{transform:none}
  .field input.just-failed,.field textarea.just-failed,.field select.just-failed{animation:none}
  .btn[data-state="sending"]::after{animation:none;border-top-color:currentColor;opacity:.5}
}

/* --------------------------------------------------------------------------
   18. LINKS ON DARK SURFACES
   The default hover (blue-800) fails contrast against navy.
   -------------------------------------------------------------------------- */
/* buttons carry their own colour pair; only plain links recolour */
.band a:not(.btn):hover,.globe a:not(.btn):hover,.teaser a:not(.btn):hover,
.upcoming a:not(.btn):hover,.brandcard--dark a:not(.btn):hover,
.site-footer a:not(.btn):hover,.banner__slide--c a:not(.btn):hover{color:var(--cyan)}

/* --------------------------------------------------------------------------
   19. TOUCH
   Every :hover in the system is now gated behind a fine pointer, so touch
   needs its own feedback. Press, not release, and only on things you can act on.
   -------------------------------------------------------------------------- */
.btn,.nav__toggle,.banner__dots button,.banner__arrow,
.teaser__cats li,.upcard__list li,.marquee__item{
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
}
a,button,[role="button"],summary{touch-action:manipulation}

@media (hover:none){
  .card:active,.mcard:active,.upcard:active,.service__card:active,.partner-col:active{
    transform:scale(.99);
    transition:transform 120ms var(--ease-out);
  }
  .nav__toggle:active,.nav__links a:active,.arrowlink:active,.site-footer a:active{
    opacity:.6;
    transition:opacity 100ms var(--ease-out);
  }
  /* the cursor spotlight has no meaning without a cursor */
  .spot::before{display:none}
}

/* --------------------------------------------------------------------------
   20. SAFE AREA
   The header, the nav panel and the footer all reach the device edges.
   -------------------------------------------------------------------------- */
.wrap{padding-inline:max(var(--pad),env(safe-area-inset-left,0px),env(safe-area-inset-right,0px))}
.site-footer{padding-bottom:env(safe-area-inset-bottom,0px)}
@media (max-width:1020px){
  .nav__links{padding-bottom:calc(1.4rem + env(safe-area-inset-bottom,0px))}
}

/* the partner ticker reads as a deliberate band, not floating text */
.marquee{
  border-block:1px solid var(--line);
  padding-block:1.1rem;
  margin-bottom:clamp(2rem,3.5vw,3.2rem);
}
.marquee__item{padding:0 clamp(1.4rem,2.6vw,2.4rem)}

a.btn:hover{color:#fff}
a.btn--onDark:hover,a.btn--ghost:hover{color:var(--navy)}
a.btn--ghost:hover{color:#fff}
a.btn--outlineDark:hover{color:var(--cyan)}
