// Constellation.jsx — the brand premise, made literal and alive.
// Floating dots drift on the bone paper; nearby dots link with thin lines,
// and the cursor pulls its own connections. This is "konnecting dots."
function Constellation({ height = 620, density = 0.00009 }) {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const pointsRef = React.useRef([]);
  const mouseRef = React.useRef({ x: -9999, y: -9999, active: false });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    let w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);

    const resize = () => {
      const rect = canvas.getBoundingClientRect();
      w = rect.width; h = rect.height;
      canvas.width = w * dpr; canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const target = Math.max(28, Math.min(90, Math.floor(w * h * density)));
      const pts = pointsRef.current;
      while (pts.length < target) {
        pts.push({
          x: Math.random() * w, y: Math.random() * h,
          vx: (Math.random() - 0.5) * 0.22, vy: (Math.random() - 0.5) * 0.22,
          r: Math.random() * 1.6 + 1.1,
        });
      }
      pts.length = target;
    };

    // brand colors, resolved from CSS vars
    const cs = getComputedStyle(document.documentElement);
    const blue = (cs.getPropertyValue('--blue') || '#8B9DC3').trim();
    const ink = (cs.getPropertyValue('--ink') || '#1A1A1A').trim();
    const hexA = (hex, a) => {
      const m = hex.replace('#', '');
      const n = m.length === 3 ? m.split('').map(c => c + c).join('') : m;
      const r = parseInt(n.slice(0, 2), 16), g = parseInt(n.slice(2, 4), 16), b = parseInt(n.slice(4, 6), 16);
      return `rgba(${r},${g},${b},${a})`;
    };

    const LINK = 132, LINK2 = LINK * LINK;
    const MOUSE = 176, MOUSE2 = MOUSE * MOUSE;

    const step = () => {
      ctx.clearRect(0, 0, w, h);
      const pts = pointsRef.current;
      const m = mouseRef.current;

      for (let i = 0; i < pts.length; i++) {
        const p = pts[i];
        if (!reduce) { p.x += p.vx; p.y += p.vy; }
        if (p.x < 0 || p.x > w) p.vx *= -1;
        if (p.y < 0 || p.y > h) p.vy *= -1;
        p.x = Math.max(0, Math.min(w, p.x));
        p.y = Math.max(0, Math.min(h, p.y));
      }

      // dot-to-dot links
      for (let i = 0; i < pts.length; i++) {
        for (let j = i + 1; j < pts.length; j++) {
          const a = pts[i], b = pts[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx * dx + dy * dy;
          if (d2 < LINK2) {
            const t = 1 - d2 / LINK2;
            ctx.strokeStyle = hexA(blue, t * 0.42);
            ctx.lineWidth = 1;
            ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
          }
        }
      }

      // cursor links
      if (m.active) {
        for (let i = 0; i < pts.length; i++) {
          const p = pts[i];
          const dx = p.x - m.x, dy = p.y - m.y;
          const d2 = dx * dx + dy * dy;
          if (d2 < MOUSE2) {
            const t = 1 - d2 / MOUSE2;
            ctx.strokeStyle = hexA(ink, t * 0.5);
            ctx.lineWidth = 1;
            ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(m.x, m.y); ctx.stroke();
            // gentle attraction toward cursor
            if (!reduce) { p.vx += dx > 0 ? -0.0008 * t : 0.0008 * t; p.vy += dy > 0 ? -0.0008 * t : 0.0008 * t; }
          }
        }
        ctx.fillStyle = hexA(ink, 0.55);
        ctx.beginPath(); ctx.arc(m.x, m.y, 2.4, 0, Math.PI * 2); ctx.fill();
      }

      // dots
      for (let i = 0; i < pts.length; i++) {
        const p = pts[i];
        ctx.fillStyle = hexA(blue, 0.85);
        ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill();
      }

      if (running) rafRef.current = requestAnimationFrame(step);
    };

    // Only animate while the hero is on-screen and the tab is visible —
    // saves CPU and lets the page settle once you scroll past.
    let running = false;
    const start = () => { if (!running) { running = true; rafRef.current = requestAnimationFrame(step); } };
    const stop = () => { running = false; cancelAnimationFrame(rafRef.current); };

    resize();
    window.addEventListener('resize', resize);

    let onScreen = true;
    const io = new IntersectionObserver(([e]) => {
      onScreen = e.isIntersecting;
      if (onScreen && !document.hidden) start(); else stop();
    }, { threshold: 0 });
    io.observe(canvas);

    const onVis = () => { if (onScreen && !document.hidden) start(); else stop(); };
    document.addEventListener('visibilitychange', onVis);

    start();

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      mouseRef.current = { x: e.clientX - rect.left, y: e.clientY - rect.top, active: true };
    };
    const onLeave = () => { mouseRef.current.active = false; };
    canvas.addEventListener('pointermove', onMove);
    canvas.addEventListener('pointerleave', onLeave);

    return () => {
      stop();
      io.disconnect();
      document.removeEventListener('visibilitychange', onVis);
      window.removeEventListener('resize', resize);
      canvas.removeEventListener('pointermove', onMove);
      canvas.removeEventListener('pointerleave', onLeave);
    };
  }, [density]);

  return (
    <canvas
      ref={canvasRef}
      aria-hidden="true"
      style={{
        position: 'absolute', inset: 0, width: '100%', height: '100%',
        display: 'block', pointerEvents: 'auto',
      }}
    />
  );
}
window.Constellation = Constellation;
