/* Motion — two signature moments only: the hero headline reveal and counting
   stats. Everything else on the page is a calm scroll-fade. */

const prefersReduced = () =>
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

/* Count a number up when it scrolls into view. Parses "+1500" / "+1M" / "98%"
   and preserves the prefix/suffix exactly as written. */
function useCountUp(ref, raw, duration = 1600) {
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const m = String(raw).match(/^([^\d]*)([\d.]+)(.*)$/);
    if (!m || prefersReduced()) return;
    const [, prefix, numStr, suffix] = m;
    const target = parseFloat(numStr);
    const decimals = (numStr.split('.')[1] || '').length;

    let raf, started = false;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (!e.isIntersecting || started) return;
        started = true;
        const t0 = performance.now();
        const tick = (now) => {
          const p = Math.min(1, (now - t0) / duration);
          const eased = 1 - Math.pow(1 - p, 3);
          const val = target * eased;
          el.textContent = prefix + val.toFixed(decimals) + suffix;
          if (p < 1) raf = requestAnimationFrame(tick);
        };
        el.textContent = prefix + '0' + suffix;
        raf = requestAnimationFrame(tick);
        io.unobserve(e.target);
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => { io.disconnect(); if (raf) cancelAnimationFrame(raf); };
  }, [raw]);
}

function CountUp({ value, className }) {
  const ref = React.useRef(null);
  useCountUp(ref, value);
  return <span ref={ref} className={className}>{value}</span>;
}

/* Split a string into per-word spans so CSS can cascade a reveal across them.
   The inter-word space is its OWN element: a trailing space inside an
   inline-block with overflow:hidden gets collapsed away by the browser,
   which runs the words together. */
function SplitWords({ text, className, delay = 0, step = 55 }) {
  const words = String(text).split(' ');
  return (
    <span className={'split ' + (className || '')}>
      {words.map((w, i) => (
        <React.Fragment key={i}>
          <span className="split-word">
            <span style={{ animationDelay: (delay + i * step) + 'ms' }}>{w}</span>
          </span>
          {i < words.length - 1 ? <span className="split-space"> </span> : null}
        </React.Fragment>
      ))}
    </span>
  );
}

/* Duration from the track's real width, so the belt always moves at a
   constant px/second no matter how many chips it holds. */
function useMarquee(speed = 55) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const apply = () => {
      const span = el.scrollWidth / 3;
      if (span > 0) el.style.animationDuration = (span / speed).toFixed(2) + 's';
    };
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(el);
    return () => ro.disconnect();
  }, [speed]);
  return ref;
}

Object.assign(window, { useMarquee, useCountUp, CountUp, SplitWords, prefersReduced });
