// scroll-fx.jsx — scroll animation primitives
// FadeUp, Stagger, Parallax, Reveal, Marquee, etc.
// Pure intersection observer + CSS transforms, no deps.

const { useEffect, useRef, useState } = React;

function useInView(threshold = 0.15, once = true) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const obs = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setInView(true);
        if (once) obs.disconnect();
      } else if (!once) {
        setInView(false);
      }
    }, { threshold });
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [threshold, once]);
  return [ref, inView];
}

function FadeUp({ children, delay = 0, distance = 28, duration = 700, as = "div", style = {}, ...rest }) {
  const [ref, inView] = useInView(0.12);
  const Tag = as;
  return (
    <Tag ref={ref} style={{
      ...style,
      opacity: inView ? 1 : 0,
      transform: inView ? "translateY(0)" : `translateY(${distance}px)`,
      transition: `opacity ${duration}ms cubic-bezier(.2,.7,.2,1) ${delay}ms, transform ${duration}ms cubic-bezier(.2,.7,.2,1) ${delay}ms`,
      willChange: "opacity, transform",
    }} {...rest}>{children}</Tag>
  );
}

function Stagger({ children, base = 0, step = 80, ...rest }) {
  const arr = React.Children.toArray(children);
  return arr.map((c, i) => (
    <FadeUp key={i} delay={base + i * step} {...rest}>{c}</FadeUp>
  ));
}

function Reveal({ children, color = "currentColor" }) {
  // Mask reveal — text slides up behind a clip
  const [ref, inView] = useInView(0.2);
  return (
    <span ref={ref} style={{ display: "inline-block", overflow: "hidden", verticalAlign: "bottom" }}>
      <span style={{
        display: "inline-block",
        transform: inView ? "translateY(0)" : "translateY(110%)",
        transition: "transform 900ms cubic-bezier(.2,.8,.2,1)",
        willChange: "transform",
      }}>{children}</span>
    </span>
  );
}

function Counter({ to, prefix = "", suffix = "", duration = 1400, style = {} }) {
  const [ref, inView] = useInView(0.4);
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!inView) return;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      setVal(Math.round(to * eased));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, to, duration]);
  return <span ref={ref} style={style}>{prefix}{val.toLocaleString()}{suffix}</span>;
}

function Marquee({ children, speed = 50, style = {} }) {
  return (
    <div style={{ overflow: "hidden", whiteSpace: "nowrap", ...style }}>
      <div style={{
        display: "inline-block", animation: `ck-marquee ${speed}s linear infinite`,
        paddingRight: 40,
      }}>{children}{children}</div>
      <style>{`@keyframes ck-marquee { 0%{transform:translateX(0)} 100%{transform:translateX(-50%)} }`}</style>
    </div>
  );
}

function Parallax({ children, speed = 0.3, style = {} }) {
  const ref = useRef(null);
  useEffect(() => {
    let raf;
    const tick = () => {
      if (ref.current) {
        const r = ref.current.getBoundingClientRect();
        const center = window.innerHeight / 2;
        const offset = (r.top + r.height / 2 - center) * -speed;
        ref.current.style.transform = `translateY(${offset}px)`;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [speed]);
  return <div ref={ref} style={{ ...style, willChange: "transform" }}>{children}</div>;
}

function ScrollProgress({ color = "currentColor", height = 2 }) {
  const [pct, setPct] = useState(0);
  useEffect(() => {
    const fn = () => {
      const h = document.documentElement;
      const p = h.scrollTop / Math.max(1, h.scrollHeight - h.clientHeight);
      setPct(p);
    };
    fn();
    window.addEventListener("scroll", fn, { passive: true });
    return () => window.removeEventListener("scroll", fn);
  }, []);
  return (
    <div style={{ position: "fixed", top: 0, left: 0, right: 0, height, zIndex: 100, pointerEvents: "none" }}>
      <div style={{ height: "100%", width: `${pct * 100}%`, background: color, transition: "width 80ms linear" }} />
    </div>
  );
}

window.useInView = useInView;
window.FadeUp = FadeUp;
window.Stagger = Stagger;
window.Reveal = Reveal;
window.Counter = Counter;
window.Marquee = Marquee;
window.Parallax = Parallax;
window.ScrollProgress = ScrollProgress;
