// site — общие хелперы: скролл-ревил, count-up, плавный скролл к якорю
const REDUCED = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function scrollToId(id) {
  const el = document.getElementById(id);
  if (!el) return;
  const top = el.getBoundingClientRect().top + window.scrollY - 72;
  window.scrollTo({ top, behavior: REDUCED ? 'auto' : 'smooth' });
}

// Появление секции при вскролле (fade + 14px вверх), один раз
function Reveal({ children, delay = 0, as = 'div', style }) {
  const ref = React.useRef(null);
  const [vis, setVis] = React.useState(REDUCED);
  React.useEffect(() => {
    if (REDUCED || !ref.current) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setVis(true); io.disconnect(); } });
    }, { threshold: 0.15, rootMargin: '0px 0px -8% 0px' });
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  const Tag = as;
  return (
    <Tag ref={ref} style={{
      ...style,
      opacity: vis ? 1 : 0,
      transform: vis ? 'none' : 'translateY(14px)',
      transition: `opacity 0.6s var(--ease-out) ${delay}ms, transform 0.6s var(--ease-out) ${delay}ms`,
    }}>{children}</Tag>
  );
}

// Плавный набор числа при появлении
function useCountUp(target, { duration = 1200, decimals = 0 } = {}) {
  const ref = React.useRef(null);
  const [val, setVal] = React.useState(REDUCED ? target : 0);
  React.useEffect(() => {
    if (REDUCED || !ref.current) return;
    let raf, start;
    const io = new IntersectionObserver((entries) => {
      if (!entries[0].isIntersecting) return;
      io.disconnect();
      const step = (t) => {
        if (!start) start = t;
        const p = Math.min(1, (t - start) / duration);
        const eased = 1 - Math.pow(1 - p, 3);
        setVal(target * eased);
        if (p < 1) raf = requestAnimationFrame(step);
        else setVal(target);
      };
      raf = requestAnimationFrame(step);
    }, { threshold: 0.4 });
    io.observe(ref.current);
    return () => { io.disconnect(); cancelAnimationFrame(raf); };
  }, [target]);
  const fmt = decimals > 0
    ? val.toFixed(decimals)
    : Math.round(val).toLocaleString('ru-RU');
  return [ref, fmt];
}

// Печатающаяся строка терминала (зациклено)
function TypeLine({ text, prefix = '➜', speed = 62, style }) {
  const [n, setN] = React.useState(REDUCED ? text.length : 0);
  React.useEffect(() => {
    if (REDUCED) return;
    let i = 0, alive = true, to;
    const tick = () => {
      if (!alive) return;
      i = i < text.length ? i + 1 : 0;
      setN(i);
      to = setTimeout(tick, i === 0 ? 2400 : i === text.length ? 2600 : speed);
    };
    to = setTimeout(tick, 700);
    return () => { alive = false; clearTimeout(to); };
  }, [text]);
  return (
    <span style={{ fontFamily: 'var(--font-mono)', ...style }}>
      <span style={{ color: 'var(--green-500)' }}>{prefix}</span> {text.slice(0, n)}<span className="ox-caret"></span>
    </span>
  );
}

Object.assign(window, { scrollToId, Reveal, useCountUp, TypeLine, SITE_REDUCED: REDUCED });
