// MotionFX, global motion + interactive effects
// Mounts once and decorates the page. No JSX dependencies.

(function () {
  // ---------- Scroll progress ----------
  function mountScrollProgress() {
    const el = document.createElement("div");
    el.className = "scroll-progress";
    document.body.appendChild(el);
    const update = () => {
      const h = document.documentElement;
      const max = h.scrollHeight - h.clientHeight;
      const p = max > 0 ? h.scrollTop / max : 0;
      el.style.setProperty("--sp", p.toFixed(4));
    };
    update();
    window.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
  }

  // ---------- Cursor spotlight ----------
  function mountCursorSpot() {
    const el = document.createElement("div");
    el.className = "cursor-spot";
    document.body.appendChild(el);
    let raf = 0, mx = window.innerWidth / 2, my = window.innerHeight / 2;
    let cx = mx, cy = my;
    const tick = () => {
      cx += (mx - cx) * 0.18;
      cy += (my - cy) * 0.18;
      el.style.setProperty("--mx", cx + "px");
      el.style.setProperty("--my", cy + "px");
      raf = requestAnimationFrame(tick);
    };
    window.addEventListener("pointermove", (e) => {
      mx = e.clientX; my = e.clientY;
      el.classList.add("on");
      if (!raf) raf = requestAnimationFrame(tick);
    });
    window.addEventListener("pointerleave", () => el.classList.remove("on"));
  }

  // ---------- Grain ----------
  function mountGrain() {
    const el = document.createElement("div");
    el.className = "grain";
    document.body.appendChild(el);
  }

  // ---------- Magnetic CTAs ----------
  function mountMagnetic() {
    const sel = ".ll-btn--hero, .ll-btn--cta, .ll-btn--xl, .ll-nav__cta .ll-btn";
    document.querySelectorAll(sel).forEach((btn) => {
      btn.classList.add("magnetic");
      let raf = 0, tx = 0, ty = 0, x = 0, y = 0;
      const tick = () => {
        x += (tx - x) * 0.25;
        y += (ty - y) * 0.25;
        btn.style.transform = `translate(${x.toFixed(2)}px, ${y.toFixed(2)}px)`;
        if (Math.abs(x - tx) > 0.1 || Math.abs(y - ty) > 0.1) {
          raf = requestAnimationFrame(tick);
        } else { raf = 0; }
      };
      btn.addEventListener("pointermove", (e) => {
        const r = btn.getBoundingClientRect();
        const px = e.clientX - (r.left + r.width / 2);
        const py = e.clientY - (r.top + r.height / 2);
        tx = px * 0.25;
        ty = py * 0.35;
        if (!raf) raf = requestAnimationFrame(tick);
      });
      btn.addEventListener("pointerleave", () => {
        tx = 0; ty = 0;
        if (!raf) raf = requestAnimationFrame(tick);
      });
      // Click ripple + sparkles
      btn.addEventListener("pointerdown", (e) => {
        const r = btn.getBoundingClientRect();
        const ripple = document.createElement("span");
        ripple.className = "ripple";
        const size = Math.max(r.width, r.height);
        ripple.style.width = ripple.style.height = size + "px";
        ripple.style.left = (e.clientX - r.left - size / 2) + "px";
        ripple.style.top = (e.clientY - r.top - size / 2) + "px";
        btn.appendChild(ripple);
        setTimeout(() => ripple.remove(), 600);
      });
    });
  }

  // ---------- Tilt cards ----------
  function mountTilt() {
    const sel = ".feature-card, .card-grad, .process__card, .outcomes__stat, .feat, .flagship__card";
    document.querySelectorAll(sel).forEach((card) => {
      card.classList.add("tilt");
      let raf = 0;
      const onMove = (e) => {
        const r = card.getBoundingClientRect();
        const px = (e.clientX - r.left) / r.width;  // 0..1
        const py = (e.clientY - r.top) / r.height;  // 0..1
        const rx = (0.5 - py) * 6;  // tilt up/down
        const ry = (px - 0.5) * 8;  // tilt left/right
        card.style.setProperty("--rx", rx.toFixed(2) + "deg");
        card.style.setProperty("--ry", ry.toFixed(2) + "deg");
        card.style.setProperty("--gx", (px * 100).toFixed(1) + "%");
        card.style.setProperty("--gy", (py * 100).toFixed(1) + "%");
      };
      card.addEventListener("pointermove", onMove);
      card.addEventListener("pointerenter", () => card.classList.add("lit"));
      card.addEventListener("pointerleave", () => {
        card.classList.remove("lit");
        card.style.setProperty("--rx", "0deg");
        card.style.setProperty("--ry", "0deg");
      });
    });
  }

  // ---------- Hero spotlight ----------
  function mountHeroSpot() {
    const hero = document.querySelector(".hero");
    if (!hero) return;
    const spot = document.createElement("div");
    spot.className = "hero__spot";
    hero.appendChild(spot);
    hero.addEventListener("pointermove", (e) => {
      const r = hero.getBoundingClientRect();
      const x = ((e.clientX - r.left) / r.width) * 100;
      const y = ((e.clientY - r.top) / r.height) * 100;
      spot.style.setProperty("--hx", x.toFixed(1) + "%");
      spot.style.setProperty("--hy", y.toFixed(1) + "%");
    });
  }

  // ---------- Hero particles ----------
  function mountHeroParticles() {
    const hero = document.querySelector(".hero");
    if (!hero) return;
    const wrap = document.createElement("div");
    wrap.className = "particles";
    const N = 14;
    for (let i = 0; i < N; i++) {
      const p = document.createElement("span");
      p.className = "particle";
      const left = Math.random() * 100;
      const dur = 12 + Math.random() * 18;
      const delay = -Math.random() * dur;
      const dx = (Math.random() * 80 - 40).toFixed(0) + "px";
      const top = 80 + Math.random() * 30;
      p.style.left = left.toFixed(1) + "%";
      p.style.top = top.toFixed(1) + "%";
      p.style.animationDuration = dur.toFixed(1) + "s";
      p.style.animationDelay = delay.toFixed(1) + "s";
      p.style.setProperty("--dx", dx);
      wrap.appendChild(p);
    }
    hero.appendChild(wrap);
  }

  // ---------- Kinetic hero title (per-character rise) ----------
  function mountKineticTitle() {
    const h1 = document.querySelector(".hero__title");
    if (!h1 || h1.dataset.kinetic) return;
    h1.dataset.kinetic = "1";

    // Wrap each text node's words/chars; preserve nested span for gradient
    const wrap = (node) => {
      if (node.nodeType === Node.TEXT_NODE) {
        const text = node.textContent;
        const frag = document.createDocumentFragment();
        const words = text.split(/(\s+)/);
        words.forEach((w) => {
          if (/^\s+$/.test(w) || w === "") {
            frag.appendChild(document.createTextNode(w));
            return;
          }
          const wEl = document.createElement("span");
          wEl.className = "word";
          [...w].forEach((c) => {
            const ch = document.createElement("span");
            ch.className = "ch";
            ch.textContent = c;
            wEl.appendChild(ch);
          });
          frag.appendChild(wEl);
        });
        node.parentNode.replaceChild(frag, node);
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        // Recurse, but keep classes. If gradient phrase, give it the new kinetic class.
        if (node.classList && node.classList.contains("gradient-text")) {
          node.classList.add("kinetic-grad");
        }
        [...node.childNodes].forEach(wrap);
      }
    };
    [...h1.childNodes].forEach(wrap);

    // Stagger animation delays
    const chars = h1.querySelectorAll(".ch");
    chars.forEach((c, i) => {
      c.style.animationDelay = (i * 14) + "ms";
    });
  }

  // ---------- Marquee ----------
  function mountMarquee() {
    const hero = document.querySelector(".hero");
    if (!hero) return;
    const items = [
      { t: "EDUCATION", a: false },
      { t: "HEALTHCARE", a: true },
      { t: "ENTERPRISE", a: false },
      { t: "AI TUTORING", a: false },
      { t: "ADAPTIVE LEARNING", a: true },
      { t: "ASSESSMENT DESIGN", a: false },
      { t: "CURRICULUM", a: false },
      { t: "ANALYTICS", a: true },
      { t: "MED-ED", a: false },
      { t: "ENGAGEMENT", a: false },
    ];
    const m = document.createElement("div");
    m.className = "marquee";
    const track = document.createElement("div");
    track.className = "marquee__track";
    const renderItems = (root) => {
      items.forEach((it) => {
        const span = document.createElement("span");
        span.className = "marquee__item" + (it.a ? " accent" : "");
        span.innerHTML = `<span class="dot"></span>${it.t}`;
        root.appendChild(span);
      });
    };
    renderItems(track);
    renderItems(track); // duplicate for seamless loop
    m.appendChild(track);
    hero.insertAdjacentElement("afterend", m);
  }

  // ---------- Scroll cue inside hero ----------
  function mountScrollCue() {
    const hero = document.querySelector(".hero");
    if (!hero || hero.querySelector(".scroll-cue")) return;
    const cue = document.createElement("div");
    cue.className = "scroll-cue";
    cue.innerHTML = `<span>scroll</span><span class="scroll-cue__line"></span>`;
    hero.appendChild(cue);
  }

  // ---------- Process, animated SVG connector ----------
  function mountProcessLine() {
    const grid = document.querySelector(".process__grid");
    if (!grid) return;
    const sec = grid.closest("section");
    if (!sec) return;
    sec.classList.add("process");
    // Compute card centers along the top
    const place = () => {
      const old = sec.querySelector(".process__line");
      if (old) old.remove();
      const cards = grid.querySelectorAll(".process__card");
      if (cards.length < 2) return;
      const gridRect = grid.getBoundingClientRect();
      const points = [...cards].map((c) => {
        const r = c.getBoundingClientRect();
        return {
          x: r.left + r.width / 2 - gridRect.left,
          y: r.top - gridRect.top + 36,
        };
      });
      const w = grid.clientWidth;
      const minY = Math.min(...points.map((p) => p.y));
      const maxY = Math.max(...points.map((p) => p.y));
      const h = Math.max(60, maxY - minY + 40);
      const svgNS = "http://www.w3.org/2000/svg";
      const svg = document.createElementNS(svgNS, "svg");
      svg.setAttribute("class", "process__line");
      svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
      svg.setAttribute("preserveAspectRatio", "none");
      svg.style.position = "absolute";
      svg.style.top = (minY - 20) + "px";
      svg.style.left = "0";
      svg.style.width = w + "px";
      svg.style.height = h + "px";

      const defs = document.createElementNS(svgNS, "defs");
      defs.innerHTML = `
        <linearGradient id="processGrad" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"  stop-color="hsl(340 82% 62%)"/>
          <stop offset="33%" stop-color="hsl(28 92% 60%)"/>
          <stop offset="66%" stop-color="hsl(175 70% 44%)"/>
          <stop offset="100%" stop-color="hsl(268 70% 62%)"/>
        </linearGradient>`;
      svg.appendChild(defs);

      // Build a smooth wavy path through points
      let d = "";
      points.forEach((p, i) => {
        const x = p.x;
        const y = p.y - (minY - 20);
        if (i === 0) d += `M ${x} ${y}`;
        else {
          const prev = points[i - 1];
          const px = prev.x;
          const py = prev.y - (minY - 20);
          const cx1 = px + (x - px) / 2;
          const cy1 = py - 30;
          const cx2 = px + (x - px) / 2;
          const cy2 = y + 30;
          d += ` C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x} ${y}`;
        }
      });
      const path = document.createElementNS(svgNS, "path");
      path.setAttribute("d", d);
      svg.appendChild(path);

      const pulse = document.createElementNS(svgNS, "circle");
      pulse.setAttribute("class", "process__pulse");
      pulse.setAttribute("r", "4");
      pulse.style.offsetPath = `path("${d}")`;
      pulse.style.offsetRotate = "0deg";
      svg.appendChild(pulse);

      sec.querySelector(".section__inner").style.position = "relative";
      grid.style.position = "relative";
      grid.insertBefore(svg, grid.firstChild);

      const io = new IntersectionObserver((es) => {
        es.forEach((e) => { if (e.isIntersecting) { svg.classList.add("go"); io.disconnect(); } });
      }, { threshold: 0.2 });
      io.observe(svg);
    };
    place();
    let resizeT;
    window.addEventListener("resize", () => {
      clearTimeout(resizeT);
      resizeT = setTimeout(place, 120);
    });
  }

  // ---------- Outcomes rings ----------
  function mountOutcomeRings() {
    const stats = document.querySelectorAll(".outcomes__stat");
    stats.forEach((stat, i) => {
      // Determine color from contained outcomes__v
      const v = stat.querySelector(".outcomes__v");
      const colors = ["pink", "blue", "teal", "gold"];
      const color = v ? (colors.find((c) => v.classList.contains("outcomes__v--" + c)) || "pink") : "pink";
      stat.dataset.color = color;
      const svgNS = "http://www.w3.org/2000/svg";
      const svg = document.createElementNS(svgNS, "svg");
      svg.setAttribute("class", "outcomes__ring");
      svg.setAttribute("viewBox", "0 0 36 36");
      svg.innerHTML = `
        <circle class="bg" cx="18" cy="18" r="15.9"/>
        <circle class="fg" cx="18" cy="18" r="15.9"/>`;
      stat.appendChild(svg);
    });
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => {
        if (e.isIntersecting) {
          e.target.dataset.fill = "1";
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.4 });
    stats.forEach((s) => io.observe(s));
  }

  // ---------- Smart hide nav on scroll-down ----------
  function mountSmartNav() {
    const nav = document.querySelector(".ll-nav");
    if (!nav) return;
    let lastY = window.scrollY;
    let ticking = false;
    const onScroll = () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        const y = window.scrollY;
        const dy = y - lastY;
        if (y > 200 && dy > 6) nav.classList.add("hidden");
        else if (dy < -6 || y < 100) nav.classList.remove("hidden");
        lastY = y;
        ticking = false;
      });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
  }

  // ---------- Wrap flagship logo with conic ring ----------
  function mountFlagshipRing() {
    const logo = document.querySelector(".flagship__logo");
    if (!logo || logo.parentElement.classList.contains("flagship__logo-wrap")) return;
    const wrap = document.createElement("div");
    wrap.className = "flagship__logo-wrap";
    logo.parentNode.insertBefore(wrap, logo);
    wrap.appendChild(logo);
  }

  // ---------- Reveal-on-scroll (fade-up) ----------
  function mountReveal() {
    const check = () => {
      const vh = window.innerHeight;
      document.querySelectorAll(".fade-up:not(.in)").forEach((el) => {
        const r = el.getBoundingClientRect();
        if (r.top < vh - 40) el.classList.add("in");
      });
    };
    let raf = 0;
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(() => { raf = 0; check(); }); };
    check();
    setTimeout(check, 80);
    setTimeout(check, 300);
    setInterval(check, 250);
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    document.addEventListener("scroll", onScroll, { passive: true, capture: true });
  }

  // ---------- Counter on case-card metrics ----------
  function mountCounters() {
    const animate = (el) => {
      const raw = el.textContent.trim();
      const m = raw.match(/^([+\-]?[\d.]+)(.*)$/);
      if (!m) return;
      const target = parseFloat(m[1]);
      const suffix = m[2];
      if (isNaN(target)) return;
      const isFloat = m[1].includes(".");
      const sign = m[1].startsWith("+") ? "+" : "";
      const dur = 1400;
      const start = performance.now();
      const ease = (t) => 1 - Math.pow(1 - t, 3);
      const tick = (now) => {
        const t = Math.min(1, (now - start) / dur);
        const v = target * ease(t);
        el.textContent = sign + (isFloat ? v.toFixed(1) : Math.round(v)) + suffix;
        if (t < 1) requestAnimationFrame(tick);
        else el.textContent = raw;
      };
      requestAnimationFrame(tick);
    };
    const check = () => {
      const vh = window.innerHeight;
      document.querySelectorAll(".case-card__metric").forEach((el) => {
        if (el.dataset.counted) return;
        const r = el.getBoundingClientRect();
        if (r.top < vh - 60) {
          el.dataset.counted = "1";
          animate(el);
        }
      });
    };
    let raf = 0;
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(() => { raf = 0; check(); }); };
    setTimeout(check, 100);
    setInterval(check, 300);
    window.addEventListener("scroll", onScroll, { passive: true });
    document.addEventListener("scroll", onScroll, { passive: true, capture: true });
  }

  // ---------- Boot ----------
  
  function mountProcessFlowArm(){
    const flow = document.querySelector(".process__flow");
    if(!flow) return;
    const arm = () => {
      const r = flow.getBoundingClientRect();
      if(r.top < window.innerHeight - 60 && r.bottom > 0){
        flow.classList.add("armed");
        window.removeEventListener("scroll", arm);
      }
    };
    arm();
    window.addEventListener("scroll", arm, { passive:true });
  }

  function boot() {
    if (document.body.dataset.motionBooted) return;
    document.body.dataset.motionBooted = "1";
    try { mountReveal(); } catch (e) {}
    try { mountCounters(); } catch (e) {}
    try { mountScrollProgress(); } catch (e) {}
    try { mountCursorSpot(); } catch (e) {}
    try { mountGrain(); } catch (e) {}
    try { mountHeroSpot(); } catch (e) {}
    try { mountHeroParticles(); } catch (e) {}
    try { mountKineticTitle(); } catch (e) {}
    // marquee removed: too SaaS-y for a boutique consulting brand
    try { mountScrollCue(); } catch (e) {}
    try { mountFlagshipRing(); } catch (e) {}
    try { mountSmartNav(); } catch (e) {}
    // After React mounts more children
    setTimeout(() => {
      try { mountTilt(); } catch (e) {}
      try { mountMagnetic(); } catch (e) {}
      try { mountProcessLine(); } catch (e) {}
      try { mountOutcomeRings(); } catch (e) {}
      try { mountProcessFlowArm(); } catch (e) {}
    }, 100);
    setTimeout(() => {
      try { mountTilt(); } catch (e) {}
      try { mountMagnetic(); } catch (e) {}
      try { mountProcessLine(); } catch (e) {}
    }, 600);
  }

  window.MotionFX = { boot };
})();
