// cinematic.jsx — Apple-product-page-inspired scroll experience
// Uses scroll progress to drive hero typography, sticky pinned moments,
// and gradual project reveals.

const { useState, useEffect, useRef, useMemo } = React;

// ── scroll progress hook ────────────────────────────────────────────────────
// Pushes --p-enter / --p-exit / --p-visible as CSS custom properties on the
// ref'd element on every scroll frame. Does NOT trigger React re-renders —
// callers consume the values via inline `calc(var(--p-enter, 0) * ...)` styles
// or via the optional onChange callback (use sparingly).
function useScrollProgress(ref, onChange) {
  // Stash the latest callback in a ref so we never re-attach scroll listeners
  // when the parent re-renders with a fresh inline function.
  const cbRef = useRef(onChange);
  cbRef.current = onChange;
  useEffect(() => {
    if (!ref.current) return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const el = ref.current;
        if (!el) return;
        const r = el.getBoundingClientRect();
        const vh = window.innerHeight;
        // Skip work when far off-screen — no var to update, no callback to fire.
        if (r.bottom < -vh * 0.5 || r.top > vh * 1.5) return;
        const total = r.height + vh;
        const visible = Math.max(0, Math.min(1, (vh - r.top) / total));
        const enter = Math.max(0, Math.min(1, (vh - r.top) / vh));
        const exit = Math.max(0, Math.min(1, -r.top / Math.max(1, r.height - vh)));
        el.style.setProperty('--p-enter', enter);
        el.style.setProperty('--p-exit', exit);
        el.style.setProperty('--p-visible', visible);
        const cb = cbRef.current;
        if (cb) cb(enter, exit, visible);
      });
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [ref]);
}

// ── reveal-on-enter hook ────────────────────────────────────────────────────
function useReveal(opts = {}) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver(
      (entries) => entries.forEach((e) => {
        if (e.isIntersecting) setShown(true);
        else if (opts.repeat) setShown(false);
      }),
      { threshold: opts.threshold ?? 0.15, rootMargin: opts.rootMargin ?? '0px 0px -10% 0px' }
    );
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return [ref, shown];
}

// ── Hero ────────────────────────────────────────────────────────────────────
function HeroCanvas({ accent }) {
  const canvasRef = useRef(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0;
    let mouse = { x: 0.5, y: 0.5, tx: 0.5, ty: 0.5 };
    let raf = 0;
    let t0 = performance.now();

    // Parse accent oklch to rgb (approximation via setting on a temp el)
    const probe = document.createElement('div');
    probe.style.color = accent;
    document.body.appendChild(probe);
    const computed = getComputedStyle(probe).color;
    document.body.removeChild(probe);
    const rgb = computed.match(/\d+/g)?.slice(0, 3).map(Number) || [120, 200, 255];

    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);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      mouse.tx = (e.clientX - rect.left) / rect.width;
      mouse.ty = (e.clientY - rect.top) / rect.height;
    };
    window.addEventListener('mousemove', onMove);

    // Particles
    const N = 80;
    const particles = Array.from({ length: N }, () => ({
      x: Math.random(), y: Math.random(),
      vx: (Math.random() - 0.5) * 0.0004,
      vy: (Math.random() - 0.5) * 0.0004,
      r: 0.4 + Math.random() * 1.6,
      a: 0.15 + Math.random() * 0.5,
      phase: Math.random() * Math.PI * 2,
    }));

    // Flow field grid for streaks
    const draw = (now) => {
      const t = (now - t0) / 1000;
      // Smoothly chase mouse
      mouse.x += (mouse.tx - mouse.x) * 0.05;
      mouse.y += (mouse.ty - mouse.y) * 0.05;

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

      // Aurora blobs (3) — sine driven
      const blobs = [
        { cx: 0.2 + Math.sin(t * 0.18) * 0.12, cy: 0.3 + Math.cos(t * 0.22) * 0.10, r: 0.55, a: 0.35, color: rgb },
        { cx: 0.85 + Math.cos(t * 0.15) * 0.10, cy: 0.75 + Math.sin(t * 0.17) * 0.08, r: 0.60, a: 0.28, color: [180, 120, 220] },
        { cx: 0.5 + Math.sin(t * 0.12 + mouse.x) * 0.18, cy: 0.55 + Math.cos(t * 0.19 + mouse.y) * 0.12, r: 0.45, a: 0.22, color: rgb },
      ];
      for (const b of blobs) {
        const cx = b.cx * w, cy = b.cy * h;
        const radius = b.r * Math.max(w, h);
        const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
        grad.addColorStop(0, `rgba(${b.color[0]},${b.color[1]},${b.color[2]},${b.a})`);
        grad.addColorStop(1, `rgba(${b.color[0]},${b.color[1]},${b.color[2]},0)`);
        ctx.fillStyle = grad;
        ctx.fillRect(0, 0, w, h);
      }

      // Connection lines between near particles + to mouse
      ctx.lineWidth = 0.5;
      for (let i = 0; i < N; i++) {
        const p = particles[i];
        // Drift
        p.x += p.vx + Math.sin(t * 0.4 + p.phase) * 0.0002;
        p.y += p.vy + Math.cos(t * 0.4 + p.phase) * 0.0002;
        // Mouse attraction
        const dxm = mouse.x - p.x, dym = mouse.y - p.y;
        const dm = Math.hypot(dxm, dym);
        if (dm < 0.28) {
          p.x += dxm * 0.0035;
          p.y += dym * 0.0035;
        }
        // Wrap
        if (p.x < 0) p.x += 1; if (p.x > 1) p.x -= 1;
        if (p.y < 0) p.y += 1; if (p.y > 1) p.y -= 1;
      }

      // Lines (background particle→particle stays subtle)
      for (let i = 0; i < N; i++) {
        const a = particles[i];
        for (let j = i + 1; j < N; j++) {
          const b = particles[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d = Math.hypot(dx, dy);
          if (d < 0.13) {
            const alpha = (1 - d / 0.13) * 0.18;
            ctx.strokeStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;
            ctx.lineWidth = 0.5;
            ctx.beginPath();
            ctx.moveTo(a.x * w, a.y * h);
            ctx.lineTo(b.x * w, b.y * h);
            ctx.stroke();
          }
        }
        // Mouse line — brighter & thicker
        const dxm = mouse.x - a.x, dym = mouse.y - a.y;
        const dm = Math.hypot(dxm, dym);
        if (dm < 0.26) {
          const alpha = (1 - dm / 0.26) * 1.0;
          ctx.strokeStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;
          ctx.lineWidth = 1.4;
          ctx.shadowColor = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.7)`;
          ctx.shadowBlur = 8;
          ctx.beginPath();
          ctx.moveTo(a.x * w, a.y * h);
          ctx.lineTo(mouse.x * w, mouse.y * h);
          ctx.stroke();
          ctx.shadowBlur = 0;
        }
      }

      // Particle dots
      for (const p of particles) {
        const tw = 0.7 + Math.sin(t * 1.2 + p.phase) * 0.3;
        ctx.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${p.a * tw})`;
        ctx.beginPath();
        ctx.arc(p.x * w, p.y * h, p.r, 0, Math.PI * 2);
        ctx.fill();
      }

      // Mouse cursor halo — brighter
      const mx = mouse.x * w, my = mouse.y * h;
      const haloR = 160;
      const haloGrad = ctx.createRadialGradient(mx, my, 0, mx, my, haloR);
      haloGrad.addColorStop(0, `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.40)`);
      haloGrad.addColorStop(0.5, `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.10)`);
      haloGrad.addColorStop(1, `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0)`);
      ctx.fillStyle = haloGrad;
      ctx.beginPath();
      ctx.arc(mx, my, haloR, 0, Math.PI * 2);
      ctx.fill();
      // Bright core dot
      ctx.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.95)`;
      ctx.beginPath();
      ctx.arc(mx, my, 3, 0, Math.PI * 2);
      ctx.fill();

      raf = requestAnimationFrame(draw);
    };

    // Only animate when the canvas is on-screen and the tab is visible.
    // The particle simulation is O(N²) per frame — no point burning CPU
    // when the user has scrolled past the hero.
    let onScreen = true;
    const startLoop = () => { if (!raf) raf = requestAnimationFrame(draw); };
    const stopLoop = () => { if (raf) { cancelAnimationFrame(raf); raf = 0; } };
    const io = new IntersectionObserver(([entry]) => {
      onScreen = entry?.isIntersecting ?? true;
      if (onScreen && !document.hidden) startLoop(); else stopLoop();
    }, { threshold: 0 });
    io.observe(canvas);
    const onVisChange = () => {
      if (document.hidden) stopLoop();
      else if (onScreen) startLoop();
    };
    document.addEventListener('visibilitychange', onVisChange);

    startLoop();

    return () => {
      stopLoop();
      io.disconnect();
      document.removeEventListener('visibilitychange', onVisChange);
      ro.disconnect();
      window.removeEventListener('mousemove', onMove);
    };
  }, [accent]);

  return <canvas ref={canvasRef} className="cine-hero-canvas" />;
}

function CineHero({ data, accent }) {
  const ref = useRef(null);
  useScrollProgress(ref);

  return (
    <section ref={ref} className="cine-hero" data-screen-label="01 Hero">
      <div className="cine-hero-sticky">
        <div className="cine-hero-bg" aria-hidden="true">
          <HeroCanvas accent={accent} />
          <div className="cine-hero-grid" />
          <div className="cine-hero-grain" />
          <div className="cine-hero-vignette" />
        </div>
        <div className="cine-hero-meta">
          <span className="cine-eyebrow">Portfolio · 2026</span>
          <span className="cine-eyebrow cine-eyebrow-end">{data.location}</span>
        </div>
        <div
          className="cine-hero-word"
          style={{
            transform: 'scale(calc(0.85 + var(--p-enter, 0) * 0.15 - var(--p-exit, 0) * 0.15))',
            opacity: 'calc(1 - var(--p-exit, 0) * 1.4)',
            filter: 'blur(calc(var(--p-exit, 0) * 8px))',
          }}
        >
          <span className="cine-hero-name">{data.name}.</span>
          <span className="cine-hero-sub" style={{ color: accent }}>
            {data.longTagline}
          </span>
        </div>
        <div className="cine-hero-foot">
          <span className="cine-mono">↓ Scroll · move cursor</span>
          <span className="cine-mono">{data.available}</span>
        </div>
      </div>
    </section>
  );
}

// ── Manifesto / About ───────────────────────────────────────────────────────
function CineManifesto({ data, accent }) {
  const ref = useRef(null);
  const [revealedCount, setRevealedCount] = useState(0);
  // Reveal words progressively as we scroll IN. Lock at full once reached
  // so reading down doesn't fade tail words. Start fade only on hard exit.
  const text = data.blurb;
  // Split into segments: highlights wrapped in {{ }} stay as one segment
  const segments = useMemo(() => {
    const out = [];
    const re = /\{\{([^}]+)\}\}/g;
    let last = 0; let m;
    while ((m = re.exec(text)) !== null) {
      if (m.index > last) out.push({ text: text.slice(last, m.index), highlight: false });
      out.push({ text: m[1], highlight: true });
      last = m.index + m[0].length;
    }
    if (last < text.length) out.push({ text: text.slice(last), highlight: false });
    return out;
  }, [text]);
  // Tokenize each segment into words/whitespace, preserving highlight flag
  const tokens = useMemo(() => {
    const arr = [];
    for (const seg of segments) {
      const parts = seg.text.split(/(\s+)/);
      for (const p of parts) {
        if (!p) continue;
        arr.push({ text: p, highlight: seg.highlight, isSpace: /^\s+$/.test(p) });
      }
    }
    return arr;
  }, [segments]);
  const totalWords = tokens.filter((t) => !t.isSpace).length;
  // Drive word-by-word reveal off scroll progress, but only re-render when the
  // integer count of revealed words changes — CSS opacity transitions smooth
  // the per-word fade-in. Trims this section's renders from 60fps to ~once
  // per word.
  useScrollProgress(ref, (enter, exit) => {
    const reveal = Math.min(1, enter * 1.4);
    const fade = exit > 0.85 ? (exit - 0.85) / 0.15 : 0;
    const progress = Math.max(0, reveal - fade);
    const count = Math.min(totalWords, Math.floor(progress * totalWords));
    setRevealedCount((prev) => (prev === count ? prev : count));
  });

  return (
    <section ref={ref} className="cine-section cine-manifesto" data-screen-label="02 About">
      <div className="cine-section-inner">
        <div className="cine-eyebrow">About</div>
        <div className="cine-manifesto-grid">
          <div className="cine-avatar-wrap">
            <div className="cine-avatar">
              <img src={data.avatar} alt={data.name} />
              <div className="cine-avatar-glow" style={{ background: `radial-gradient(60% 60% at 50% 50%, ${accent}, transparent)` }} />
            </div>
            <div className="cine-avatar-meta">
              <span className="cine-mono">● Available</span>
              <span className="cine-mono">{data.location}</span>
            </div>
          </div>
          <p className="cine-manifesto-text">
            {(() => {
              let wordIdx = -1;
              return tokens.map((tok, i) => {
                if (tok.isSpace) return <span key={i}>{tok.text}</span>;
                wordIdx++;
                const revealed = wordIdx < revealedCount;
                const baseStyle = {
                  opacity: revealed ? 1 : 0.18,
                  transition: 'opacity .35s, color .35s',
                };
                if (tok.highlight) {
                  return (
                    <span key={i} className="cine-mh" style={{
                      ...baseStyle,
                      color: revealed ? accent : undefined,
                      fontStyle: 'italic',
                    }}>{tok.text}</span>
                  );
                }
                return (
                  <span key={i} style={{
                    ...baseStyle,
                    color: revealed ? 'var(--fg)' : undefined,
                  }}>{tok.text}</span>
                );
              });
            })()}
          </p>
        </div>
        <div className="cine-specs">
          {data.specialties.map((s, i) => (
            <div key={s} className="cine-spec">
              <span className="cine-spec-num" style={{ color: accent }}>0{i + 1}</span>
              <span className="cine-spec-name">{s}</span>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

// ── Projects ────────────────────────────────────────────────────────────────
function CineProjectCard({ p, idx, accent }) {
  const [ref, shown] = useReveal({ threshold: 0.25 });
  const cardRef = useRef(null);

  // Tell embedded demos when the card enters / exits the viewport so heavy
  // assets (video, animations) only run while on-screen. The 200px rootMargin
  // gives the demo a moment to start before the user reaches it.
  useEffect(() => {
    if (!cardRef.current || !p.demoSrc) return;
    let visible = false;
    const post = () => {
      const iframe = cardRef.current?.querySelector('iframe');
      const win = iframe?.contentWindow;
      if (!win) return;
      win.postMessage({ type: visible ? 'cine:visible' : 'cine:hidden' }, '*');
    };
    const iframe = cardRef.current.querySelector('iframe');
    const onLoad = () => post();
    iframe?.addEventListener('load', onLoad);
    const io = new IntersectionObserver((entries) => {
      const next = entries[0]?.isIntersecting ?? false;
      if (next === visible) return;
      visible = next;
      post();
    }, { rootMargin: '200px 0px', threshold: 0 });
    io.observe(cardRef.current);
    return () => {
      io.disconnect();
      iframe?.removeEventListener('load', onLoad);
    };
  }, [p.demoSrc]);

  return (
    <article
      ref={(el) => { ref.current = el; cardRef.current = el; }}
      className={`cine-project ${shown ? 'is-shown' : ''}`}
      data-screen-label={`04.${idx + 1} ${p.name}`}
      style={{ '--reveal-delay': `${idx * 60}ms` }}
    >
      <div className="cine-project-num">
        <span style={{ color: accent }}>{String(idx + 1).padStart(2, '0')}</span>
        <span className="cine-mono cine-project-year">{p.year}</span>
      </div>
      <div
        className="cine-project-visual"
        style={{
          background: `radial-gradient(120% 120% at 30% 20%, oklch(0.42 0.18 ${p.hue}) 0%, oklch(0.22 0.10 ${p.hue}) 45%, var(--bg-2) 100%)`,
        }}
      >
        {p.videoSrc ? (
          <video
            src={p.videoSrc}
            autoPlay
            muted
            loop
            playsInline
            preload="auto"
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              display: 'block',
              zIndex: 1,
              pointerEvents: 'none',
            }}
          />
        ) : p.demoSrc ? (
          <iframe
            src={p.demoSrc}
            title={p.name}
            loading="lazy"
            style={p.demoScale ? {
              position: 'absolute',
              top: 0,
              left: 0,
              width: `${(1 / p.demoScale) * 100}%`,
              height: `${(1 / p.demoScale) * 100}%`,
              border: 'none',
              display: 'block',
              pointerEvents: p.demoInteractive ? 'auto' : 'none',
              transform: `scale(${p.demoScale})`,
              transformOrigin: 'top left',
              ...(p.demoStyle || {}),
            } : {
              position: 'absolute',
              inset: 0,
              width: '100%',
              height: '100%',
              border: 'none',
              display: 'block',
              pointerEvents: p.demoInteractive ? 'auto' : 'none',
              ...(p.demoStyle || {}),
            }}
          />
        ) : (
          <div className="cine-project-visual-inner">
            <span className="cine-mono">[ visual placeholder · {p.name.toLowerCase().replace(/\s/g, '-')} ]</span>
          </div>
        )}
        <div className="cine-project-grain" style={p.demoInteractive ? { pointerEvents: 'none' } : undefined} />
      </div>
      <div className="cine-project-body">
        <div className="cine-project-tag cine-mono">{p.tag} · {p.role}</div>
        <h3 className="cine-project-name">{p.name}</h3>
        <p className="cine-project-summary">{p.summary}</p>
        <ul className="cine-project-stack">
          {p.stack.map((s) => <li key={s}>{s}</li>)}
        </ul>
      </div>
    </article>
  );
}

function CineProjects({ data, accent }) {
  return (
    <section className="cine-section cine-projects" data-screen-label="04 Projects">
      <div className="cine-section-inner">
        <div className="cine-projects-head">
          <div className="cine-eyebrow">Selected Work · {data.projects.length} projects</div>
          <h2 className="cine-h2">Things I've shipped.</h2>
        </div>
        <div className="cine-projects-grid">
          {data.projects.map((p, i) => (
            <CineProjectCard key={p.id} p={p} idx={i} accent={accent} />
          ))}
        </div>
      </div>
    </section>
  );
}

// ── Experience timeline ─────────────────────────────────────────────────────
function CineTimelineRow({ x, accent, kind }) {
  const [ref, shown] = useReveal({ threshold: 0.2, repeat: true });
  return (
    <div ref={ref} className={`cine-timeline-row ${shown ? 'is-shown' : ''}`}>
      <div className="cine-timeline-period cine-mono">{x.period}</div>
      <div className="cine-timeline-body">
        <div className="cine-timeline-role">{kind === 'edu' ? x.degree : x.role}</div>
        <div className="cine-timeline-company" style={{ color: accent }}>{kind === 'edu' ? x.school : x.company}</div>
        <div className="cine-timeline-note">{x.note}</div>
      </div>
    </div>
  );
}
function CineExperience({ data, accent }) {
  const [ref, shown] = useReveal();
  return (
    <section
      ref={ref}
      className={`cine-section cine-exp ${shown ? 'is-shown' : ''}`}
      data-screen-label="05 Experience"
    >
      <div className="cine-section-inner">
        <div className="cine-eyebrow">Experience</div>
        <h2 className="cine-h2">Where I've been.</h2>
        <div className="cine-timeline">
          {data.experience.map((x, i) => (
            <CineTimelineRow key={i} x={x} accent={accent} />
          ))}
        </div>
        <div className="cine-edu">
          <div className="cine-eyebrow">Education</div>
          {data.education.map((e, i) => (
            <CineTimelineRow key={i} x={e} accent={accent} kind="edu" />
          ))}
        </div>
      </div>
    </section>
  );
}

// ── Skills ──────────────────────────────────────────────────────────────────
function CineSkills({ data, accent }) {
  const [ref, shown] = useReveal();
  return (
    <section
      ref={ref}
      className={`cine-section cine-skills ${shown ? 'is-shown' : ''}`}
      data-screen-label="06 Skills"
    >
      <div className="cine-section-inner">
        <div className="cine-eyebrow">Toolkit</div>
        <h2 className="cine-h2">What I work with.</h2>
        <div className="cine-skills-grid">
          {Object.entries(data.skills).map(([cat, items], i) => (
            <div key={cat} className="cine-skill-col" style={{ '--i': i }}>
              <div className="cine-skill-cat" style={{ color: accent }}>{cat}</div>
              <ul className="cine-skill-list">
                {items.map((s) => <li key={s}>{s}</li>)}
              </ul>
            </div>
          ))}
        </div>
        <CineInterests interests={data.interests} accent={accent} />
      </div>
    </section>
  );
}

const INTEREST_GLYPHS = {
  "Gaming": { glyph: "▶", caption: "the joystick generation" },
  "Coding": { glyph: "{ }", caption: "side-projects on weekends" },
  "Photography": { glyph: "◉", caption: "mostly streets & light" },
  "Sourdough Baking": { glyph: "◐", caption: "slow ferments, crusty crumbs" },
  "Coffee": { glyph: "◴", caption: "single-origin pour-overs" },
  "Travel": { glyph: "✈", caption: "always one trip in mind" },
};

function CineInterestCard({ label, accent, idx }) {
  const [ref, shown] = useReveal({ threshold: 0.3 });
  const meta = INTEREST_GLYPHS[label] || { glyph: "•", caption: "" };
  return (
    <div
      ref={ref}
      className={`cine-interest-card ${shown ? 'is-shown' : ''}`}
      style={{ '--i': idx, '--accent': accent }}
    >
      <div className="cine-interest-glyph" style={{ color: accent }}>{meta.glyph}</div>
      <div className="cine-interest-label">{label}</div>
      <div className="cine-interest-caption">{meta.caption}</div>
    </div>
  );
}

function CineInterests({ interests, accent }) {
  return (
    <div className="cine-interests">
      <div className="cine-interests-head">
        <div className="cine-eyebrow">Off the clock</div>
        <div className="cine-interests-sub">Things I keep coming back to.</div>
      </div>
      <div className="cine-interest-grid">
        {interests.map((s, i) => <CineInterestCard key={s} label={s} accent={accent} idx={i} />)}
      </div>
    </div>
  );
}

// ── Contact ─────────────────────────────────────────────────────────────────
function CineContactForm({ accent }) {
  const [form, setForm] = useState({ name: '', email: '', message: '' });
  const [status, setStatus] = useState('idle'); // idle | sending | sent | error
  const [focused, setFocused] = useState(null);
  const submit = async (e) => {
    e.preventDefault();
    if (!form.name || !form.email || !form.message) return;
    setStatus('sending');
    try {
      const res = await fetch('https://formspree.io/f/xjglprpn', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        body: JSON.stringify(form),
      });
      if (!res.ok) throw new Error('submit failed');
      setStatus('sent');
      setTimeout(() => {
        setStatus('idle');
        setForm({ name: '', email: '', message: '' });
      }, 2400);
    } catch {
      setStatus('error');
      setTimeout(() => setStatus('idle'), 3200);
    }
  };
  const Field = ({ id, label, type = 'text', textarea = false }) => {
    const isActive = focused === id || form[id];
    const props = {
      id,
      value: form[id],
      onChange: (e) => setForm({ ...form, [id]: e.target.value }),
      onFocus: () => setFocused(id),
      onBlur: () => setFocused(null),
      required: true,
      className: 'cine-form-input',
    };
    return (
      <div className={`cine-form-field ${isActive ? 'is-active' : ''}`}>
        <label htmlFor={id} style={{ color: isActive ? accent : undefined }}>{label}</label>
        {textarea ? <textarea rows={5} {...props} /> : <input type={type} {...props} />}
        <span className="cine-form-line" style={{ background: accent }} />
      </div>
    );
  };
  return (
    <form className="cine-form" onSubmit={submit}>
      <div className="cine-form-row">
        <Field id="name" label="Name" />
        <Field id="email" label="Email" type="email" />
      </div>
      <Field id="message" label="Message" textarea />
      <button
        type="submit"
        className="cine-form-submit"
        disabled={status !== 'idle'}
        style={{ borderColor: accent, color: status === 'sent' ? accent : undefined }}
      >
        <span>
          {status === 'idle' && 'Send message'}
          {status === 'sending' && 'Sending…'}
          {status === 'sent' && '✓ Message sent'}
          {status === 'error' && '✗ Failed — try again'}
        </span>
        <span className="cine-form-arrow">→</span>
      </button>
    </form>
  );
}

function CineContact({ data, accent }) {
  const ref = useRef(null);
  useScrollProgress(ref);
  return (
    <section
      ref={ref}
      className="cine-section cine-contact"
      data-screen-label="07 Contact"
    >
      <div className="cine-section-inner">
        <div className="cine-eyebrow">Contact</div>
        <h2
          className="cine-contact-h"
          style={{
            transform: 'translateY(calc((1 - min(1, var(--p-enter, 0) * 1.2)) * 40px))',
            opacity: 'min(1, calc(var(--p-enter, 0) * 1.2))',
          }}
        >
          Let's build <span style={{ color: accent, fontStyle: 'italic' }}>something</span>.
        </h2>
        <div className="cine-contact-grid">
          <CineContactForm accent={accent} />
          <div className="cine-contact-links-wrap">
            <div className="cine-eyebrow" style={{ marginBottom: 16 }}>Or reach me at</div>
        <div className="cine-contact-links">
          <a href={`mailto:${data.links.email}`} className="cine-contact-link">
            <span className="cine-mono">01</span>
            <span className="cine-contact-link-label">Email</span>
            <span className="cine-contact-link-val">{data.links.email}</span>
            <span className="cine-contact-link-arrow">→</span>
          </a>
          <a href={data.links.github} target="_blank" rel="noreferrer" className="cine-contact-link">
            <span className="cine-mono">02</span>
            <span className="cine-contact-link-label">GitHub</span>
            <span className="cine-contact-link-val">@sim-yujie</span>
            <span className="cine-contact-link-arrow">→</span>
          </a>
          <a href={data.links.linkedin} target="_blank" rel="noreferrer" className="cine-contact-link">
            <span className="cine-mono">03</span>
            <span className="cine-contact-link-label">LinkedIn</span>
            <span className="cine-contact-link-val">/in/sim-yu-jie</span>
            <span className="cine-contact-link-arrow">→</span>
          </a>
          <button
            type="button"
            className="cine-contact-link cine-contact-link-btn"
            onClick={() => window.print()}
          >
            <span className="cine-mono">04</span>
            <span className="cine-contact-link-label">Resume</span>
            <span className="cine-contact-link-val">Download PDF</span>
            <span className="cine-contact-link-arrow">↓</span>
          </button>
        </div>
          </div>
        </div>
        <div className="cine-foot">
          <span className="cine-mono">© 2026 {data.name}</span>
          <span className="cine-mono">Built from scratch · Singapore</span>
        </div>
      </div>
    </section>
  );
}

// ── Marquee strip (between sections) ───────────────────────────────────────
function CineMarquee({ accent }) {
  const ref = useRef(null);
  useScrollProgress(ref);
  return (
    <div ref={ref} className="cine-marquee">
      <div
        className="cine-marquee-track"
        style={{ transform: 'translateX(calc(-50% - (var(--p-enter, 0) - var(--p-exit, 0)) * 10%))' }}
      >
        {Array.from({ length: 6 }).map((_, i) => (
          <span key={i} className="cine-marquee-item">
            Frontend <span style={{ color: accent }}>·</span> Backend <span style={{ color: accent }}>·</span> DevSecOps <span style={{ color: accent }}>·</span> Cybersecurity <span style={{ color: accent }}>·</span>
          </span>
        ))}
      </div>
    </div>
  );
}

window.Cinematic = { CineHero, CineManifesto, CineProjects, CineExperience, CineSkills, CineContact, CineMarquee };
