// Ascend Faith — shared motion layer for the website + app UI kits.
// Loaded as <script type="text/babel"> after the DS bundle; exposes window.AF_MOTION.
// No framework dependency beyond React. GSAP / ScrollTrigger / Lenis are optional (website loads them).
(() => {
const reduced = () => window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

/* ---------- useInView: IntersectionObserver against any scroll root ---------- */
function useInView(ref, { root = null, once = true, threshold = 0.2 } = {}) {
  const [inView, setInView] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    if (reduced()) { setInView(true); return; }
    const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setInView(true); if (once) io.disconnect(); } else if (!once) setInView(false); }, { root: root && root.current ? root.current : root, threshold });
    io.observe(el); return () => io.disconnect();
  }, []);
  return inView;
}

/* ---------- WordsPullUp — ported from 21st.dev PrismaHero (framer-motion → CSS) ---------- */
function WordsPullUp({ text, className = '', style, mark = false, delay = 0, stagger = 80, as: Tag = 'span' }) {
  const ref = React.useRef(null);
  const inView = useInView(ref, { threshold: 0.1 });
  const words = text.split(' ');
  return (
    <Tag ref={ref} className={`afm-words ${inView ? 'is-in' : ''} ${className}`} style={style}>
      <span className="afm-sr">{text}</span>
      {words.map((w, i) => (
        <span key={i} className="afm-word" aria-hidden="true" style={{ marginRight: i === words.length - 1 ? 0 : '0.25em', '--wd': (delay + i * stagger) + 'ms' }}>
          <span>{w}</span>
        </span>
      ))}
      {mark && <img className="afm-mark-sup" src={mark} alt="" aria-hidden="true" style={{ '--wd': (delay + words.length * stagger + 250) + 'ms' }} />}
    </Tag>
  );
}

/* ---------- WordsPullUpMultiStyle — segments with their own className ---------- */
function WordsPullUpMultiStyle({ segments, className = '', style, delay = 0, stagger = 80 }) {
  const ref = React.useRef(null);
  const inView = useInView(ref, { threshold: 0.1 });
  const words = [];
  segments.forEach(s => s.text.split(' ').forEach(w => w && words.push({ w, c: s.className })));
  return (
    <span ref={ref} className={`afm-words ${inView ? 'is-in' : ''} ${className}`} style={style}>
      <span className="afm-sr">{segments.map(s => s.text).join(' ')}</span>
      {words.map((x, i) => <span key={i} className={`afm-word ${x.c || ''}`} aria-hidden="true" style={{ marginRight: '0.25em', '--wd': (delay + i * stagger) + 'ms' }}><span>{x.w}</span></span>)}
    </span>
  );
}

/* ---------- Iconic figures (gold line art, 24-unit grid) ---------- */
const FIG = {
  cross: <path d="M12 2.5v19M6.5 8h11" />,
  dove: <path d="M3 13.5c2.6.4 4.6-.6 6-3.2.9 2.3 2.7 3.4 5.4 3.3L20 10l-1.6 4.6c-1.8 4.3-6.6 6.2-11.4 5.1L4.4 21l.9-3.6C4 16.5 3.3 15.2 3 13.5zM14.5 8.5c.4-2.6 2.2-4.4 5-5-.2 2.6-1.4 4.4-3.6 5.3" />,
  peak: <><path d="M2 20.5 9 9.5l4 6 3-4 6 9z" /><path d="M13.5 7.5 19 2M15 2h4v4" /></>,
  flame: <path d="M12 2.5c.8 3.8 5 5.9 5 10.7a5 5 0 0 1-10 0c0-2.8 1.8-4 2-6.6 1.2 1 2.1 2.2 2.6 3.9 1-2.8.4-5.3.4-8z" />,
  book: <><path d="M2.5 5.5c3.2-1.2 6.6-1 9.5.9 2.9-1.9 6.3-2.1 9.5-.9v13.8c-3.2-1.2-6.6-1-9.5.9-2.9-1.9-6.3-2.1-9.5-.9z" /><path d="M12 6.4v13.8" /></>,
  fish: <><path d="M2.5 12c3.8-5.4 10.7-5.4 15 0-4.3 5.4-11.2 5.4-15 0z" /><path d="M17.5 12 21.5 8v8z" /></>,
  crown: <path d="M3 18.5 4.8 8l4.4 4.2L12 5l2.8 7.2L19.2 8 21 18.5zM3 21h18" />,
  sparkle: <path d="M12 2.5l1.7 6.8 6.8 2.7-6.8 2.7L12 21.5l-1.7-6.8L3.5 12l6.8-2.7z" />,
  olive: <><path d="M4 20C9 15 13 10 20 4" /><path d="M8.5 15.5c-2.2-.3-3.6-1.6-4-3.8 2.2.3 3.6 1.6 4 3.8zM12 12c.3-2.2 1.6-3.6 3.8-4-.3 2.2-1.6 3.6-3.8 4zM15.5 8.5c-2.2-.3-3.6-1.6-4-3.8 2.2.3 3.6 1.6 4 3.8z" /></>,
};
function Figure({ name, size = 28, stroke = 1.3, style, className = '' }) {
  return <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" className={className} style={style} aria-hidden="true">{FIG[name]}</svg>;
}

/* ---------- FloatingIcons — ambient layer; optional pointer parallax ---------- */
// items: [{ fig, x:'12%', y:'30%', size, depth (0-1), o, r, dur, delay }]
function FloatingIcons({ items, parallax = true, className = '', style }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!parallax || reduced()) return;
    const host = ref.current && ref.current.parentElement; if (!host) return;
    const figs = Array.from(ref.current.children);
    let raf = 0, tx = 0, ty = 0, cx = 0, cy = 0;
    const move = e => { const r = host.getBoundingClientRect(); tx = (e.clientX - r.left) / r.width - 0.5; ty = (e.clientY - r.top) / r.height - 0.5; if (!raf) raf = requestAnimationFrame(tick); };
    const tick = () => { cx += (tx - cx) * 0.08; cy += (ty - cy) * 0.08; figs.forEach(f => { const d = +f.dataset.depth || 0.4; f.style.transform = `translate3d(${-cx * 60 * d}px,${-cy * 60 * d}px,0)`; }); raf = Math.abs(tx - cx) + Math.abs(ty - cy) > 0.001 ? requestAnimationFrame(tick) : 0; };
    host.addEventListener('pointermove', move); return () => { host.removeEventListener('pointermove', move); cancelAnimationFrame(raf); };
  }, [parallax]);
  return (
    <div ref={ref} className={`afm-float-layer ${className}`} style={style} aria-hidden="true">
      {items.map((it, i) => (
        <span key={i} className="afm-fig" data-depth={it.depth ?? 0.4} style={{ left: it.x, top: it.y, width: it.size || 28, height: it.size || 28, '--o': it.o ?? 0.5, '--r': (it.r ?? 6) + 'deg', '--dur': (it.dur ?? 7) + 's', '--delay': (it.delay ?? i * -1.3) + 's', '--amp': (it.amp ?? 18) + 'px', color: it.color }}>
          <Figure name={it.fig} size="100%" stroke={it.stroke ?? 1.2} />
        </span>
      ))}
    </div>
  );
}

/* ---------- GoldDust — canvas particles rising like embers / light motes ---------- */
function GoldDust({ count = 60, className = '', style, speed = 1 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const c = ref.current; if (!c || reduced()) return;
    const ctx = c.getContext('2d'); let w = 0, h = 0, raf = 0, alive = true;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const size = () => { const r = c.getBoundingClientRect(); w = r.width; h = r.height; c.width = w * dpr; c.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); };
    size();
    const P = Array.from({ length: count }, () => ({ x: Math.random() * w, y: Math.random() * h, r: Math.random() * 1.6 + 0.4, v: (Math.random() * 0.35 + 0.1) * speed, s: Math.random() * Math.PI * 2, a: Math.random() * 0.6 + 0.2 }));
    const ro = new ResizeObserver(size); ro.observe(c);
    // pause when offscreen
    let visible = true; const io = new IntersectionObserver(([e]) => { visible = e.isIntersecting; if (visible && !raf) raf = requestAnimationFrame(draw); }); io.observe(c);
    function draw() {
      raf = 0; if (!alive || !visible) return;
      ctx.clearRect(0, 0, w, h);
      for (const p of P) {
        p.y -= p.v; p.s += 0.01; p.x += Math.sin(p.s) * 0.25;
        if (p.y < -4) { p.y = h + 4; p.x = Math.random() * w; }
        ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(255,208,82,${p.a * (0.6 + 0.4 * Math.sin(p.s * 3))})`;
        ctx.shadowColor = 'rgba(255,208,82,.8)'; ctx.shadowBlur = 6; ctx.fill();
      }
      raf = requestAnimationFrame(draw);
    }
    raf = requestAnimationFrame(draw);
    return () => { alive = false; cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); };
  }, [count, speed]);
  return <canvas ref={ref} className={className} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', ...style }} aria-hidden="true" />;
}

/* ---------- Tilt — pointer-driven 3D tilt with gold glare ---------- */
function Tilt({ children, max = 8, className = '', style }) {
  const ref = React.useRef(null);
  const onMove = e => {
    if (reduced()) return;
    const el = ref.current, r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
    el.style.transform = `perspective(900px) rotateX(${(0.5 - py) * max}deg) rotateY(${(px - 0.5) * max}deg)`;
    el.style.setProperty('--gx', px * 100 + '%'); el.style.setProperty('--gy', py * 100 + '%');
  };
  const onLeave = () => { ref.current.style.transform = ''; };
  return <div ref={ref} className={`afm-tilt ${className}`} style={style} onPointerMove={onMove} onPointerLeave={onLeave}>{children}<span className="afm-tilt__glare" /></div>;
}

/* ---------- useStaggerReveal — tag children of a container and reveal as they enter its scroll root ---------- */
function useStaggerReveal(containerRef, selector = ':scope > *', deps = [], { rootRef = null, step = 70, max = 6 } = {}) {
  React.useEffect(() => {
    const host = containerRef.current; if (!host) return;
    const els = Array.from(host.querySelectorAll(selector)).filter(el => !el.classList.contains('afm-rv') && !el.closest('[data-no-rv]'));
    els.forEach((el, i) => { el.classList.add('afm-rv'); el.style.setProperty('--d', Math.min(i, max) * step + 'ms'); });
    if (reduced()) { els.forEach(el => el.classList.add('in')); return; }
    const root = rootRef ? rootRef.current : null;
    const io = new IntersectionObserver(es => es.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }), { root, threshold: 0.08, rootMargin: '0px 0px -24px 0px' });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, deps);
}

/* ---------- burst — gold particle burst + optional floating word at a screen point ---------- */
function burst(x, y, word) {
  if (reduced()) return;
  const host = document.createElement('div'); host.className = 'afm-burst'; host.style.left = x + 'px'; host.style.top = y + 'px';
  for (let i = 0; i < 14; i++) { const a = (Math.PI * 2 * i) / 14 + Math.random() * 0.3, d = 26 + Math.random() * 30; const p = document.createElement('i'); p.style.setProperty('--x', Math.cos(a) * d + 'px'); p.style.setProperty('--y', Math.sin(a) * d + 'px'); p.style.animationDelay = Math.random() * 60 + 'ms'; host.appendChild(p); }
  if (word) { const b = document.createElement('b'); b.textContent = word; host.appendChild(b); }
  document.body.appendChild(host); setTimeout(() => host.remove(), 1200);
}

/* ---------- Smooth scroll (Lenis + GSAP ScrollTrigger sync) — website only ---------- */
function initSmoothScroll() {
  if (window.__afLenis || reduced() || !window.Lenis) return window.__afLenis || null;
  const lenis = new window.Lenis({ duration: 1.15, easing: t => 1 - Math.pow(1 - t, 4), smoothWheel: true });
  if (window.gsap && window.ScrollTrigger) {
    lenis.on('scroll', window.ScrollTrigger.update);
    window.gsap.ticker.add(t => lenis.raf(t * 1000));
    window.gsap.ticker.lagSmoothing(0);
  } else { const raf = t => { lenis.raf(t); requestAnimationFrame(raf); }; requestAnimationFrame(raf); }
  window.__afLenis = lenis; return lenis;
}
function scrollToY(y, immediate = false) {
  if (window.__afLenis) window.__afLenis.scrollTo(y, { immediate, duration: 1.6 });
  else window.scrollTo({ top: y, behavior: immediate ? 'auto' : 'smooth' });
}

/* ---------- ScrollProgress — thin gold bar tracking page scroll ---------- */
function ScrollProgress() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const on = () => { const h = document.documentElement.scrollHeight - innerHeight; ref.current && ref.current.style.setProperty('--p', h > 0 ? scrollY / h : 0); };
    on(); addEventListener('scroll', on, { passive: true }); return () => removeEventListener('scroll', on);
  }, []);
  return <div ref={ref} className="afm-progress" aria-hidden="true" />;
}

/* ---------- ScrubWords — scripture text that brightens word-by-word as it scrolls through ---------- */
function ScrubWords({ text, className = '', style }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const spans = Array.from(el.querySelectorAll('span'));
    if (reduced() || !window.gsap || !window.ScrollTrigger) { spans.forEach(s => (s.style.opacity = 1)); return; }
    const tw = window.gsap.fromTo(spans, { opacity: 0.14 }, { opacity: 1, stagger: 0.08, ease: 'none', scrollTrigger: { trigger: el, start: 'top 85%', end: 'bottom 45%', scrub: true } });
    return () => { tw.scrollTrigger && tw.scrollTrigger.kill(); tw.kill(); };
  }, [text]);
  return <span ref={ref} className={className} style={style}>{text.split(' ').map((w, i) => <span key={i} style={{ opacity: 0.14 }}>{w + ' '}</span>)}</span>;
}

/* ---------- useParallax — any [data-speed] inside rootRef drifts vertically while its section scrolls ---------- */
function useParallax(rootRef, deps = []) {
  React.useEffect(() => {
    const host = rootRef.current; if (!host || reduced() || !window.gsap || !window.ScrollTrigger) return;
    const gsap = window.gsap; gsap.registerPlugin(window.ScrollTrigger);
    const tweens = Array.from(host.querySelectorAll('[data-speed]')).map(el => gsap.fromTo(el, { yPercent: +el.dataset.speed * 12 }, { yPercent: +el.dataset.speed * -12, ease: 'none', scrollTrigger: { trigger: el.closest('section') || el, start: 'top bottom', end: 'bottom top', scrub: true } }));
    return () => tweens.forEach(t => { t.scrollTrigger && t.scrollTrigger.kill(); t.kill(); });
  }, deps);
}

window.AF_MOTION = { reduced, useInView, WordsPullUp, WordsPullUpMultiStyle, Figure, FloatingIcons, GoldDust, Tilt, useStaggerReveal, useParallax, burst, initSmoothScroll, scrollToY, ScrollProgress, ScrubWords };
})();
