// js/lib.jsx — shared motion + utility components (exported to window)
const { useState, useEffect, useRef } = React;

/* Scroll reveal fallback for browsers without animation-timeline: view() */
function useRevealObserver() {
  useEffect(() => {
    if (CSS.supports && CSS.supports("animation-timeline: view()")) return;
    const els = [...document.querySelectorAll(".reveal, .line-mask")];
    let raf;
    const sweep = () => els.forEach((el) => {
      if (el.classList.contains("in")) return;
      if (el.getBoundingClientRect().top < innerHeight * 0.9) {
        el.classList.add("in");
        el.style.animation = el.classList.contains("line-mask") ? "" : "revealIn .9s var(--ease) both";
        if (el.classList.contains("line-mask")) { const s = el.querySelector("span"); if (s) s.style.animation = "maskIn 1s var(--ease) both"; }
        else el.style.opacity = 1, el.style.transform = "none";
      }
    });
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(sweep); };
    sweep(); addEventListener("scroll", onScroll, { passive: true }); addEventListener("resize", onScroll);
    return () => { removeEventListener("scroll", onScroll); removeEventListener("resize", onScroll); cancelAnimationFrame(raf); };
  }, []);
}

/* Parallax drift on the ambient orbs (mouse + scroll) */
function useParallaxOrbs() {
  useEffect(() => {
    if (window.matchMedia("(hover: none)").matches) return;
    let raf, mx = 0, my = 0, tx = 0, ty = 0;
    const move = (e) => { mx = (e.clientX / innerWidth - 0.5); my = (e.clientY / innerHeight - 0.5); };
    const loop = () => {
      tx += (mx - tx) * 0.06; ty += (my - ty) * 0.06;
      const sy = window.scrollY;
      const o1 = document.getElementById("orb1"), o2 = document.getElementById("orb2"), o3 = document.getElementById("orb3");
      if (o1) o1.style.transform = `translate(${tx * 50}px, ${ty * 40 + sy * 0.04}px)`;
      if (o2) o2.style.transform = `translate(${tx * -40}px, ${ty * -30 - sy * 0.03}px)`;
      if (o3) o3.style.transform = `translate(${tx * 70}px, ${ty * 60}px)`;
      raf = requestAnimationFrame(loop);
    };
    addEventListener("mousemove", move); loop();
    return () => { removeEventListener("mousemove", move); cancelAnimationFrame(raf); };
  }, []);
}

/* Custom cursor with hover-grow on [data-cursor] */
function Cursor() {
  const dot = useRef(null), ring = useRef(null);
  useEffect(() => {
    if (window.matchMedia("(hover: none)").matches) return;
    let mx = innerWidth / 2, my = innerHeight / 2, rx = mx, ry = my, raf;
    const move = (e) => {
      mx = e.clientX; my = e.clientY;
      if (dot.current) dot.current.style.transform = `translate(${mx}px,${my}px) translate(-50%,-50%)`;
      const t = e.target.closest("[data-cursor]"); const r = ring.current; if (!r) return;
      if (t) { r.classList.add("hot"); const l = t.getAttribute("data-cursor"); if (l) { r.classList.add("lbl"); r.setAttribute("data-label", l); } else r.classList.remove("lbl"); }
      else r.classList.remove("hot", "lbl");
    };
    const loop = () => { rx += (mx - rx) * 0.2; ry += (my - ry) * 0.2; if (ring.current) ring.current.style.transform = `translate(${rx}px,${ry}px) translate(-50%,-50%)`; raf = requestAnimationFrame(loop); };
    addEventListener("mousemove", move); loop();
    return () => { removeEventListener("mousemove", move); cancelAnimationFrame(raf); };
  }, []);
  return (<>
    <div className="cursor-ring" ref={ring}></div>
    <div className="cursor-dot" ref={dot}></div>
  </>);
}

/* Magnetic wrapper */
function Magnetic({ children, strength = 0.35, className, style }) {
  const ref = useRef(null);
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    el.style.transform = `translate(${(e.clientX - (r.left + r.width / 2)) * strength}px, ${(e.clientY - (r.top + r.height / 2)) * strength}px)`;
  };
  const reset = () => { if (ref.current) ref.current.style.transform = "translate(0,0)"; };
  return (<span ref={ref} onMouseMove={onMove} onMouseLeave={reset}
    style={{ display: "inline-block", transition: "transform .35s cubic-bezier(.22,.61,.36,1)", ...style }} className={className}>{children}</span>);
}

/* Count-up number when scrolled into view */
function CountUp({ value, suffix = "", dur = 1700 }) {
  const ref = useRef(null);
  const [n, setN] = useState(0);
  useEffect(() => {
    let done = false, raf;
    const io = new IntersectionObserver((es) => es.forEach((e) => {
      if (e.isIntersecting && !done) {
        done = true; const t0 = performance.now();
        const tick = (t) => { const p = Math.min(1, (t - t0) / dur); setN(Math.round((1 - Math.pow(1 - p, 3)) * value)); if (p < 1) raf = requestAnimationFrame(tick); };
        raf = requestAnimationFrame(tick);
      }
    }), { threshold: 0.5 });
    if (ref.current) io.observe(ref.current);
    return () => { io.disconnect(); cancelAnimationFrame(raf); };
  }, [value]);
  return <span ref={ref}>{n}{suffix}</span>;
}

/* glowing status-dot bloom */
function Bloom({ color, style }) {
  return <span className="bloom" style={{ "--c": color || "var(--accent)", ...style }}><i></i><i></i><i></i></span>;
}

/* Rotating circular badge */
function RotatingBadge({ text = "OPEN TO WORK · ", size = 116, speed = 18 }) {
  const id = useRef("p" + Math.random().toString(36).slice(2, 8)).current;
  return (
    <div style={{ width: size, height: size, position: "relative" }}>
      <svg viewBox="0 0 100 100" width={size} height={size} style={{ animation: `spin ${speed}s linear infinite` }}>
        <defs><path id={id} d="M50,50 m-37,0 a37,37 0 1,1 74,0 a37,37 0 1,1 -74,0" /></defs>
        <text fontFamily="var(--font-mono)" fontSize="8.6" letterSpacing="1.4" fill="var(--text-2)"><textPath href={`#${id}`}>{text.repeat(2)}</textPath></text>
      </svg>
      <span style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: size * 0.26, color: "var(--accent)" }}>✦</span>
      <style>{`@keyframes spin{to{transform:rotate(360deg)}}`}</style>
    </div>
  );
}

/* Infinite marquee row */
function Marquee({ items, sep = "✦", style }) {
  const row = items.map((it, i) => (
    <React.Fragment key={i}>
      <span className="marquee__item">{it}</span>
      <span className="marquee__star grad">{sep}</span>
    </React.Fragment>
  ));
  return <div className="marquee" style={style}><div className="marquee__track">{row}{row}</div></div>;
}

const Icon = {
  arrow: (p) => (<svg width="16" height="16" viewBox="0 0 16 16" fill="none" {...p}><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  arrowUR: (p) => (<svg width="15" height="15" viewBox="0 0 16 16" fill="none" {...p}><path d="M5 11L11 5M11 5H5M11 5v6" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  lock: (p) => (<svg width="13" height="13" viewBox="0 0 14 14" fill="none" {...p}><rect x="2.5" y="6" width="9" height="6.5" rx="1.4" stroke="currentColor" strokeWidth="1.4"/><path d="M4.5 6V4.3a2.5 2.5 0 015 0V6" stroke="currentColor" strokeWidth="1.4"/></svg>),
  scan: (p) => (<svg width="15" height="15" viewBox="0 0 16 16" fill="none" {...p}><path d="M2 5V3.5A1.5 1.5 0 013.5 2H5M11 2h1.5A1.5 1.5 0 0114 3.5V5M14 11v1.5a1.5 1.5 0 01-1.5 1.5H11M5 14H3.5A1.5 1.5 0 012 12.5V11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/><path d="M2 8h12" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>),
  pin: (p) => (<svg width="15" height="15" viewBox="0 0 16 16" fill="none" {...p}><path d="M8 1.6c-2.5 0-4.4 2-4.4 4.4 0 3.1 4.4 8.4 4.4 8.4s4.4-5.3 4.4-8.4c0-2.4-2-4.4-4.4-4.4z" stroke="currentColor" strokeWidth="1.3"/><circle cx="8" cy="6" r="1.7" stroke="currentColor" strokeWidth="1.3"/></svg>),
};

Object.assign(window, { useRevealObserver, useParallaxOrbs, Cursor, Magnetic, CountUp, Bloom, RotatingBadge, Marquee, Icon });
