/* ===========================================================================
   motion.css · the animation language
   Entrances are pure CSS transitions driven by `data-a` (variant) and
   `data-at` (delay, ms -> --at). The engine only toggles `.is-in` on a slide,
   so a whole scene stages itself with no JS timers to drift or leak.
   =========================================================================== */

/* --- entrance base ------------------------------------------------------- */
[data-a]{
  opacity:0;
  transition:
    opacity 900ms var(--ease) var(--at,0ms),
    transform 1150ms var(--ease) var(--at,0ms),
    filter 900ms var(--ease) var(--at,0ms),
    clip-path 1150ms var(--ease) var(--at,0ms);
  will-change:opacity,transform;
}
.slide.is-in [data-a]{opacity:1;transform:none;filter:none}

/* --- entrance variants --------------------------------------------------- */
[data-a="up"]     {transform:translate3d(0,42px,0);filter:blur(8px)}
[data-a="down"]   {transform:translate3d(0,-38px,0);filter:blur(8px)}
[data-a="left"]   {transform:translate3d(-56px,0,0);filter:blur(8px)}
[data-a="right"]  {transform:translate3d(56px,0,0);filter:blur(8px)}
[data-a="fade"]   {filter:blur(10px)}
[data-a="zoom"]   {transform:scale(.90);filter:blur(12px)}
[data-a="pop"]    {transform:scale(.72);filter:blur(6px)}
[data-a="rise"]   {transform:perspective(1400px) translate3d(0,60px,-60px) rotateX(9deg);filter:blur(10px)}
[data-a="tilt"]   {transform:perspective(1600px) translate3d(70px,30px,-120px) rotateY(-11deg);filter:blur(12px)}
[data-a="clip"]   {clip-path:inset(0 100% 0 0);transform:translate3d(0,16px,0)}
.slide.is-in [data-a="clip"]{clip-path:inset(0 0 0 0)}
[data-a="curtain"]{clip-path:inset(0 0 100% 0);transform:translate3d(0,26px,0)}
.slide.is-in [data-a="curtain"]{clip-path:inset(0 0 0 0)}
[data-a="wipe"]   {clip-path:inset(0 0 0 100%)}
.slide.is-in [data-a="wipe"]{clip-path:inset(0 0 0 0)}
[data-a="blurin"] {filter:blur(24px);transform:scale(1.06)}
[data-a="grow"]   {transform:scaleX(0);transform-origin:left center}
[data-a="growy"]  {transform:scaleY(0);transform-origin:bottom center}
[data-a="growc"]  {transform:scaleX(0);transform-origin:center}
/* a card taking its place on a rail: it rises AND settles into size, so the
   row assembles instead of materialising in position */
[data-a="place"]  {transform:translate3d(0,38px,0) scale(.90);filter:blur(9px)}

/* pop overshoots — a touch of spring where it reads as delight, not bounce */
[data-a="pop"],[data-a="zoom"]{transition-timing-function:cubic-bezier(.2,1.35,.4,1)}
/* the card the frame is about settles with a little more spring than its row */
.gn.is-new[data-a="place"]{transition-timing-function:cubic-bezier(.2,1.26,.4,1)}

/* --- headline word cascade ---------------------------------------------- */
/* JS wraps each word in <i data-w>; they lift out of a clipped line box.    */
.cascade{display:block}
.cascade .wln{display:block;overflow:hidden;padding-bottom:.20em;margin-bottom:-.20em}
.cascade i[data-w]{display:inline-block;font-style:inherit;
  transform:translate3d(0,110%,0) rotate(3deg);opacity:0;
  transition:transform 1150ms var(--ease),opacity 700ms var(--ease);
  transition-delay:calc(var(--at,0ms) + var(--wi,0) * 62ms)}
.slide.is-in .cascade i[data-w]{transform:none;opacity:1}

/* --- SVG stroke draw ----------------------------------------------------- */
[data-draw]{stroke-dasharray:100;stroke-dashoffset:100;
  transition:stroke-dashoffset 1600ms var(--ease) var(--at,0ms)}
.slide.is-in [data-draw]{stroke-dashoffset:0}

/* --- ambient loops ------------------------------------------------------- */
/* light sweeping across a panel — the single most "video" gesture here      */
.sweep{position:relative;overflow:hidden}
.sweep::after{content:'';position:absolute;top:0;bottom:0;width:58%;left:-70%;pointer-events:none;z-index:1;
  background:linear-gradient(100deg,transparent,rgb(150 205 255 / .07),transparent);
  transform:skewX(-14deg);filter:blur(6px)}
.slide.is-in .sweep::after{animation:sweep 2600ms var(--ease-soft) var(--at,0ms) 1 both}
@keyframes sweep{from{left:-60%}to{left:130%}}

/* slow shimmer that keeps looping on hero type */
.shine{background-image:linear-gradient(100deg,#fff 30%,#9cc8ff 45%,#fff 60%);
  background-size:280% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
  animation:shine 7s var(--ease-soft) infinite}
@keyframes shine{0%{background-position:180% 0}55%,100%{background-position:-80% 0}}

.float{animation:float 9s var(--ease-soft) infinite alternate}
@keyframes float{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,-14px,0)}}

.spin-slow{animation:spin 44s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

.pulse{animation:pulse 3.4s var(--ease-soft) infinite}
@keyframes pulse{0%,100%{opacity:.35;transform:scale(1)}50%{opacity:.9;transform:scale(1.05)}}

/* expanding rings — used under logos and AI nodes */
.rings{position:absolute;inset:0;pointer-events:none}
.rings i{position:absolute;left:50%;top:50%;width:var(--r,240px);height:var(--r,240px);margin:calc(var(--r,240px) / -2);
  border-radius:50%;border:1px solid rgb(120 190 255 / .40);opacity:0}
.slide.is-in .rings i{animation:ripple 4.6s var(--ease-soft) infinite}
/* a node waiting to be lit holds its ripple until it turns on */
.gn:not(.is-new) .rings i{animation:none!important}
.rings i:nth-child(2){animation-delay:1.5s}
.rings i:nth-child(3){animation-delay:3s}
@keyframes ripple{0%{transform:scale(.55);opacity:0}18%{opacity:.75}100%{transform:scale(2.1);opacity:0}}

/* travelling pulse along a connector path */
.flowline{stroke-dasharray:6 260;stroke-dashoffset:266}
.slide.is-in .flowline{animation:flow 3.2s linear var(--at,0ms) infinite}
@keyframes flow{to{stroke-dashoffset:-266}}

/* scanning bar for "processing" states */
.scanbar{position:absolute;left:0;right:0;height:2px;top:0;
  background:linear-gradient(90deg,transparent,var(--sky),transparent);opacity:.85;
  box-shadow:0 0 18px var(--glow-cyan)}
.slide.is-in .scanbar{animation:scan 2.8s var(--ease-soft) var(--at,0ms) infinite}
@keyframes scan{0%{top:0;opacity:0}12%{opacity:.9}88%{opacity:.9}100%{top:100%;opacity:0}}

/* typing caret */
.caret{display:inline-block;width:2px;height:1em;vertical-align:-.14em;margin-left:3px;
  background:var(--sky);animation:caret 1s steps(1) infinite}
@keyframes caret{0%,49%{opacity:1}50%,100%{opacity:0}}

/* counting numbers land with a subtle glow flash */
.slide.is-in [data-count]{animation:numflash 1400ms var(--ease) var(--at,0ms) both}
@keyframes numflash{
  0%{text-shadow:0 0 0 rgb(93 164 255 / 0)}
  35%{text-shadow:0 0 46px rgb(93 164 255 / .85)}
  100%{text-shadow:0 0 26px rgb(93 164 255 / .30)}
}

/* bar chart growth */
[data-bar]{transform:scaleY(0);transform-origin:bottom;
  transition:transform 1200ms cubic-bezier(.2,1.1,.35,1) var(--at,0ms)}
.slide.is-in [data-bar]{transform:scaleY(1)}

/* donut / gauge sweep */
[data-arc]{stroke-dasharray:var(--len,100) 1000;stroke-dashoffset:var(--len,100);
  transition:stroke-dashoffset 1700ms var(--ease) var(--at,0ms)}
.slide.is-in [data-arc]{stroke-dashoffset:0}

/* --- continuous sequences ------------------------------------------------
   Slides in the same `data-group` are frames of ONE diagram, not separate
   scenes. Moving between them must not dissolve: the shared furniture would
   flicker out and back in and the diagram would stop reading as continuous.
   So the cut is instant, everything marked `data-hold` arrives already
   finished, and only what actually changed animates.                        */
#deck.is-continuous .slide.is-active{animation:none!important}
#deck.is-continuous .slide.is-leaving{animation:none!important;display:none!important}

/* held only while the frame is still showing the diagram — a slide that goes
   on to collapse into its conclusion has to be free to move again */
.slide.from-group:not(.is-merged) [data-hold]{
  opacity:1!important;transform:none!important;filter:none!important;
  clip-path:none!important;transition:none!important}
/* the staggered entrance is for arriving at a scene, not for stepping inside it */
.slide.from-group [data-a]{--at:0ms!important}
.slide.from-group .grow-fill{transition-duration:900ms}
/* a held node still lights up — that transition lives on its children */
.slide.from-group .gn .disc,
.slide.from-group .gn b{transition-duration:820ms}
/* mid-sequence a node ignites after a beat, so its ripple waits for it */
.slide.from-group:not(.is-rewind) .gn.is-new .disc,
.slide.from-group:not(.is-rewind) .gn.is-new b{transition-delay:420ms}
/* the mark starts growing almost at once and only the LIGHT waits for the
   beat, so it is never caught sitting at the wrong size (opacity,filter,transform) */
.slide.from-group:not(.is-rewind) .gn.is-new .disc>img,
.slide.from-group:not(.is-rewind) .gn.is-new .disc>svg{transition-delay:420ms,420ms,170ms}
.slide.from-group:not(.is-rewind) .gn.is-new .rings{animation:rings-hold 760ms linear forwards}
@keyframes rings-hold{0%,55%{opacity:0}100%{opacity:1}}
/* One bloom washes across the card as it comes online. Only the card's
   CONTENTS may move — the card is held furniture, and shifting it would break
   the illusion that the diagram never restarted. */
.gn .disc::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(58% 58% at 50% 50%,rgb(162 206 255 / .50),rgb(120 175 255 / .16) 56%,transparent 74%);
  opacity:0}
.slide.from-group:not(.is-rewind) .gn.is-new .disc::after{animation:ignite 1150ms var(--ease) 400ms both}
@keyframes ignite{0%{opacity:0;transform:scale(.5)}28%{opacity:1;transform:scale(1)}
  100%{opacity:0;transform:scale(1.18)}}
/* The heading is the one thing that must change between frames, so it gets a
   real swap rather than a snap: title, then its rule, then the subtitle. The
   stagger is written as a delay because --at is zeroed for the whole group
   above, and the travel is shorter than a cold entrance so the diagram behind
   it never looks like it restarted. */
.slide.from-group .head [data-a]{transition-duration:560ms,680ms,560ms,680ms}
.slide.from-group .head [data-a="up"]{transform:translate3d(0,26px,0);filter:blur(6px)}
.slide.from-group .head [data-a="left"]{transform:translate3d(-16px,0,0);filter:none}
/* The settled state has to be restated: .slide.is-in [data-a] is LESS specific
   than the two variant rules above, so without this the heading parks at its
   start offset and only cross-fades. */
.slide.from-group.is-in .head [data-a]{transform:none;filter:none}
.slide.from-group .head .head-rule{transition-delay:190ms}
.slide.from-group .head .sub{transition-delay:300ms}

/* --- title-card opening --------------------------------------------------
   The heading arrives centre-frame, holds long enough to be read, then glides
   up into its normal position while the scene fills in behind it. One keyframe
   on the header keeps it entirely declarative — re-entering the slide replays
   it, and there is no state to reset.                                        */
/* The animation runs linear so each phase can carry its own curve. With one
   expo-out across the whole timeline the opening fade was traversed almost
   instantly and the title appeared to pop in. */
/* the delay lets the slide's own dolly land before the title arrives, so the
   two movements read as separate beats rather than one crowded one */
.slide.has-titlecard.is-in .head{animation:titlecard 2900ms linear 620ms both}
@keyframes titlecard{
  /* rise into centre frame */
  0%  {transform:translateY(438px);opacity:0;
       animation-timing-function:cubic-bezier(.33,1,.68,1)}
  18% {transform:translateY(411px);opacity:1;
       animation-timing-function:linear}
  /* hold centre-frame for a full beat before it moves */
  52% {transform:translateY(411px);opacity:1;
       animation-timing-function:cubic-bezier(.16,1,.30,1)}
  /* and settle into the heading position */
  100%{transform:none;opacity:1}
}

/* Centre stage the title reads as two lines — the name large with the platform
   beneath it — and folds into the single heading line as it travels up. The
   offsets are measured on the real layout at activation (deck.js) and handed
   here as custom properties, so this survives any change to the wording. */
.tc-a,.tc-d,.tc-b{display:inline-block}
/* the dash needs its own breathing room: inline-block swallows the spaces */
.tc-d{padding:0 .26em}
.slide.has-titlecard.is-in .tc-a{animation:tc-a 2900ms linear 620ms both}
.slide.has-titlecard.is-in .tc-b{animation:tc-b 2900ms linear 620ms both}
.slide.has-titlecard.is-in .tc-d{animation:tc-d 2900ms linear 620ms both}
@keyframes tc-a{
  0%,52%{transform:translate(var(--ax,0px),var(--ay,0px)) scale(var(--as,1));
         animation-timing-function:cubic-bezier(.16,1,.30,1)}
  100%  {transform:none}
}
@keyframes tc-b{
  0%,52%{transform:translate(var(--bx,0px),var(--by,0px)) scale(var(--bs,1));
         animation-timing-function:cubic-bezier(.16,1,.30,1)}
  100%  {transform:none}
}
@keyframes tc-d{
  0%,46%{opacity:0}
  100%  {opacity:1}
}
/* the kicker would sit across the big centred title, so it waits its turn */
/* the agency mark crowns the title card, then clears out as it merges */
.tc-icon{position:absolute;left:50%;top:-136px;width:120px;height:auto;opacity:0;pointer-events:none;
  transform:translateX(-50%);filter:drop-shadow(0 10px 34px rgb(0 8 26 / .9))}
.slide.has-titlecard.is-in .tc-icon{animation:tc-i 2900ms linear 620ms both}
@keyframes tc-i{
  0%  {opacity:0;transform:translateX(-50%) translateY(18px) scale(.88);
       animation-timing-function:cubic-bezier(.33,1,.68,1)}
  20% {opacity:1;transform:translateX(-50%) translateY(0) scale(1);animation-timing-function:linear}
  52% {opacity:1;transform:translateX(-50%) translateY(0) scale(1);
       animation-timing-function:cubic-bezier(.65,0,.35,1)}
  74% {opacity:0;transform:translateX(-50%) translateY(-30px) scale(.86)}
  100%{opacity:0;transform:translateX(-50%) translateY(-30px) scale(.86)}
}
.slide.has-titlecard.is-in .kicker{animation:tc-k 2900ms linear 620ms both}
@keyframes tc-k{
  0%,58%{opacity:0}
  100%  {opacity:1}
}
/* stepping inside a group, or settling for a still, skips the performance */
.slide.has-titlecard.from-group .head,
.slide.has-titlecard.from-group .tc-a,.slide.has-titlecard.from-group .tc-b,.slide.has-titlecard.from-group .tc-d,
.slide.has-titlecard.from-group .kicker,.slide.has-titlecard.from-group .tc-icon,
#deck.is-still .slide.has-titlecard .tc-icon,
#deck.is-still .slide.has-titlecard .kicker,
#deck.is-still .slide.has-titlecard .head,
#deck.is-still .slide.has-titlecard .tc-a,
#deck.is-still .slide.has-titlecard .tc-b,
#deck.is-still .slide.has-titlecard .tc-d{animation:none!important;transform:none!important;opacity:1!important}

/* --- presenter pause (K) ------------------------------------------------- */
#deck.is-paused *,#deck.is-paused *::before,#deck.is-paused *::after{
  animation-play-state:paused!important}

/* --- replay: commit the pre-entrance state -------------------------------- */
/* Held for one reflow while .is-in comes off, so the scene really is at zero
   before it is asked to play again. Without it the browser interpolates away
   from the finished state and the replay cancels itself. */
.slide.is-rearm [data-a],
.slide.is-rearm [data-draw],
.slide.is-rearm .cascade i[data-w],
.slide.is-rearm .st-cycle span,
.slide.is-rearm .head,
.slide.is-rearm .grow-fill,
.slide.is-rearm .gn .disc,
.slide.is-rearm .gn .disc>img,
.slide.is-rearm .gn .disc>svg,
.slide.is-rearm .lg-bar i::after{transition:none!important;animation:none!important}

/* --- settle: jump every scene to its finished state ---------------------- */
#deck.is-still .slide{animation:none!important}
/* a slide on its way out has no finished state — with its fade-out animation
   suppressed it would otherwise sit fully opaque under the incoming slide */
#deck.is-still .slide.is-leaving{display:none!important}
#deck.is-still .slide:not(.is-merged) [data-a],
#deck.is-still .cascade i[data-w]{
  opacity:1!important;transform:none!important;filter:none!important;
  clip-path:none!important;transition:none!important}
/* A slide that ends by collapsing has a DIFFERENT finished state — the rail is
   gone and the lockup holds the centre — so it opts out of the reset above and
   simply drops the timings instead. */
#deck.is-still .slide.is-merged .gn,
#deck.is-still .slide.is-merged .grow-line,
#deck.is-still .slide.is-merged .grow-fill,
#deck.is-still .slide.is-merged .head,
#deck.is-still .slide.is-merged .eco-final{transition-duration:0ms!important;transition-delay:0ms!important}
#deck.is-still .slide.is-merged .eco-flare{animation:none!important;opacity:0!important}
#deck.is-still .gn .disc>img,
#deck.is-still .gn .disc>svg{transform:none!important;transition:none!important}
#deck.is-still [data-draw],#deck.is-still [data-arc]{stroke-dashoffset:0!important;transition:none!important}
#deck.is-still [data-bar]{transform:scaleY(1)!important;transition:none!important}

@media (prefers-reduced-motion:reduce){
  .slide.is-active,.slide.is-leaving{animation:none!important}
  [data-a],.cascade i[data-w]{transition:opacity 220ms linear!important}
  [data-a]{transform:none!important;filter:none!important;clip-path:none!important}
  .stagefield.is-live *{animation:none!important}
  .shine{animation:none;color:#fff;-webkit-text-fill-color:#fff}
}
