/* store-anim.jsx — motion & interaction primitives for the store.
   Reveal / CountUp (scroll), CursorGlow, useTilt, useParallax, useCountdown.
   One shared pointer source (FxPointer) drives glow + parallax.
   Everything honors prefers-reduced-motion. Exported to window. */

const _fxReduce = () =>
  typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;

/* ---- shared pointer ---- */
const FxPointer = (function () {
  let s = { x: 0, y: 0, nx: 0, ny: 0 };
  const subs = new Set();
  if (typeof window !== "undefined") {
    window.addEventListener("mousemove", (e) => {
      s = {
        x: e.clientX,
        y: e.clientY,
        nx: (e.clientX / window.innerWidth) * 2 - 1,
        ny: (e.clientY / window.innerHeight) * 2 - 1,
      };
      subs.forEach((fn) => fn(s));
    }, { passive: true });
  }
  return { subscribe(fn) { subs.add(fn); return () => subs.delete(fn); }, get() { return s; } };
})();

/* ---- Reveal (scroll fade + rise) ---- */
function Reveal({ children, y = 26, delay = 0, dur = 720, className = "", style = {}, as: Tag = "div" }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (_fxReduce()) { setShown(true); return; }
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(el); } });
    }, { threshold: 0.14, rootMargin: "0px 0px -6% 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const ease = "cubic-bezier(.21,.68,.24,1)";
  return (
    <Tag ref={ref} className={className} style={{
      ...style,
      opacity: shown ? 1 : 0,
      transform: shown ? "translateY(0)" : `translateY(${y}px)`,
      transition: `opacity ${dur}ms ${ease} ${delay}ms, transform ${dur}ms ${ease} ${delay}ms`,
      willChange: "opacity, transform",
    }}>{children}</Tag>
  );
}

/* ---- CountUp ---- */
function CountUp({ to, from = 0, dur = 1500, prefix = "", suffix = "", decimals = 0, className = "", style = {} }) {
  const ref = React.useRef(null);
  const started = React.useRef(false);
  const [val, setVal] = React.useState(from);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (_fxReduce()) { setVal(to); return; }
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => {
        if (e.isIntersecting && !started.current) {
          started.current = true;
          const t0 = performance.now();
          const tick = (now) => {
            const p = Math.min(1, (now - t0) / dur);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(from + (to - from) * eased);
            if (p < 1) requestAnimationFrame(tick);
          };
          requestAnimationFrame(tick);
          io.unobserve(el);
        }
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return <span ref={ref} className={className} style={style}>{prefix}{val.toFixed(decimals)}{suffix}</span>;
}

/* ---- CursorGlow — canvas cursor system: breathing glow + trail + sparkles.
   One rAF loop, transform/opacity only, capped particle pool. ---- */
function CursorGlow() {
  const cvsRef = React.useRef(null);
  React.useEffect(() => {
    if (_fxReduce()) return;
    const cvs = cvsRef.current;
    const ctx = cvs.getContext("2d");
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0;
    const resize = () => {
      w = window.innerWidth; h = window.innerHeight;
      cvs.width = w * dpr; cvs.height = h * dpr;
      cvs.style.width = w + "px"; cvs.style.height = h + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    window.addEventListener("resize", resize);

    let mx = -600, my = -600, ex = -600, ey = -600, lx = -600, ly = -600, present = 0;
    const trail = [], sparks = [];
    const rand = (a, b) => a + Math.random() * (b - a);
    const unsub = FxPointer.subscribe(({ x, y }) => { mx = x; my = y; });

    let raf;
    const loop = () => {
      ex += (mx - ex) * 0.18; ey += (my - ey) * 0.18;
      const speed = Math.hypot(mx - lx, my - ly);
      lx = mx; ly = my;
      present += ((mx < 0 ? 0 : 1) - present) * 0.07;

      trail.push({ x: ex, y: ey }); if (trail.length > 20) trail.shift();

      if (speed > 5 && mx > 0) {
        const n = Math.min(3, 1 + Math.floor(speed / 12));
        for (let i = 0; i < n; i++) sparks.push({ x: mx + rand(-7, 7), y: my + rand(-7, 7), vx: rand(-0.7, 0.7), vy: rand(-1, -0.2), life: 0, max: rand(46, 84), size: rand(1.1, 2.6), hue: rand(250, 320) });
      }
      if (sparks.length > 100) sparks.splice(0, sparks.length - 100);

      ctx.clearRect(0, 0, w, h);
      const t = performance.now() / 1000;
      const breathe = 1 + Math.sin(t * 1.7) * 0.07;

      // large soft glow
      const R = 210 * breathe;
      let g = ctx.createRadialGradient(ex, ey, 0, ex, ey, R);
      g.addColorStop(0, `rgba(122,86,255,${0.17 * present})`);
      g.addColorStop(0.4, `rgba(150,112,255,${0.08 * present})`);
      g.addColorStop(1, "rgba(150,112,255,0)");
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ex, ey, R, 0, 7); ctx.fill();

      // inner lavender core
      g = ctx.createRadialGradient(ex, ey, 0, ex, ey, 95);
      g.addColorStop(0, `rgba(196,158,255,${0.16 * present})`);
      g.addColorStop(1, "rgba(196,158,255,0)");
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ex, ey, 95, 0, 7); ctx.fill();

      // trail
      for (let i = 0; i < trail.length; i++) {
        const p = trail[i], a = i / trail.length, rr = 30 * a;
        g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, rr);
        g.addColorStop(0, `rgba(140,100,255,${0.06 * a * present})`);
        g.addColorStop(1, "rgba(140,100,255,0)");
        ctx.fillStyle = g; ctx.beginPath(); ctx.arc(p.x, p.y, rr, 0, 7); ctx.fill();
      }

      // bright core dot at real cursor
      g = ctx.createRadialGradient(mx, my, 0, mx, my, 15);
      g.addColorStop(0, `rgba(83,58,253,${0.5 * present})`);
      g.addColorStop(1, "rgba(83,58,253,0)");
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(mx, my, 15, 0, 7); ctx.fill();

      // sparkles
      for (let i = sparks.length - 1; i >= 0; i--) {
        const s = sparks[i]; s.life++; s.x += s.vx; s.y += s.vy; s.vy += 0.012;
        const lt = s.life / s.max;
        if (lt >= 1) { sparks.splice(i, 1); continue; }
        const a = Math.sin(lt * Math.PI) * present;
        g = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.size * 3.4);
        g.addColorStop(0, `hsla(${s.hue},100%,82%,${0.85 * a})`);
        g.addColorStop(1, `hsla(${s.hue},100%,82%,0)`);
        ctx.fillStyle = g; ctx.beginPath(); ctx.arc(s.x, s.y, s.size * 3.4, 0, 7); ctx.fill();
        ctx.fillStyle = `hsla(${s.hue},100%,94%,${a})`;
        ctx.beginPath(); ctx.arc(s.x, s.y, s.size * 0.7, 0, 7); ctx.fill();
      }

      raf = requestAnimationFrame(loop);
    };
    loop();
    return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); unsub(); };
  }, []);
  return <canvas ref={cvsRef} aria-hidden="true" style={{ position: "fixed", inset: 0, pointerEvents: "none", zIndex: 60 }} />;
}

/* ---- Magnetic — element eases toward the cursor while hovered ---- */
function Magnetic({ children, strength = 0.35, className = "", style = {} }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || _fxReduce()) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const dx = e.clientX - (r.left + r.width / 2);
      const dy = e.clientY - (r.top + r.height / 2);
      el.style.transform = `translate(${(dx * strength).toFixed(2)}px, ${(dy * strength).toFixed(2)}px)`;
    };
    const onLeave = () => { el.style.transform = "translate(0,0)"; };
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => { el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", onLeave); };
  }, [strength]);
  return <span ref={ref} className={className} style={{ display: "inline-flex", transition: "transform 300ms cubic-bezier(.21,.68,.24,1)", ...style }}>{children}</span>;
}

/* ---- useSpotlight — sets --sx/--sy/--sop on an element for a cursor spotlight ---- */
function useSpotlight() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || _fxReduce()) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      el.style.setProperty("--sx", (e.clientX - r.left) + "px");
      el.style.setProperty("--sy", (e.clientY - r.top) + "px");
      el.style.setProperty("--sop", "1");
    };
    const onLeave = () => el.style.setProperty("--sop", "0");
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => { el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", onLeave); };
  }, []);
  return ref;
}

/* ---- useTilt — 3D tilt toward pointer over an element ---- */
function useTilt(max = 8, scale = 1) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || _fxReduce()) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const px = (e.clientX - r.left) / r.width - 0.5;
      const py = (e.clientY - r.top) / r.height - 0.5;
      el.style.transform = `perspective(900px) rotateX(${(-py * max).toFixed(2)}deg) rotateY(${(px * max).toFixed(2)}deg) scale(${scale})`;
    };
    const onLeave = () => { el.style.transform = "perspective(900px) rotateX(0) rotateY(0) scale(1)"; };
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => { el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", onLeave); };
  }, [max, scale]);
  return ref;
}

/* ---- useParallax — eased translate from pointer position ---- */
function useParallax(depth = 18) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (_fxReduce()) return;
    let raf, tx = 0, ty = 0, cx = 0, cy = 0;
    const unsub = FxPointer.subscribe(({ nx, ny }) => { tx = -nx * depth; ty = -ny * depth; });
    const loop = () => {
      cx += (tx - cx) * 0.07; cy += (ty - cy) * 0.07;
      if (ref.current) ref.current.style.transform = `translate3d(${cx.toFixed(2)}px, ${cy.toFixed(2)}px, 0)`;
      raf = requestAnimationFrame(loop);
    };
    loop();
    return () => { cancelAnimationFrame(raf); unsub(); };
  }, [depth]);
  return ref;
}

/* ---- useCountdown — ms remaining split into d/h/m/s ---- */
function useCountdown(targetMs) {
  const [now, setNow] = React.useState(Date.now());
  React.useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);
  let diff = Math.max(0, targetMs - now);
  const d = Math.floor(diff / 864e5); diff -= d * 864e5;
  const h = Math.floor(diff / 36e5); diff -= h * 36e5;
  const m = Math.floor(diff / 6e4); diff -= m * 6e4;
  const s = Math.floor(diff / 1e3);
  const pad = (n) => String(n).padStart(2, "0");
  return { d: pad(d), h: pad(h), m: pad(m), s: pad(s) };
}

Object.assign(window, { FxPointer, Reveal, CountUp, CursorGlow, Magnetic, useSpotlight, useTilt, useParallax, useCountdown, _fxReduce });
/* cache-bust r2 */
