// Site intro — a full-viewport brand cover shown when you first land on the
// home page. A canvas "focus field" animation (drifting dots + a lens that
// pulls nearby dots sharp and ember-bright) embodies the HyperFocus mark.
//
//  • Theme-aware: mirrors the site's light/dark setting (data-theme + the
//    hf-theme-change event), so the cover never clashes with the page.
//  • Interactive lens: the focal point auto-roams on a slow Lissajous path,
//    but follows the pointer (mouse / touch) whenever one is active, easing
//    back to auto-roam a couple of seconds after the pointer goes idle.
//  • Focus pull: the wordmark + slogan sit slightly out of focus until the
//    lens sweeps near them, then snap sharp — the brand's signature motion.
//
// As you scroll, the overlay parallax-fades and hands off to the index
// content; the sticky TopNav lives *after* this band so it appears once you
// scroll past.

function IntroCover() {
  const isMobile = window.useIsMobile ? useIsMobile() : false;
  const [theme, setTheme] = React.useState(
    () => (window.HFTheme ? window.HFTheme.resolve() : 'light')
  );

  const canvasRef = React.useRef(null);
  const sectionRef = React.useRef(null);
  const overlayRef = React.useRef(null);
  const stageRef = React.useRef(null);
  const themeRef = React.useRef(theme);
  themeRef.current = theme;

  // ── Track the site theme (light / dark) live ──────────────────────────
  React.useEffect(() => {
    const onChange = (e) => setTheme(e.detail.theme);
    window.addEventListener('hf-theme-change', onChange);
    if (window.HFTheme) setTheme(window.HFTheme.resolve());
    return () => window.removeEventListener('hf-theme-change', onChange);
  }, []);

  // ── Canvas focus-field + interactive lens ─────────────────────────────
  React.useEffect(() => {
    const canvas = canvasRef.current;
    const section = sectionRef.current;
    const overlay = overlayRef.current;
    if (!canvas || !section) return;

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

    const ctx = canvas.getContext('2d');
    let dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0, dots = [];
    let ocx = 0, ocy = 0; // overlay (wordmark) centre, in canvas space

    const EMBER = [194, 97, 31];
    const BONE = [236, 239, 233];
    const INK = [20, 32, 26];

    // Lens state — current (eased) position, plus the pointer target.
    let curX = 0, curY = 0;
    let ptrX = 0, ptrY = 0;
    let lastPointer = -1e9; // timestamp of last pointer activity
    const IDLE_MS = 2200;   // revert to auto-roam this long after pointer stops
    // Mobile focus intent: on touch (or while scrolling) the lens parks on
    // the logo and pulls it sharp; releasing returns it to auto-roam.
    let touchHeld = false;
    let lastScroll = -1e9;
    const SCROLL_FOCUS_MS = 1000; // keep the logo sharp this long after a scroll tick
    // Auto-roam pause: when the roaming lens finds the logo it settles on it
    // for a beat before drifting away again.
    let autoMode = 'roam';  // 'roam' | 'paused'
    let pauseStart = 0, cooldownUntil = 0;
    const PAUSE_MS = 5000;

    // The problems that orbit the lens — they dissolve as it focuses on the
    // logo, and swirl back as it drifts away.
    const PHRASES = [
      'No website or email',
      'Untracked invoices',
      'Drowning in emails',
      'Spreadsheet overload',
      'Too much manual admin',
      'No time to grow'
    ];
    let phraseLayouts = [], rText = 0, ringCirc = 0;

    function measureOverlay() {
      if (!overlay) { ocx = w / 2; ocy = h * 0.46; return; }
      const sr = section.getBoundingClientRect();
      const or = overlay.getBoundingClientRect();
      ocx = or.left - sr.left + or.width / 2;
      ocy = or.top - sr.top + or.height / 2;
    }

    function build() {
      const r = section.getBoundingClientRect();
      w = r.width; h = r.height;
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.floor(w * dpr);
      canvas.height = Math.floor(h * dpr);
      canvas.style.width = w + 'px';
      canvas.style.height = h + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      const spacing = w < 600 ? 40 : 52;
      dots = [];
      for (let y = spacing * 0.5; y < h + spacing; y += spacing) {
        for (let x = spacing * 0.5; x < w + spacing; x += spacing) {
          dots.push({
            bx: x + (Math.random() - 0.5) * spacing * 0.5,
            by: y + (Math.random() - 0.5) * spacing * 0.5,
            ph: Math.random() * Math.PI * 2,
            sp: 0.6 + Math.random() * 0.8
          });
        }
      }
      if (curX === 0 && curY === 0) { curX = w / 2; curY = h / 2; }
      measureOverlay();

      // Lay out the orbiting problem text — one phrase at a time, sized to sit
      // comfortably in the reticle annulus with room to breathe.
      const Rb = Math.min(w, h) * 0.46;
      rText = Rb * 0.43;
      ringCirc = 2 * Math.PI * rText;
      const baseFs = Math.max(11, Math.min(20, Rb * 0.078));
      const maxArc = ringCirc * 0.74;
      phraseLayouts = PHRASES.map((p) => {
        let fs = baseFs;
        const measure = () => {
          ctx.font = `600 ${fs}px "JetBrains Mono", ui-monospace, monospace`;
          let tot = 0; const chars = [];
          for (const ch of p) { const cw = ctx.measureText(ch).width; chars.push({ ch, w: cw }); tot += cw; }
          return { tot, chars };
        };
        let m = measure();
        if (m.tot > maxArc) { fs *= maxArc / m.tot; m = measure(); }
        return { font: `600 ${fs.toFixed(2)}px "JetBrains Mono", ui-monospace, monospace`, chars: m.chars, total: m.tot };
      });
    }

    function lerp(a, b, t) { return a + (b - a) * t; }
    function clamp01(v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
    function rgba(c, a) { return `rgba(${c[0]},${c[1]},${c[2]},${a})`; }
    function mix(c1, c2, t) {
      return `rgba(${Math.round(lerp(c1[0], c2[0], t))},${Math.round(lerp(c1[1], c2[1], t))},${Math.round(lerp(c1[2], c2[2], t))},`;
    }

    // One problem phrase, curved through the reticle annulus. Each glyph is
    // oriented by its own absolute angle so the bottom half stays readable.
    function drawPhrase(cx, cy, lay, centerAngle, alpha, col) {
      if (alpha <= 0.02 || !lay || !rText) return;
      ctx.save();
      ctx.translate(cx, cy);
      ctx.font = lay.font;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillStyle = `rgba(${col[0]},${col[1]},${col[2]},${alpha.toFixed(3)})`;
      const totalAng = lay.total / rText;
      let a = centerAngle - totalAng / 2;
      for (let i = 0; i < lay.chars.length; i++) {
        const { ch, w: cw } = lay.chars[i];
        const adv = cw / rText;
        const mid = a + adv / 2;
        ctx.save();
        ctx.rotate(mid);
        ctx.translate(0, -rText);
        ctx.fillText(ch, 0, 0);
        ctx.restore();
        a += adv;
      }
      ctx.restore();
    }

    function frame(now) {
      const t = now || 0;
      const dark = themeRef.current === 'dark';
      const BASE = dark ? BONE : INK;
      const baseAlpha = dark ? 0.10 : 0.085;
      const reticleA = dark ? [0.28, 0.14, 0.32] : [0.34, 0.16, 0.40];

      ctx.clearRect(0, 0, w, h);

      // auto-roam path
      const autoX = w * (0.5 + 0.30 * Math.sin(t * 0.00020));
      const autoY = h * (0.5 + 0.26 * Math.sin(t * 0.00016 + 1.3));

      // choose the desired focal point: pointer if recently active, else the
      // auto-roam — which pauses on the logo for a beat each time it finds it.
      const pointerLive = (t - lastPointer) < IDLE_MS;
      const mobileFocus = isMobile && (touchHeld || (t - lastScroll) < SCROLL_FOCUS_MS);
      let desX, desY;
      if (mobileFocus) {
        // Phone: snap the lens onto the wordmark and hold it there.
        desX = ocx; desY = ocy; autoMode = 'roam';
      } else if (pointerLive) {
        desX = ptrX; desY = ptrY; autoMode = 'roam';
      } else if (autoMode === 'paused') {
        desX = ocx; desY = ocy;
        if (t - pauseStart > PAUSE_MS) { autoMode = 'roam'; cooldownUntil = t + 3500; }
      } else {
        desX = autoX; desY = autoY;
        const dC = Math.hypot(curX - ocx, curY - ocy);
        if (t > cooldownUntil && dC < Math.min(w, h) * 0.06) {
          autoMode = 'paused'; pauseStart = t; desX = ocx; desY = ocy;
        }
      }

      // ease current toward desired (snappier while following the pointer,
      // gentle while settling onto the logo)
      const k = mobileFocus ? 0.16 : (pointerLive ? 0.14 : (autoMode === 'paused' ? 0.07 : 0.05));
      curX = lerp(curX, desX, k);
      curY = lerp(curY, desY, k);

      const fx = curX, fy = curY;
      const R = Math.min(w, h) * 0.46;

      // proximity of the lens to the logo — drives both the ring-text dissolve
      // and the wordmark focus-pull
      const od = Math.hypot(fx - ocx, fy - ocy);
      const nearR = Math.min(w, h) * 0.46;
      const sharp = clamp01(1 - od / nearR);

      for (let i = 0; i < dots.length; i++) {
        const d = dots[i];
        const drift = reduce ? 0 : 5;
        const x = d.bx + Math.sin(t * 0.0003 * d.sp + d.ph) * drift;
        const y = d.by + Math.cos(t * 0.00026 * d.sp + d.ph) * drift;
        const dist = Math.hypot(x - fx, y - fy);
        let focus = clamp01(1 - dist / R);
        const f = focus * focus;

        const radius = lerp(0.6, isMobile ? 2.6 : 3.4, Math.pow(focus, 1.4));
        const alpha = lerp(baseAlpha, 0.92, f);
        ctx.beginPath();
        ctx.fillStyle = mix(BASE, EMBER, f) + alpha.toFixed(3) + ')';
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
      }

      // the lens — concentric reticle rings + crosshair at the focal point
      ctx.lineWidth = 1;
      ctx.strokeStyle = rgba(EMBER, reticleA[0]);
      ctx.beginPath();
      ctx.arc(fx, fy, R * 0.34, 0, Math.PI * 2);
      ctx.stroke();
      ctx.strokeStyle = rgba(EMBER, reticleA[1]);
      ctx.beginPath();
      ctx.arc(fx, fy, R * 0.52, 0, Math.PI * 2);
      ctx.stroke();
      ctx.strokeStyle = rgba(EMBER, reticleA[2]);
      const tick = 7, rr = R * 0.34;
      [[0, -1], [0, 1], [-1, 0], [1, 0]].forEach(([dx, dy]) => {
        ctx.beginPath();
        ctx.moveTo(fx + dx * (rr - tick), fy + dy * (rr - tick));
        ctx.lineTo(fx + dx * (rr + tick), fy + dy * (rr + tick));
        ctx.stroke();
      });

      // problem text orbiting the annulus — one phrase at a time, swirling in
      // as the lens drifts off the logo and dissolving as it focuses on it
      if (phraseLayouts.length) {
        const DUR = 4200;                    // ms each phrase is shown (incl. fades)
        const FADE = 0.16;
        const idx = Math.floor(t / DUR) % phraseLayouts.length;
        const local = (t % DUR) / DUR;
        let cyc = 1;
        if (local < FADE) cyc = local / FADE;
        else if (local > 1 - FADE) cyc = (1 - local) / FADE;
        // gentle sway around the top of the ring — keeps every phrase upright
        // and readable in the upper arc instead of orbiting fully around
        const centerAngle = Math.sin(t * 0.00035) * 0.13;
        const textA = clamp01(1 - sharp * 1.12) * (dark ? 0.72 : 0.64) * cyc;
        drawPhrase(fx, fy, phraseLayouts[idx], centerAngle, textA, dark ? BONE : INK);
      }

      // focus-pull on the wordmark/slogan: blurred until the lens nears it
      if (overlay && !reduce) {
        const maxBlur = isMobile ? 3 : 5.5;
        const blur = (1 - sharp * sharp) * maxBlur;
        overlay.style.setProperty('--hf-intro-blur', blur.toFixed(2) + 'px');
      }

      if (!reduce && running) raf = requestAnimationFrame(frame);
    }

    let raf = null;
    let running = false;
    function start() {
      if (running) return;
      running = true;
      raf = requestAnimationFrame(frame);
    }
    function stop() {
      running = false;
      if (raf) cancelAnimationFrame(raf);
      raf = null;
    }

    // ── Pointer following (desktop) / touch focus (mobile) ──────────────
    function onPointer(e) {
      const sr = section.getBoundingClientRect();
      ptrX = e.clientX - sr.left;
      ptrY = e.clientY - sr.top;
      lastPointer = performance.now();
    }
    function onTouchStart() { touchHeld = true; }
    function onTouchEnd() { touchHeld = false; }
    function onScrollFocus() { lastScroll = performance.now(); }
    if (!reduce) {
      if (isMobile) {
        // On phones the lens parks on the logo while the finger is down and
        // stays sharp through scrolling — instead of chasing the finger.
        section.addEventListener('touchstart', onTouchStart, { passive: true });
        window.addEventListener('touchend', onTouchEnd, { passive: true });
        window.addEventListener('touchcancel', onTouchEnd, { passive: true });
        window.addEventListener('scroll', onScrollFocus, { passive: true });
      } else {
        section.addEventListener('pointermove', onPointer, { passive: true });
        section.addEventListener('pointerdown', onPointer, { passive: true });
      }
    }

    build();
    if (reduce) {
      // static, fully sharp & visible
      if (overlay) overlay.style.setProperty('--hf-intro-blur', '0px');
      frame(0);
    } else {
      start();
    }

    // Re-measure once the mono font is ready so the ring text fits cleanly.
    if (document.fonts && document.fonts.ready) {
      document.fonts.ready.then(() => { build(); if (reduce) frame(0); });
    }

    // Pause when scrolled out of view (saves CPU for the rest of the page)
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (reduce) return;
        if (e.isIntersecting) start(); else stop();
      });
    }, { threshold: 0.01 });
    io.observe(section);

    const onResize = () => { build(); if (reduce) frame(0); };
    window.addEventListener('resize', onResize);
    const onScrollMeasure = () => measureOverlay();
    window.addEventListener('scroll', onScrollMeasure, { passive: true });
    const onVis = () => { if (document.hidden) stop(); else if (!reduce) start(); };
    document.addEventListener('visibilitychange', onVis);

    return () => {
      stop();
      io.disconnect();
      window.removeEventListener('resize', onResize);
      window.removeEventListener('scroll', onScrollMeasure);
      document.removeEventListener('visibilitychange', onVis);
      section.removeEventListener('pointermove', onPointer);
      section.removeEventListener('pointerdown', onPointer);
      section.removeEventListener('touchstart', onTouchStart);
      window.removeEventListener('touchend', onTouchEnd);
      window.removeEventListener('touchcancel', onTouchEnd);
      window.removeEventListener('scroll', onScrollFocus);
    };
  }, [isMobile]);

  // ── Scroll hand-off — pin the cover and dissolve it as the page rises ──
  // The cover sits in a tall sticky track; the page (.hf-page, z-index 2)
  // wipes up over it. We fade the text out first, then dissolve + scale the
  // whole field back so it recedes behind the arriving page — a cinematic
  // crossfade rather than a hard scroll cut.
  React.useEffect(() => {
    const stage = stageRef.current;
    const overlay = overlayRef.current;
    const section = sectionRef.current;
    if (!stage || !overlay || !section) return;
    const reduce = window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduce) return;

    let ticking = false;
    const onScroll = () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        const travel = section.getBoundingClientRect().height || window.innerHeight;
        const y = window.scrollY;
        const p = Math.min(1, y / travel);

        // text leaves first — zoom-through fade
        const tp = Math.min(1, y / (travel * 0.5));
        overlay.style.opacity = String(1 - tp);
        overlay.style.transform = `translateY(${(-tp * 34).toFixed(1)}px) scale(${(1 + 0.06 * tp).toFixed(3)})`;

        // the whole field dissolves a touch later, receding behind the page
        const sp = Math.max(0, (p - 0.12) / 0.88);
        stage.style.opacity = String(1 - sp);
        stage.style.transform = `scale(${(1 - 0.05 * sp).toFixed(3)})`;
        ticking = false;
      });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  // ── Safety net — guarantee the cover content ends visible even if the CSS
  // entrance animations never run (stalled timeline, throttled tab, etc).
  React.useEffect(() => {
    const sec = sectionRef.current;
    if (!sec) return;
    const id = setTimeout(() => sec.classList.add('hf-intro-ready'), 1500);
    return () => clearTimeout(id);
  }, []);

  // ── Theme palette ─────────────────────────────────────────────────────
  const dark = theme === 'dark';
  const pal = dark ? {
    bg: 'radial-gradient(120% 90% at 50% 40%, #1a1714 0%, #141210 45%, #0d0b09 100%)',
    pageBg: '#0f0d0a',
    scrim: 'radial-gradient(50% 42% at 50% 46%, rgba(13,11,9,0.62) 0%, rgba(13,11,9,0) 70%)',
    text: '#f3eee4',
    eyebrow: 'rgba(243,238,228,0.55)',
    sub: 'rgba(243,238,228,0.78)',
    cue: 'rgba(243,238,228,0.6)'
  } : {
    bg: 'radial-gradient(120% 90% at 50% 40%, #faf7f1 0%, #f1ebdf 48%, #e6ded0 100%)',
    pageBg: '#f3eee4',
    scrim: 'radial-gradient(50% 42% at 50% 46%, rgba(243,238,228,0.55) 0%, rgba(243,238,228,0) 70%)',
    text: '#141414',
    eyebrow: 'rgba(20,20,20,0.55)',
    sub: 'rgba(20,20,20,0.7)',
    cue: 'rgba(20,20,20,0.5)'
  };

  const wm = isMobile ? 46 : 92;

  return (
    <section ref={sectionRef} aria-label="HyperFocusOZ" style={{
      position: 'sticky', top: 0, zIndex: 1,
      height: '100svh', minHeight: 520,
      background: pal.pageBg,
      overflow: 'hidden',
      display: 'flex', alignItems: 'center', justifyContent: 'center'
    }}>
      <div ref={stageRef} className="hf-intro-stage" style={{
        position: 'absolute', inset: 0,
        background: pal.bg,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        willChange: 'opacity, transform'
      }}>
        <canvas ref={canvasRef} aria-hidden="true" style={{
          position: 'absolute', inset: 0, display: 'block'
        }} />

        {/* soft scrim behind the wordmark for legibility */}
        <div aria-hidden="true" style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          background: pal.scrim
        }} />

        <div ref={overlayRef} className="hf-intro-overlay" style={{
          position: 'relative', zIndex: 2,
          textAlign: 'center', padding: '0 24px',
          pointerEvents: 'none',
          filter: 'blur(var(--hf-intro-blur, 4px))',
          willChange: 'opacity, transform, filter'
        }}>
          <div className="mono hf-intro-eyebrow" style={{
            fontSize: isMobile ? 11 : 12, letterSpacing: '0.28em', textTransform: 'uppercase',
            color: pal.eyebrow
          }}>
            <span style={{ color: 'var(--ember)' }}>//</span> Business consulting for the AI era
          </div>

          <div className="hf-intro-wordmark" style={{
            marginTop: isMobile ? 18 : 24,
            fontFamily: '"Hanken Grotesk", sans-serif',
            fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.92,
            fontSize: wm, color: pal.text,
            display: 'inline-flex', alignItems: 'baseline'
          }}>
            hyperfocusoz<span style={{
              display: 'inline-block',
              width: wm * 0.165, height: wm * 0.165, borderRadius: '50%',
              background: 'var(--ember)', marginLeft: wm * 0.03,
              transform: `translateY(${wm * -0.02}px)`
            }} />
          </div>

          <div className="hf-intro-sub" style={{
            marginTop: isMobile ? 18 : 22,
            fontSize: isMobile ? 16 : 20, fontWeight: 500,
            color: pal.sub, textWrap: 'balance',
            maxWidth: 560, marginLeft: 'auto', marginRight: 'auto'
          }}>

          </div>
        </div>

        {/* scroll cue */}
        <a href="#start" className="hf-intro-cue" aria-label="Scroll to enter" style={{
          position: 'absolute', left: 0, right: 0, bottom: isMobile ? 22 : 32,
          zIndex: 2, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
          color: pal.cue, textDecoration: 'none'
        }}>
          <span className="mono" style={{ fontSize: 10, letterSpacing: '0.24em', textTransform: 'uppercase' }}>Scroll</span>
          <svg className="hf-intro-chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
            <path d="M3 6l5 5 5-5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </a>
      </div>

      <style>{`
        /* Cover hand-off: the cover is a sticky element inside a tall track;
           the page below (.hf-page) is pulled up over it with z-index, so as
           you scroll the first screen the page wipes up while the cover
           dissolves in place. margin-top == the extra track height, so this
           adds NO net scroll length — the intro still clears in one screen. */
        .hf-cover-track { position: relative; z-index: 0; height: calc(100svh + 100svh); }
        .hf-page {
          position: relative; z-index: 2;
          margin-top: -100svh;
          background: var(--bg);
        }

        .hf-intro-eyebrow { opacity: 0; animation: hf-intro-rise .7s cubic-bezier(.2,.7,.2,1) .15s both; }
        .hf-intro-wordmark { opacity: 0; animation: hf-intro-rise .8s cubic-bezier(.2,.7,.2,1) .3s both; }
        .hf-intro-sub { opacity: 0; animation: hf-intro-rise .7s cubic-bezier(.2,.7,.2,1) .6s both; }
        .hf-intro-cue { opacity: 0; animation: hf-intro-rise .7s ease-out .9s both; }
        .hf-intro-chevron { animation: hf-intro-bob 1.8s ease-in-out infinite; }
        @keyframes hf-intro-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
        @keyframes hf-intro-bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
        /* Safety net: once mounted, force the resting (visible) state so the
           cover never gets stuck on an un-played entrance animation. The
           proximity blur lives on the container (--hf-intro-blur), so we
           leave filter alone here. */
        .hf-intro-ready .hf-intro-eyebrow,
        .hf-intro-ready .hf-intro-wordmark,
        .hf-intro-ready .hf-intro-sub,
        .hf-intro-ready .hf-intro-cue {
          opacity: 1 !important; transform: none !important;
        }
        @media (prefers-reduced-motion: reduce) {
          .hf-intro-eyebrow, .hf-intro-wordmark, .hf-intro-sub, .hf-intro-cue {
            animation: none !important; opacity: 1 !important; transform: none !important;
          }
          .hf-intro-chevron { animation: none !important; }
        }
      `}</style>
    </section>);

}

Object.assign(window, { IntroCover });
