// Scroll-reveal wrapper: fades + slides into place once in view.
// `from` controls the entry direction: "up" (default) | "down" | "left" | "right".
// `distance` is the travel in px. `delay` staggers grouped reveals.
function Reveal({ children, delay = 0, from = "up", distance = 36, style = {} }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(
      (entries) => { entries.forEach((e) => { if (e.isIntersecting) setShown(true); }); },
      { threshold: 0.15 }
    );
    obs.observe(el);
    return () => obs.disconnect();
  }, []);
  const offsets = {
    up: `translateY(${distance}px) scale(0.95)`,
    down: `translateY(-${distance}px) scale(0.95)`,
    left: `translateX(-${distance}px) scale(0.95)`,
    right: `translateX(${distance}px) scale(0.95)`,
  };
  const ease = "cubic-bezier(.16,1,.3,1)"; // cinematic easeOutExpo — strong, smooth settle
  return (
    <div
      ref={ref}
      style={{
        opacity: shown ? 1 : 0,
        transform: shown ? "translate(0, 0) scale(1)" : (offsets[from] || offsets.up),
        transition: `opacity 1000ms ${ease} ${delay}ms, transform 1000ms ${ease} ${delay}ms`,
        willChange: "opacity, transform",
        ...style,
      }}
    >
      {children}
    </div>
  );
}

// Tracks window scrollY, returns it for parallax math.
function useScrollY() {
  const [y, setY] = React.useState(typeof window !== "undefined" ? window.scrollY : 0);
  React.useEffect(() => {
    let raf;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { setY(window.scrollY); raf = null; });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return y;
}

// Element-relative parallax. Returns [ref, offsetPx] where offset tracks how far
// the element's centre is from the viewport centre, so drift is smooth and local
// (not a runaway offset that grows with page scroll position). Attach the ref to a
// container and apply translateY(offset) to the moving child.
function useParallax(speed = 0.1) {
  const ref = React.useRef(null);
  const [offset, setOffset] = React.useState(0);
  React.useEffect(() => {
    let raf;
    const update = () => {
      raf = null;
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      const centreDist = (rect.top + rect.height / 2 - vh / 2) / vh; // -~0.5..0.5 across a pass
      setOffset(centreDist * speed * 100);
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    update();
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [speed]);
  return [ref, offset];
}

// Reactive media-query hook for responsive layout switches in inline styles.
function useMediaQuery(query) {
  const get = () => (typeof window !== "undefined" ? window.matchMedia(query).matches : false);
  const [matches, setMatches] = React.useState(get);
  React.useEffect(() => {
    const mql = window.matchMedia(query);
    const onChange = () => setMatches(mql.matches);
    onChange();
    if (mql.addEventListener) mql.addEventListener("change", onChange);
    else mql.addListener(onChange);
    return () => {
      if (mql.removeEventListener) mql.removeEventListener("change", onChange);
      else mql.removeListener(onChange);
    };
  }, [query]);
  return matches;
}

window.JMK = window.JMK || {};
window.JMK.Reveal = Reveal;
window.JMK.useScrollY = useScrollY;
window.JMK.useParallax = useParallax;
window.JMK.useMediaQuery = useMediaQuery;
