/* ==========================================================================
   Mediator Town — animations.css
   Pure CSS keyframes and motion utilities.
   On-load entrance animations use staggered `animation-delay`. When
   assets/js/main.js is present it adds a `.js` class to <html> and swaps
   these from "animate on load" to "animate on scroll into view" by toggling
   `.in-view` — see the `.js …` overrides below each base rule. Without JS
   the base (on-load) rules still apply, so nothing breaks if JS fails to load.
   Hover / focus interactions are defined alongside their components in
   style.css and use transition rather than keyframes.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference){

  /* ---------- Entrance: fade + slide up ---------- */
  @keyframes fade-up{
    from{ opacity:0; transform:translateY(22px); }
    to{ opacity:1; transform:translateY(0); }
  }
  .reveal-up{
    animation: fade-up .8s var(--ease-out) both;
  }
  .reveal-up.d1{ animation-delay:.08s; }
  .reveal-up.d2{ animation-delay:.16s; }
  .reveal-up.d3{ animation-delay:.24s; }
  .reveal-up.d4{ animation-delay:.32s; }
  .reveal-up.d5{ animation-delay:.4s; }

  @keyframes fade-in{
    from{ opacity:0; }
    to{ opacity:1; }
  }
  .reveal-fade{ animation: fade-in 1s var(--ease-out) both; }

  @keyframes scale-in{
    from{ opacity:0; transform:scale(.92); }
    to{ opacity:1; transform:scale(1); }
  }
  .reveal-scale{ animation: scale-in .7s var(--ease-out) both; }

  .stagger-grid > *{ animation: fade-up .7s var(--ease-out) both; }
  .stagger-grid > *:nth-child(1){ animation-delay:.05s; }
  .stagger-grid > *:nth-child(2){ animation-delay:.13s; }
  .stagger-grid > *:nth-child(3){ animation-delay:.21s; }
  .stagger-grid > *:nth-child(4){ animation-delay:.29s; }
  .stagger-grid > *:nth-child(5){ animation-delay:.37s; }
  .stagger-grid > *:nth-child(6){ animation-delay:.45s; }

  /* ---------- Scroll-triggered variant (progressive enhancement via main.js) ---------- */
  .js .reveal-up, .js .reveal-fade, .js .reveal-scale{ opacity:0; animation:none; }
  .js .reveal-up.in-view{ animation: fade-up .8s var(--ease-out) both; }
  .js .reveal-up.d1.in-view{ animation-delay:.08s; }
  .js .reveal-up.d2.in-view{ animation-delay:.16s; }
  .js .reveal-up.d3.in-view{ animation-delay:.24s; }
  .js .reveal-up.d4.in-view{ animation-delay:.32s; }
  .js .reveal-up.d5.in-view{ animation-delay:.4s; }
  .js .reveal-fade.in-view{ animation: fade-in 1s var(--ease-out) both; }
  .js .reveal-scale.in-view{ animation: scale-in .7s var(--ease-out) both; }

  .js .stagger-grid > *{ opacity:0; animation:none; }
  .js .stagger-grid.in-view > *{ animation: fade-up .7s var(--ease-out) both; }
  .js .stagger-grid.in-view > *:nth-child(1){ animation-delay:.05s; }
  .js .stagger-grid.in-view > *:nth-child(2){ animation-delay:.13s; }
  .js .stagger-grid.in-view > *:nth-child(3){ animation-delay:.21s; }
  .js .stagger-grid.in-view > *:nth-child(4){ animation-delay:.29s; }
  .js .stagger-grid.in-view > *:nth-child(5){ animation-delay:.37s; }
  .js .stagger-grid.in-view > *:nth-child(6){ animation-delay:.45s; }

  /* ---------- Pulse dot (announcement bar / live status / chips) ---------- */
  @keyframes pulse-dot{
    0%,100%{ opacity:1; box-shadow:0 0 0 0 rgba(34,211,238,.5); }
    50%{ opacity:.7; box-shadow:0 0 0 5px rgba(34,211,238,0); }
  }
  .chip-dot{ animation: pulse-dot 2.2s var(--ease-in-out) infinite; }

  /* ---------- Marquee scroll ---------- */
  @keyframes marquee-scroll{
    from{ transform:translateX(0); }
    to{ transform:translateX(-50%); }
  }

  /* ---------- Score gauge fill (progressive enhancement via @property) ---------- */
  @property --gauge-val{
    syntax:'<number>';
    inherits:true;
    initial-value:0;
  }
  @keyframes gauge-fill{
    from{ --gauge-val:0; }
  }
  .gauge-ring{ animation: gauge-fill 1.6s var(--ease-out) .3s both; }

  /* ---------- Floating browser / glass mockup elements ---------- */
  @keyframes drift-y{
    0%,100%{ transform:translateY(0) rotate(var(--rot,0deg)); }
    50%{ transform:translateY(-10px) rotate(var(--rot,0deg)); }
  }
  .drift{ animation: drift-y 5s var(--ease-in-out) infinite; }

  @keyframes draw-bar{
    from{ width:0; }
  }
  .bar-fill{ animation: draw-bar 1.4s var(--ease-out) .4s both; }

  @keyframes ticker-blink{
    0%,100%{ opacity:1; }
    50%{ opacity:.35; }
  }
  .blink-caret{ animation: ticker-blink 1.1s steps(1) infinite; }

  /* ---------- Underline draw on heading accents ---------- */
  @keyframes underline-draw{
    from{ transform:scaleX(0); }
    to{ transform:scaleX(1); }
  }
  .underline-accent{
    position:relative;
    display:inline-block;
  }
  .underline-accent::after{
    content:'';
    position:absolute;
    left:0; right:0; bottom:.06em;
    height:.32em;
    background:var(--color-bg-green);
    z-index:-1;
    transform-origin:left;
    animation: underline-draw .7s .5s var(--ease-out) both;
  }

  /* ---------- Ambient glow pulse for glass panels / hero blobs ---------- */
  @keyframes glow-pulse{
    0%,100%{ opacity:.5; }
    50%{ opacity:.85; }
  }
  .hero::before{ animation: glow-pulse 9s var(--ease-in-out) infinite; }

  /* ---------- Gradient-text sheen (borrowed from a competitor teardown:
     a bright band sweeps across the hero's gradient headline every ~8s,
     the way light catches foil, instead of the gradient just sitting
     static). Rests at 0% (plain 2-tone look) most of the cycle. ---------- */
  @keyframes text-sheen{
    0%, 82%, 100%{ background-position:0% 50%; }
    93%{ background-position:100% 50%; }
  }
  .text-gradient{ animation: text-sheen 8s var(--ease-in-out) infinite; }

  /* ---------- Spin (used sparingly, e.g. loading/processing icon in process section) ---------- */
  @keyframes spin-slow{ to{ transform:rotate(360deg); } }
  .spin-slow{ animation: spin-slow 9s linear infinite; }

  /* ---------- Hero headline word reveal (motion.js splits the h1) ----------
     Each word sits in an overflow-hidden .word mask; the inner span rotates
     up from below it, so letters wipe into place rather than just fading.
     The staggered per-word animation-delay is set inline by motion.js. */
  .headline-split .word{
    display:inline-block;
    overflow:hidden;
    vertical-align:bottom;
    /* Descenders (g, y, p) would be clipped by the mask without a little
       extra room, so pad the bottom and pull it back with a margin. */
    padding-bottom:.12em;
    margin-bottom:-.12em;
  }
  .headline-split .word-inner{
    display:inline-block;
    transform:translateY(110%) rotateX(-40deg);
    opacity:0;
    animation: word-rise .85s var(--ease-out) both;
  }
  @keyframes word-rise{
    from{ transform:translateY(110%) rotateX(-40deg); opacity:0; }
    to{ transform:translateY(0) rotateX(0); opacity:1; }
  }

  /* ---------- Marquee reacts to scroll velocity (skew set by motion.js) ---------- */
  .marquee-track{ transition: transform .45s var(--ease-out); }

  /* ---------- Hero service rotator (hero-rotator.js) ----------
     The hero mock cycles through the services every 5-7s. Each element that
     changes gets one of these entrance animations picked at random, so the
     panel never re-enters the same way twice: a mix of slides from each
     edge, scale-ups, a flip and a blur.

     These live inside the reduced-motion guard on purpose. Under `reduce`
     the @keyframes below simply don't exist, so `animation: mt-in-…` resolves
     to nothing and the element just appears. hero-rotator.js separately
     stops the timer entirely under `reduce`, so there's no auto-updating
     content either — it holds on the first service. */
  @keyframes mt-in-top{
    from{ opacity:0; transform:translateY(-26px); }
    to{ opacity:1; transform:translateY(0); }
  }
  @keyframes mt-in-bottom{
    from{ opacity:0; transform:translateY(26px); }
    to{ opacity:1; transform:translateY(0); }
  }
  @keyframes mt-in-left{
    from{ opacity:0; transform:translateX(-30px); }
    to{ opacity:1; transform:translateX(0); }
  }
  @keyframes mt-in-right{
    from{ opacity:0; transform:translateX(30px); }
    to{ opacity:1; transform:translateX(0); }
  }
  @keyframes mt-in-scale{
    from{ opacity:0; transform:scale(.72); }
    to{ opacity:1; transform:scale(1); }
  }
  @keyframes mt-in-scale-rot{
    from{ opacity:0; transform:scale(.8) rotate(-4deg); }
    to{ opacity:1; transform:scale(1) rotate(0); }
  }
  @keyframes mt-in-flip{
    from{ opacity:0; transform:perspective(600px) rotateX(-65deg); }
    to{ opacity:1; transform:perspective(600px) rotateX(0); }
  }
  @keyframes mt-in-blur{
    from{ opacity:0; filter:blur(9px); transform:scale(1.04); }
    to{ opacity:1; filter:blur(0); transform:scale(1); }
  }

  .mt-in-top      { animation: mt-in-top       .4s  var(--ease-out) both; }
  .mt-in-bottom   { animation: mt-in-bottom    .4s  var(--ease-out) both; }
  .mt-in-left     { animation: mt-in-left      .4s  var(--ease-out) both; }
  .mt-in-right    { animation: mt-in-right     .4s  var(--ease-out) both; }
  .mt-in-scale    { animation: mt-in-scale     .4s  var(--ease-out) both; }
  .mt-in-scale-rot{ animation: mt-in-scale-rot .4s  var(--ease-out) both; }
  .mt-in-flip     { animation: mt-in-flip      .44s var(--ease-out) both; }
  .mt-in-blur     { animation: mt-in-blur      .4s  var(--ease-out) both; }

  /* Outgoing content just fades, so the swap reads as one motion rather
     than two competing ones. */
  @keyframes mt-out{
    from{ opacity:1; }
    to{ opacity:0; }
  }
  .mt-out{ animation: mt-out .16s var(--ease-in-out) both; }

  /* ---------- Live motion inside each hero scene ----------
     Each scene keeps running while it's on screen so the mock reads as a
     live product rather than a static wireframe: the call waveform moves,
     the chatbot types, the map pin pings, data flows along the automation
     wires. All looping, all CSS, and all switched off under reduced
     motion because the whole block sits inside the guard. */

  /* Call waveform: bars breathe on a stagger, like live audio */
  @keyframes ms-wave-pulse{
    0%, 100%{ transform:scaleY(.3); }
    50%{ transform:scaleY(1); }
  }
  .ms-wave i{ transform-origin:center; animation: ms-wave-pulse 1s var(--ease-in-out) infinite; }
  .ms-wave i:nth-child(1){ animation-delay:-.90s; }
  .ms-wave i:nth-child(2){ animation-delay:-.15s; }
  .ms-wave i:nth-child(3){ animation-delay:-.55s; }
  .ms-wave i:nth-child(4){ animation-delay:-.30s; }
  .ms-wave i:nth-child(5){ animation-delay:-.75s; }
  .ms-wave i:nth-child(6){ animation-delay:-.05s; }
  .ms-wave i:nth-child(7){ animation-delay:-.65s; }
  .ms-wave i:nth-child(8){ animation-delay:-.40s; }
  .ms-wave i:nth-child(9){ animation-delay:-.85s; }
  .ms-wave i:nth-child(10){ animation-delay:-.20s; }

  /* Chatbot: a three-dot typing indicator */
  @keyframes ms-typing{
    0%, 65%, 100%{ opacity:.25; transform:translateY(0); }
    30%{ opacity:1; transform:translateY(-3px); }
  }
  .ms-typing i{ animation: ms-typing 1.3s var(--ease-in-out) infinite; }
  .ms-typing i:nth-child(2){ animation-delay:.18s; }
  .ms-typing i:nth-child(3){ animation-delay:.36s; }

  /* Google Business Profile: the map pin pings like a live location */
  @keyframes ms-pin-ping{
    0%{ box-shadow:0 0 0 0 rgba(155,107,243,.5); }
    70%{ box-shadow:0 0 0 13px rgba(155,107,243,0); }
    100%{ box-shadow:0 0 0 0 rgba(155,107,243,0); }
  }
  .ms-pin{ animation: ms-pin-ping 1.5s var(--ease-out) infinite; }

  /* Automation: a packet of data runs along each wire between the nodes.
     Animates `left`, not translateX: a percentage translate resolves
     against the dot's own 5px width, so it would barely move. A
     percentage `left` resolves against the wire it sits in, which is
     what we actually want it to traverse. */
  @keyframes ms-flow-dot{
    from{ left:-6px; opacity:0; }
    15%{ opacity:1; }
    85%{ opacity:1; }
    to{ left:100%; opacity:0; }
  }
  .ms-link::after{
    content:'';
    position:absolute; top:50%; left:0;
    width:5px; height:5px; margin-top:-2.5px;
    border-radius:50%;
    background:var(--svc-l);
    box-shadow:0 0 6px var(--svc-l);
    animation: ms-flow-dot 1.3s linear infinite;
  }
  .ms-link:nth-of-type(2)::after{ animation-delay:.6s; }
  .ms-link:nth-of-type(3)::after{ animation-delay:1.2s; }

  /* Chatbot: the reply types itself out, line by line, on a loop.
     The dots alone were far too small to read at this size, so the lines
     themselves grow to their target width, which is visible across the
     whole panel. --w carries each line's final width. */
  @keyframes ms-type-line{
    0%{ width:0; opacity:.45; }
    38%{ width:var(--w); opacity:1; }
    86%{ width:var(--w); opacity:1; }
    100%{ width:var(--w); opacity:0; }
  }
  .ms-chatlines i.type{ width:0; animation: ms-type-line 2s var(--ease-out) infinite; }
  .ms-chatlines i.type.d2{ animation-delay:.5s; }

  /* Chat widget: floats gently, with an unread badge that keeps pinging,
     so the first scene carries as much life as the cart and map ones. */
  @keyframes ms-widget-float{
    0%, 100%{ transform:translateY(0); }
    50%{ transform:translateY(-4px); }
  }
  .ms-widget{ animation: ms-widget-float 1.9s var(--ease-in-out) infinite; }

  @keyframes ms-badge-ping{
    0%, 100%{ transform:scale(1); box-shadow:0 0 0 0 rgba(198,58,30,.65); }
    50%{ transform:scale(1.18); box-shadow:0 0 0 7px rgba(198,58,30,0); }
  }
  .ms-badge{ animation: ms-badge-ping 1.3s var(--ease-out) infinite; }

  /* The page content behind the widget fills in line by line, so the
     whole scene reads as a site that is live rather than a still frame. */
  @keyframes ms-fill-line{
    0%{ width:0; opacity:.3; }
    30%{ width:var(--w); opacity:1; }
    88%{ width:var(--w); opacity:1; }
    100%{ width:var(--w); opacity:0; }
  }
  .ms-bar.fill{ width:0; animation: ms-fill-line 2.2s var(--ease-out) infinite; }
  .ms-bar.fill.f2{ animation-delay:.35s; }
  .ms-bar.fill.f3{ animation-delay:.7s; }

  /* Website build: a bright highlight sweeps the skeleton, and the three
     columns build in one after another, like a page assembling.
     background-color is set separately from background-image here so the
     sweeping gradient reads against it instead of washing out. */
  @keyframes ms-shimmer{
    from{ background-position:-75% 0; }
    to{ background-position:175% 0; }
  }
  .ms-bar.shimmer, .ms-col.shimmer{
    background-color:rgba(255,255,255,.07);
    background-image:linear-gradient(100deg,
      rgba(255,255,255,0) 0%,
      rgba(255,255,255,.42) 50%,
      rgba(255,255,255,0) 100%);
    background-size:65% 100%;
    background-repeat:no-repeat;
    animation: ms-shimmer 1.4s linear infinite;
  }
  /* Big amplitude and a short loop on purpose. Each scene is only on
     screen for about 3s now, so a long cycle with a hold in the middle
     just reads as a frozen frame — an earlier 3s version of this sat at
     full scale for practically the whole time it was visible. */
  @keyframes ms-build{
    0%{ opacity:.1; transform:scaleY(.12); }
    30%, 68%{ opacity:1; transform:scaleY(1); }
    100%{ opacity:.1; transform:scaleY(.12); }
  }
  .ms-col.build{ transform-origin:bottom center; animation: ms-build 1.7s var(--ease-out) infinite; }
  .ms-col.build:nth-child(2){ animation-delay:.18s; }
  .ms-col.build:nth-child(3){ animation-delay:.36s; }

  /* Build progress bar, and the palette swatches popping in turn */
  @keyframes ms-progress-fill{
    0%{ width:0; }
    75%, 100%{ width:100%; }
  }
  .ms-progress i{ animation: ms-progress-fill 1.8s var(--ease-out) infinite; }

  @keyframes ms-swatch-pop{
    0%, 100%{ transform:scale(.8); opacity:.45; }
    50%{ transform:scale(1); opacity:1; }
  }
  .ai-mock-swatches.pulse span{ animation: ms-swatch-pop 1.5s var(--ease-in-out) infinite; }
  .ai-mock-swatches.pulse span:nth-child(2){ animation-delay:.18s; }
  .ai-mock-swatches.pulse span:nth-child(3){ animation-delay:.36s; }

  /* Ecommerce: the checkout button pulses, the way a live CTA would */
  @keyframes ms-cta-pulse{
    0%, 100%{ box-shadow:0 0 0 0 rgba(155,107,243,.45); }
    60%{ box-shadow:0 0 0 9px rgba(155,107,243,0); }
  }
  .ms-cta{ animation: ms-cta-pulse 1.5s var(--ease-out) infinite; }

  /* SEO: the rank badge pulses, like a position that keeps holding #1 */
  @keyframes ms-rank-pop{
    0%, 100%{ transform:scale(1); box-shadow:0 0 0 0 rgba(52,211,153,.5); }
    50%{ transform:scale(1.12); box-shadow:0 0 0 8px rgba(52,211,153,0); }
  }
  .ms-chip{ display:inline-block; animation: ms-rank-pop 1.5s var(--ease-out) infinite; }

  /* Booking: the selected slot and day glow as if just taken */
  @keyframes ms-slot-glow{
    0%, 100%{ opacity:1; }
    50%{ opacity:.5; }
  }
  .ms-cal i.on, .ms-slot.on{ animation: ms-slot-glow 1.4s var(--ease-in-out) infinite; }

  /* llms.txt: a caret blinks at the end of the file */
  .ms-caret{ animation: ticker-blink 1.1s steps(1) infinite; }

} /* end prefers-reduced-motion guard */

/* The rotating bubbles change length between services. Reserving the tallest
   height would leave a gap on the short ones, so instead the panel animates
   its own height, which keeps the floating cards from jumping around it.
   Outside the motion guard: this is a layout smoothing transition, not
   decorative movement, and it stops the surrounding cards from teleporting. */
.ai-mock-bubbles{ transition: min-height .3s var(--ease-out); }

/* ---------- Scroll progress rail ----------
   Kept outside the reduced-motion guard on purpose: it communicates
   position rather than adding decorative movement, which is useful on the
   long case-study pages. motion.js drives scaleX. */
.scroll-progress{
  position:fixed;
  top:0; left:0;
  height:3px;
  width:100%;
  transform:scaleX(0);
  transform-origin:0 50%;
  background:linear-gradient(90deg, var(--color-primary), var(--color-green), var(--color-coral));
  z-index:900;
  pointer-events:none;
}

/* ---------- Hover-only micro interactions (kept outside the guard:
   these are short, purposeful, and respond to user action rather than
   playing automatically, so they remain even when motion is reduced,
   per WCAG guidance to keep necessary state-change feedback) ---------- */
.portfolio-card{ transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out); }
.portfolio-card:hover{ transform:translateY(-8px); box-shadow:var(--shadow-lg); }
.portfolio-card .portfolio-media img{ transition: transform .6s var(--ease-out); }
.portfolio-card:hover .portfolio-media img{ transform:scale(1.05); }

.tool-chip{ transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast), background var(--dur-fast); }
.tool-chip:hover{ transform:translateY(-4px); border-color:var(--color-primary); background:var(--color-bg-blue); }

.process-step{ transition: transform var(--dur-base) var(--ease-out); }
.process-step:hover .process-num{ transform:scale(1.08); background:var(--color-primary); color:#fff; }
.process-num{ transition: transform var(--dur-base) var(--ease-out), background var(--dur-fast), color var(--dur-fast); }
