// Landing v5 — motion system. Uses scroll-position + getBoundingClientRect
// (IntersectionObserver is unreliable in some embedded iframes) so reveals
// always trigger. All motion honors prefers-reduced-motion: reduced shows the
// final frame instantly, blobs sit still, the bloom snaps open, nothing loops.

function useReducedMotion() {
  const [reduced, setReduced] = React.useState(() => {
    try { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
    catch { return false; }
  });
  React.useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const on = () => setReduced(mq.matches);
    mq.addEventListener?.('change', on);
    return () => mq.removeEventListener?.('change', on);
  }, []);
  return reduced;
}

function useScrollProgress() {
  const [prog, setProg] = React.useState(0);
  React.useEffect(() => {
    let ticking = false;
    const compute = () => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      setProg(max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0);
      ticking = false;
    };
    const onScroll = () => { if (!ticking) { ticking = true; requestAnimationFrame(compute); } };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    compute();
    return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); };
  }, []);
  return prog;
}

// Core: reveal when the element scrolls into view. `amount` = fraction of the
// viewport the top must cross. Checks run synchronously + on scroll (not via
// requestAnimationFrame, which is paused while the preview iframe is hidden).
function useReveal({ amount = 0.12, once = true } = {}) {
  const reduced = useReducedMotion();
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    if (reduced) { setShown(true); return; }
    const el = ref.current; if (!el) return;
    let finished = false, last = 0;
    const check = () => {
      if (finished) return;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      const visible = r.top < vh * (1 - amount) && r.bottom > vh * 0.03;
      if (visible) { setShown(true); if (once) { finished = true; cleanup(); } }
      else if (!once) setShown(false);
    };
    const onScroll = () => { const now = Date.now(); if (now - last > 40) { last = now; check(); } };
    const cleanup = () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    check();                       // synchronous, runs even when iframe hidden
    const t1 = setTimeout(check, 80);
    const t2 = setTimeout(check, 400);
    return () => { cleanup(); clearTimeout(t1); clearTimeout(t2); };
  }, [reduced, amount, once]);
  return [ref, shown];
}

// Coordinated section reveal — returns [ref, inView].
function useInView(amount = 0.3) {
  return useReveal({ amount });
}

// Reveal — fade + translate up as it enters the viewport.
function Reveal({ children, delay = 0, y = 22, once = true, as = 'div', amount = 0.12, style = {}, ...rest }) {
  const reduced = useReducedMotion();
  const [ref, shown] = useReveal({ once, amount });
  const Tag = as;
  return (
    <Tag ref={ref} style={{
      opacity: shown ? 1 : 0,
      transform: shown ? 'translateY(0)' : `translateY(${y}px)`,
      transition: reduced ? 'none' : `opacity 720ms cubic-bezier(.2,.7,.2,1) ${delay}ms, transform 720ms cubic-bezier(.2,.7,.2,1) ${delay}ms`,
      ...style,
    }} {...rest}>{children}</Tag>
  );
}

// Breathing organic blob — slowly morphing, very low-contrast background shape.
function Blob({ color, size = 520, top, left, right, bottom, opacity = 0.5, delay = 0, blur = 36, dur = 13 }) {
  const reduced = useReducedMotion();
  return (
    <div aria-hidden="true" style={{
      position: 'absolute', top, left, right, bottom,
      width: size, height: size, background: color, opacity,
      filter: `blur(${blur}px)`, pointerEvents: 'none', zIndex: 0,
      borderRadius: '42% 58% 56% 44% / 48% 44% 56% 52%',
      animation: reduced ? 'none' : `v5blob ${dur}s ${delay}s ease-in-out infinite`,
    }} />
  );
}

function LSection({ children, p, bg, id, style = {}, pad = '150px 0', topRule = false }) {
  return (
    <section id={id} data-screen-label={id} style={{
      background: bg || 'transparent', color: p.ink, padding: pad,
      position: 'relative', overflow: 'hidden',
      borderTop: topRule ? `1px solid ${p.rule}` : 'none', ...style,
    }}>{children}</section>
  );
}

// Parallax — translates child by `speed` * scroll within its own range.
function Parallax({ speed = 0.12, children, style = {} }) {
  const reduced = useReducedMotion();
  const ref = React.useRef(null);
  const [offset, setOffset] = React.useState(0);
  React.useEffect(() => {
    if (reduced) return;
    let ticking = false;
    const compute = () => {
      const el = ref.current; ticking = false;
      if (!el) return;
      const r = el.getBoundingClientRect();
      const center = r.top + r.height / 2 - window.innerHeight / 2;
      setOffset(-center * speed);
    };
    const onScroll = () => { if (!ticking) { ticking = true; requestAnimationFrame(compute); } };
    window.addEventListener('scroll', onScroll, { passive: true });
    compute();
    return () => window.removeEventListener('scroll', onScroll);
  }, [reduced, speed]);
  return <div ref={ref} style={{ transform: `translateY(${offset}px)`, willChange: 'transform', ...style }}>{children}</div>;
}

Object.assign(window, { useReducedMotion, useScrollProgress, useReveal, useInView, Reveal, Blob, LSection, Parallax });
