// components.jsx — shared bits for Visimatic
// Exports to window: VM (namespace with constants + components)

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

function useMobile(bp = 768) {
  const [mobile, setMobile] = useState(window.innerWidth < bp);
  useEffect(() => {
    const check = () => setMobile(window.innerWidth < bp);
    window.addEventListener("resize", check, { passive: true });
    return () => window.removeEventListener("resize", check);
  }, [bp]);
  return mobile;
}

/* ─── data ──────────────────────────────────────────────────────────── */

const R2 = "https://pub-e01667d961094e52be5860918f659f12.r2.dev";

const SHOWREEL = {
  full: `${R2}/Logo%20Animation%20Clean.mp4`,
  loop: `${R2}/short%20loop%20Logo%20Animation%20Clean%203.mp4`
};

const PORTFOLIO = [
{ id: "embark1", title: "Embark", client: "Embark", style: "Kinetic", year: "2025", src: `${R2}/Embark%20(1).mp4`, aspect: "16/9" },
{ id: "helvetic", title: "Helvetik", client: "Helvetik", style: "Minimal", year: "2025", src: `${R2}/HELVETIC.mp4`, aspect: "16/9" },
{ id: "gokaido", title: "GOkaido", client: "GOkaido", style: "3D", year: "2024", src: `${R2}/GOkaido.mp4`, aspect: "16/9" },
{ id: "ganymede", title: "Ganymede", client: "Ganymede Labs", style: "Minimal", year: "2025", src: `${R2}/Ganymede%20(1).mp4`, aspect: "16/9" },
{ id: "grippi", title: "Grippi Media", client: "Grippi Media", style: "Minimal", year: "2024", src: `${R2}/Grippi%20Media%20%20(1).mp4`, aspect: "16/9" },
{ id: "onpath", title: "On Path Studio", client: "On Path", style: "Kinetic", year: "2024", src: `${R2}/On%20Path%20Studio%202.mp4`, aspect: "16/9" },
{ id: "embark2", title: "Embark — alt", client: "Embark", style: "Kinetic", year: "2024", src: `${R2}/Embark%20(2).mp4`, aspect: "16/9" },
{ id: "ae", title: "AE Basics", client: "Personal", style: "Minimal", year: "2024", src: `${R2}/AE%20Basics.mp4`, aspect: "16/9" }];


const STYLES = [
{ id: "minimal", label: "Minimal & clean", blurb: "Smooth, elegant, premium.", note: "best for SaaS, finance, premium brands" },
{ id: "glitch", label: "Glitch / digital", blurb: "Tech, edgy, futuristic.", note: "best for AI, dev tools, web3" },
{ id: "cartoon", label: "2D cartoon", blurb: "Playful, expressive, fun.", note: "best for kids, creators, mascots" },
{ id: "unsure", label: "Leave it to you", blurb: "Trust the creative direction.", note: "I'll pick what fits your brand" }];


const PACKAGES = [
{ id: "starter", name: "Starter", price: 150, delivery: "5 days", revisions: 1, popular: false,
  includes: ["Simple custom animation", "Custom colours & text", "MP4 export", "Commercial rights"] },
{ id: "standard", name: "Standard", price: 225, delivery: "4 days", revisions: 1, popular: true,
  includes: ["Detailed animation", "Music + sound effects", "Logo transparency (PNG/MOV)", "Everything in Starter"] },
{ id: "pro", name: "Pro", price: 300, delivery: "2 days", revisions: 2, popular: false,
  includes: ["Perfect loop", "Priority 2-day delivery", "2 revisions", "Everything in Standard"] }];


const ADDONS = [
{ id: "transparency", label: "Logo transparency", price: 20, excludes: ["standard", "pro"] },
{ id: "soundfx", label: "Sound effects", price: 25, excludes: ["standard", "pro"] },
{ id: "loop", label: "Perfect loop", price: 30, excludes: ["pro"] },
{ id: "revision", label: "Extra revision", price: 20, excludes: [] },
{ id: "rush", label: "Rush delivery", price: 50, excludes: [] },
{ id: "vectorize", label: "Logo vectorisation", price: 30, excludes: [] },
{ id: "source", label: "Source file (AE)", price: 50, excludes: [] }];


const REVIEWS = [
{ id: 1, name: "Maya C", role: "Founder", rating: 5,
  text: "Came back with a reveal that made our investors stop and rewind. Worth ten times what we paid.",
  delivery: "3 days" },
{ id: 2, name: "Tomás R", role: "Marketing", rating: 5,
  text: "The first cut was the cut. Felt like he'd been inside the brand for a year.",
  delivery: "4 days" },
{ id: 3, name: "Priya A", role: "Creator", rating: 5,
  text: "I have this on the top of every video now. People DM me about the logo, not the content.",
  delivery: "2 days" },
{ id: 4, name: "Jules H", role: "Studio lead", rating: 5,
  text: "Got our brand mark moving in a way our in-house team had been chasing for months.",
  delivery: "3 days" }];


/* ─── prism (logo) ──────────────────────────────────────────────────── */

function Prism({ size = 28, animate = false, style = {} }) {
  return (
    <img
      src="assets/prism.png"
      width={size}
      height={size * 688 / 793}
      alt="Visimatic prism"
      draggable={false}
      style={{
        display: "block",
        filter: "drop-shadow(0 0 24px rgba(138,77,255,0.35))",
        animation: animate ? "prismFloat 7s ease-in-out infinite" : "none",
        ...style
      }} />);


}

/* ─── nav ───────────────────────────────────────────────────────────── */

function Nav({ route, setRoute }) {
  const [scrolled, setScrolled] = useState(false);
  const [open, setOpen] = useState(false);
  const mobile = useMobile();

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 16);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  useEffect(() => { setOpen(false); }, [route]);

  const links = [
    { id: "work",    label: "Work" },
    { id: "pricing", label: "Pricing" },
    { id: "about",   label: "About" },
    { id: "contact", label: "Contact" },
  ];

  const logoBg = scrolled ? "rgba(8,8,16,0.82)" : "rgba(8,8,16,0.52)";

  if (mobile) {
    return (
      <nav style={{
        position: "fixed", top: 16, left: 16, right: 16, zIndex: 100,
        display: "flex", justifyContent: "space-between", alignItems: "flex-start",
        pointerEvents: "none",
      }}>
        {/* logo pill */}
        <div className="glass" style={{
          pointerEvents: "auto",
          display: "flex", alignItems: "center",
          padding: "10px 16px", borderRadius: 999,
          background: logoBg, transition: "background 280ms",
        }}>
          <button onClick={() => setRoute("home")} style={{
            background: "transparent", border: 0, cursor: "pointer",
            display: "flex", alignItems: "center", gap: 8, color: "var(--ink)", padding: 0,
          }}>
            <Prism size={20} />
            <span style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 16, letterSpacing: "-0.02em" }}>
              Visimatic
            </span>
          </button>
        </div>

        {/* hamburger */}
        <button onClick={() => setOpen(o => !o)} style={{
          pointerEvents: "auto",
          width: 44, height: 44, borderRadius: 999,
          background: logoBg,
          border: "1px solid var(--line-2)",
          backdropFilter: "blur(20px)", WebkitBackdropFilter: "blur(20px)",
          display: "flex", alignItems: "center", justifyContent: "center",
          cursor: "pointer", color: "var(--ink)", fontSize: open ? 22 : 18,
          transition: "font-size 180ms",
        }}>
          {open ? "×" : "≡"}
        </button>

        {/* dropdown */}
        {open && (
          <div className="glass" style={{
            position: "absolute", top: 56, left: 0, right: 0,
            padding: "8px",
            borderRadius: "var(--radius)",
            background: "rgba(8,8,16,0.94)",
            backdropFilter: "blur(24px)", WebkitBackdropFilter: "blur(24px)",
            pointerEvents: "auto",
            display: "flex", flexDirection: "column", gap: 2,
          }}>
            {links.map(l => {
              const active = route === l.id;
              return (
                <button key={l.id} onClick={() => setRoute(l.id)} style={{
                  background: active ? "var(--glass-hi)" : "transparent",
                  border: 0, borderRadius: "var(--radius-sm)",
                  color: active ? "var(--ink)" : "var(--ink-2)",
                  fontFamily: "var(--font-sans)", fontSize: 16, fontWeight: 500,
                  padding: "14px 16px", cursor: "pointer", textAlign: "left",
                  transition: "background 200ms, color 200ms",
                }}>
                  {l.label}
                </button>
              );
            })}
            <button onClick={() => setRoute("order")} className="btn btn-primary"
              style={{ justifyContent: "center", marginTop: 4, width: "100%" }}>
              Start a project <span className="arr">→</span>
            </button>
          </div>
        )}
      </nav>
    );
  }

  return (
    <nav style={{
      position: "fixed", top: 16, left: 16, right: 16, zIndex: 100,
      display: "flex", justifyContent: "center", pointerEvents: "none",
    }}>
      <div className="glass" style={{
        pointerEvents: "auto",
        display: "flex", alignItems: "center", gap: 32,
        padding: "10px 14px 10px 18px",
        borderRadius: 999,
        background: scrolled ? "rgba(8,8,16,0.72)" : "rgba(8,8,16,0.42)",
        transition: "background 280ms",
      }}>
        <button onClick={() => setRoute("home")} style={{
          background: "transparent", border: 0, padding: 0, cursor: "pointer",
          display: "flex", alignItems: "center", gap: 10, color: "var(--ink)",
        }}>
          <Prism size={22} />
          <span style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 17, letterSpacing: "-0.02em" }}>
            Visimatic
          </span>
        </button>

        <div style={{ display: "flex", gap: 2 }}>
          {links.map((l) => {
            const active = route === l.id;
            return (
              <button key={l.id} onClick={() => setRoute(l.id)} style={{
                background: active ? "var(--glass-hi)" : "transparent",
                border: 0, color: active ? "var(--ink)" : "var(--ink-2)",
                fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500,
                padding: "8px 12px", borderRadius: 999, cursor: "pointer",
                transition: "background 200ms, color 200ms",
              }}
              onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = "var(--ink)"; }}
              onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = "var(--ink-2)"; }}>
                {l.label}
              </button>
            );
          })}
        </div>

        <button onClick={() => setRoute("order")} className="btn btn-primary"
          style={{ padding: "10px 18px", fontSize: 13 }}>
          Start a project <span className="arr">→</span>
        </button>
      </div>
    </nav>
  );
}

/* ─── footer ────────────────────────────────────────────────────────── */

function Footer({ setRoute }) {
  const mobile = useMobile();
  return (
    <footer style={{ padding: mobile ? "64px 0 32px" : "120px 0 48px", marginTop: mobile ? 40 : 80 }}>
      <div className="wrap-wide">
        <div style={{
          display: "grid",
          gridTemplateColumns: mobile ? "1fr" : "1.4fr 1fr 1fr",
          gap: mobile ? 32 : 48, paddingBottom: mobile ? 32 : 56,
          borderBottom: "1px solid var(--line)"
        }}>
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 18 }}>
              <Prism size={36} animate />
              <span style={{ fontFamily: "var(--font-display)", fontSize: 24, fontWeight: 500 }}>
                Visimatic
              </span>
            </div>
            <p className="serif" style={{
              fontSize: 22, lineHeight: 1.15, color: "var(--ink-2)",
              margin: 0, maxWidth: 360
            }}>
              A small studio, animating one logo at a time.
            </p>
          </div>
          <FooterCol title="Studio" links={[
          { label: "Work", action: () => setRoute("work") },
          { label: "About", action: () => setRoute("about") },
          { label: "Pricing", action: () => setRoute("pricing") },
          { label: "Order", action: () => setRoute("order") }]
          } />
          <FooterCol title="Talk" links={[
          { label: "Contact", action: () => setRoute("contact") },
          { label: "FAQ", action: () => setRoute("faq") },
          { label: "Blog", action: () => setRoute("blog") }]
          } />
        </div>
        <div style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          paddingTop: 32, gap: 16, flexWrap: "wrap",
          flexDirection: mobile ? "column" : "row",
        }}>
          <div className="mono" style={{ fontSize: 11, color: "var(--ink-4)", letterSpacing: "0.06em" }}>
            © 2026 VISIMATIC STUDIO · MADE WITH SUNLIGHT AND BÉZIER CURVES
          </div>
          <div style={{ display: "flex", gap: 20, alignItems: "center" }}>
            <a href="https://www.instagram.com/visimatic_/" target="_blank" rel="noopener noreferrer"
            className="mono"
            style={{
              fontSize: 11, color: "var(--ink-3)", letterSpacing: "0.16em",
              textDecoration: "none", transition: "color 180ms"
            }}
            onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
            onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
              INSTAGRAM
            </a>
            <a href="https://dribbble.com/visimatic" target="_blank" rel="noopener noreferrer"
            className="mono"
            style={{
              fontSize: 11, color: "var(--ink-3)", letterSpacing: "0.16em",
              textDecoration: "none", transition: "color 180ms"
            }}
            onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
            onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
              DRIBBBLE
            </a>
          </div>
        </div>

        {/* giant wordmark */}
        <div style={{
          fontFamily: "var(--font-display)",
          fontSize: "clamp(120px, 22vw, 360px)",
          fontWeight: 500,
          letterSpacing: "-0.06em",
          lineHeight: 0.85,
          marginTop: 64,
          textAlign: "center",
          background: "linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.02) 70%, transparent 100%)",
          WebkitBackgroundClip: "text",
          backgroundClip: "text",
          color: "transparent",
          userSelect: "none"
        }}>
          Visimatic
        </div>
      </div>
    </footer>);

}

function FooterCol({ title, links }) {
  return (
    <div className="col" style={{ gap: 14 }}>
      <div className="eyebrow">{title}</div>
      {links.map((l, i) =>
      <button
        key={i}
        onClick={l.action}
        style={{
          background: "transparent", border: 0, padding: 0,
          color: "var(--ink-2)", fontFamily: "var(--font-sans)",
          fontSize: 15, textAlign: "left", cursor: "pointer",
          transition: "color 180ms"
        }}
        onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
        onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-2)"}>
        
          {l.label}
        </button>
      )}
    </div>);

}

/* ─── video tile (auto-loop preview, hover scrub) ──────────────────── */

function VideoTile({ item, aspect = "16/9", fill = false, onClick, big = false }) {
  const ref = useRef(null);
  const [hover, setHover] = useState(false);

  useEffect(() => {
    const v = ref.current;if (!v) return;
    v.muted = true;v.loop = true;v.playsInline = true;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) v.play().catch(() => {});else v.pause();
    }, { threshold: 0.15 });
    io.observe(v);
    return () => io.disconnect();
  }, []);

  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      onClick={onClick}
      style={{
        position: "relative",
        ...(fill ? { height: "100%" } : { aspectRatio: aspect }),
        borderRadius: big ? "var(--radius-lg)" : "var(--radius)",
        overflow: "hidden",
        border: "1px solid var(--line)",
        background: "var(--void-2)",
        cursor: onClick ? "pointer" : "default",
        transition: "transform 360ms cubic-bezier(.2,.7,.2,1), border-color 240ms",
        transform: hover ? "translateY(-3px)" : "translateY(0)",
        borderColor: hover ? "var(--line-2)" : "var(--line)"
      }}>
      
      <video
        ref={ref}
        src={item.src}
        preload="metadata"
        style={{
          position: "absolute", inset: 0,
          width: "100%", height: "100%",
          objectFit: "cover",
          transform: hover ? "scale(1.04)" : "scale(1.0)",
          transition: "transform 600ms cubic-bezier(.2,.7,.2,1), filter 300ms",
          filter: hover ? "saturate(1.1) brightness(1.05)" : "saturate(1) brightness(1)"
        }} />
      

      {/* chromatic edge wash on hover */}
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: "linear-gradient(180deg, transparent 50%, rgba(0,0,0,0.55) 100%)",
        opacity: hover ? 1 : 0.85,
        transition: "opacity 240ms"
      }} />
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: "radial-gradient(60% 90% at 10% 100%, rgba(138,77,255,0.25), transparent 70%), radial-gradient(60% 90% at 100% 0%, rgba(0,212,255,0.18), transparent 70%)",
        opacity: hover ? 1 : 0,
        transition: "opacity 320ms",
        mixBlendMode: "screen"
      }} />

      {/* meta overlay */}
      <div style={{
        position: "absolute", left: 18, right: 18, bottom: 16,
        display: "flex", justifyContent: "space-between", alignItems: "flex-end",
        gap: 16, color: "white"
      }}>
        <div>
          <div className="eyebrow" style={{ color: "rgba(255,255,255,0.6)", marginBottom: 6 }}>
            {item.client} · {item.year}
          </div>
          <div style={{
            fontFamily: "var(--font-display)",
            fontSize: big ? 32 : 22, fontWeight: 500,
            letterSpacing: "-0.02em", lineHeight: 1
          }}>
            {item.title}
          </div>
        </div>
        <div className="chip" style={{
          background: "rgba(0,0,0,0.4)",
          borderColor: "rgba(255,255,255,0.16)",
          color: "rgba(255,255,255,0.85)"
        }}>
          {item.style}
        </div>
      </div>

      {/* play marker, top-right */}
      <div style={{
        position: "absolute", top: 16, right: 16,
        width: 36, height: 36, borderRadius: "50%",
        display: "flex", alignItems: "center", justifyContent: "center",
        background: "rgba(0,0,0,0.4)",
        border: "1px solid rgba(255,255,255,0.18)",
        backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)",
        transform: hover ? "scale(1.08)" : "scale(1)",
        transition: "transform 240ms"
      }}>
        <div style={{
          width: 6, height: 6, borderRadius: "50%",
          background: "var(--p-amber)",
          boxShadow: "0 0 10px var(--p-amber)",
          animation: "dot 2s ease-in-out infinite"
        }} />
      </div>
    </div>);

}

/* ─── showreel card ────────────────────────────────────────────────── */

function ShowreelCard({ onPlay }) {
  const loopRef = useRef(null);
  const fullRef = useRef(null);
  const [hover, setHover] = useState(false);
  const [playing, setPlaying] = useState(false);
  const mobile = useMobile();

  useEffect(() => {
    const v = loopRef.current;if (!v) return;
    v.muted = true;v.loop = true;v.playsInline = true;
    v.play().catch(() => {});
  }, []);

  const handlePlay = (e) => {
    e.stopPropagation();
    setPlaying(true);
    requestAnimationFrame(() => {
      const f = fullRef.current;
      if (f) { f.muted = false; f.play().catch(() => {}); }
    });
  };

  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      onClick={!playing ? handlePlay : undefined}
      style={{
        position: "relative",
        borderRadius: "var(--radius-lg)",
        overflow: "hidden",
        border: "1px solid var(--line-2)",
        background: "var(--void-2)",
        cursor: playing ? "default" : "pointer",
        aspectRatio: mobile ? "4/3" : "21/9"
      }}>
      
      {!playing && (
        <video
          ref={loopRef}
          src={SHOWREEL.loop}
          preload="auto"
          style={{
            position: "absolute", inset: 0,
            width: "100%", height: "100%",
            objectFit: "cover",
            transform: hover ? "scale(1.03)" : "scale(1)",
            transition: "transform 800ms cubic-bezier(.2,.7,.2,1)"
          }} />
      )}

      {playing && (
        <video
          ref={fullRef}
          src={SHOWREEL.full}
          controls
          autoPlay
          playsInline
          onEnded={() => setPlaying(false)}
          onClick={(e) => e.stopPropagation()}
          style={{
            position: "absolute", inset: 0,
            width: "100%", height: "100%",
            objectFit: "contain",
            background: "black",
            zIndex: 2
          }} />
      )}

      {!playing && (<>
      <div style={{
        position: "absolute", inset: 0,
        background: "linear-gradient(180deg, rgba(6,6,11,0.2) 0%, rgba(6,6,11,0.0) 30%, rgba(6,6,11,0.6) 100%)"
      }} />

      {/* corner ornaments */}
      <CornerMark pos="tl" />
      <CornerMark pos="tr" />
      <CornerMark pos="bl" />
      <CornerMark pos="br" />

      <div style={{
        position: "absolute", left: 32, top: 32,
        display: "flex", gap: 10, alignItems: "center"
      }}>
        <span className="chip" style={{ background: "rgba(0,0,0,0.5)" }}>
          <span className="chip-dot" /> SHOWREEL · 2026
        </span>
      </div>

      <div style={{
        position: "absolute", inset: 0,
        display: "flex", alignItems: "center", justifyContent: "center"
      }}>
        <div style={{
          position: "relative",
          width: hover ? 132 : 116, height: hover ? 132 : 116,
          borderRadius: "50%",
          background: "rgba(255,255,255,0.95)",
          color: "var(--void)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 500,
          letterSpacing: "0.08em", textTransform: "uppercase",
          transition: "width 240ms, height 240ms",
          boxShadow: "0 0 60px rgba(255,255,255,0.4)"
        }}>
          PLAY
          <span style={{
            position: "absolute", inset: -2,
            borderRadius: "50%",
            border: "1px solid rgba(255,255,255,0.5)",
            animation: "pulseRing 2.4s ease-out infinite"
          }} />
          <style>{`@keyframes pulseRing{0%{transform:scale(1);opacity:.8}100%{transform:scale(1.55);opacity:0}}`}</style>
        </div>
      </div>

      <div style={{
        position: "absolute", left: 32, bottom: 28,
        fontFamily: "var(--font-display)", fontSize: 28,
        fontWeight: 500, letterSpacing: "-0.025em",
        color: "white"
      }}>
        Watch the reel — <span className="serif" style={{ color: "rgba(255,255,255,0.8)" }}></span>
      </div>
      <div className="mono" style={{
        position: "absolute", right: 32, bottom: 32,
        fontSize: 11, letterSpacing: "0.18em", color: "rgba(255,255,255,0.7)"
      }}>PROJECTS · AE 25.0

      </div>
      </>)}

      {playing && (
        <button
          onClick={(e) => { e.stopPropagation(); setPlaying(false); }}
          style={{
            position: "absolute", top: 16, right: 16, zIndex: 3,
            width: 36, height: 36, borderRadius: "50%",
            background: "rgba(0,0,0,0.6)", border: "1px solid rgba(255,255,255,0.25)",
            color: "white", cursor: "pointer", fontSize: 16,
            display: "flex", alignItems: "center", justifyContent: "center"
          }}
          aria-label="Close video">×</button>
      )}
    </div>);

}

function CornerMark({ pos }) {
  const map = {
    tl: { top: 16, left: 16, borderTop: "1px solid white", borderLeft: "1px solid white" },
    tr: { top: 16, right: 16, borderTop: "1px solid white", borderRight: "1px solid white" },
    bl: { bottom: 16, left: 16, borderBottom: "1px solid white", borderLeft: "1px solid white" },
    br: { bottom: 16, right: 16, borderBottom: "1px solid white", borderRight: "1px solid white" }
  };
  return (
    <div style={{
      position: "absolute", width: 14, height: 14,
      borderColor: "rgba(255,255,255,0.55)",
      ...map[pos]
    }} />);

}

/* ─── lightbox (full showreel) ─────────────────────────────────────── */

function Lightbox({ src, onClose }) {
  useEffect(() => {
    const k = (e) => e.key === "Escape" && onClose();
    document.addEventListener("keydown", k);
    document.body.style.overflow = "hidden";
    return () => {document.removeEventListener("keydown", k);document.body.style.overflow = "";};
  }, [onClose]);
  return ReactDOM.createPortal(
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 1000,
      background: "rgba(0,0,0,0.92)",
      backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      animation: "fadeIn 240ms ease"
    }}>
      <style>{`@keyframes fadeIn{from{opacity:0}to{opacity:1}}`}</style>
      <video
        src={src}
        autoPlay controls
        style={{ width: "min(92vw, 1280px)", height: "auto", borderRadius: 16 }}
        onClick={(e) => e.stopPropagation()} />

      <button onClick={onClose} style={{
        position: "absolute", top: 24, right: 24,
        width: 44, height: 44, borderRadius: "50%",
        background: "rgba(255,255,255,0.08)", border: "1px solid rgba(255,255,255,0.18)",
        color: "white", cursor: "pointer", fontSize: 18
      }}>×</button>
    </div>,
    document.body
  );

}

/* ─── scroll reveal ────────────────────────────────────────────────── */

function Reveal({ children, delay = 0, y = 28, as = "div", style = {}, ...rest }) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) {setVisible(true);io.disconnect();}
    }, { threshold: 0.08, rootMargin: "0px 0px -40px 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const As = as;
  return (
    <As
      ref={ref}
      style={{
        opacity: visible ? 1 : 0,
        transform: visible ? "translateY(0)" : `translateY(${y}px)`,
        transition: `opacity 800ms cubic-bezier(.2,.7,.2,1) ${delay}ms, transform 800ms cubic-bezier(.2,.7,.2,1) ${delay}ms`,
        ...style
      }}
      {...rest}>
      
      {children}
    </As>);

}

/* ─── marquee ──────────────────────────────────────────────────────── */

function Marquee({ items, gap = 56 }) {
  const content = items.map((it, i) =>
  <div key={i} style={{
    display: "flex", alignItems: "center", gap: 24,
    whiteSpace: "nowrap"
  }}>
      <span style={{
      fontFamily: "var(--font-display)", fontSize: 56, fontWeight: 400,
      letterSpacing: "-0.025em", color: "var(--ink)"
    }}>
        {it}
      </span>
      <span className="serif" style={{ fontSize: 36, color: "var(--ink-3)" }}>✦</span>
    </div>
  );
  return (
    <div className="marquee">
      <div className="marquee__track" style={{ gap }}>
        {content}{content}{content}
      </div>
    </div>);

}

/* ─── accordion ────────────────────────────────────────────────────── */

function Accordion({ items, defaultOpen = -1 }) {
  const [open, setOpen] = useState(defaultOpen);
  const mobile = useMobile();
  return (
    <div style={{
      borderTop: "1px solid var(--line)"
    }}>
      {items.map((it, i) => {
        const isOpen = open === i;
        return (
          <div key={i} style={{
            borderBottom: "1px solid var(--line)"
          }}>
            <button
              onClick={() => setOpen(isOpen ? -1 : i)}
              style={{
                width: "100%",
                background: "transparent", border: 0,
                padding: mobile ? "18px 0" : "26px 0",
                display: "grid",
                gridTemplateColumns: mobile ? "32px 1fr 28px" : "60px 1fr 32px",
                gap: mobile ? 12 : 24, alignItems: "center",
                cursor: "pointer", textAlign: "left",
                color: "var(--ink)"
              }}>
              
              <span className="mono" style={{
                fontSize: 11, letterSpacing: "0.14em",
                color: isOpen ? "var(--p-cyan)" : "var(--ink-3)",
                transition: "color 200ms"
              }}>
                {String(i + 1).padStart(2, "0")}
              </span>
              <span style={{
                fontFamily: "var(--font-display)",
                fontSize: "clamp(20px, 2.2vw, 28px)",
                fontWeight: 500,
                letterSpacing: "-0.02em",
                lineHeight: 1.15,
                color: isOpen ? "var(--ink)" : "var(--ink-2)",
                transition: "color 200ms"
              }}>
                {it.q}
              </span>
              <span style={{
                width: 32, height: 32, borderRadius: "50%",
                border: "1px solid var(--line-2)",
                display: "flex", alignItems: "center", justifyContent: "center",
                transform: isOpen ? "rotate(45deg)" : "rotate(0)",
                transition: "transform 240ms cubic-bezier(.2,.7,.2,1), background 240ms",
                background: isOpen ? "var(--glass-hi)" : "transparent",
                flexShrink: 0
              }}>
                <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
                  <path d="M6 2v8M2 6h8" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
                </svg>
              </span>
            </button>
            <div style={{
              display: "grid",
              gridTemplateRows: isOpen ? "1fr" : "0fr",
              transition: "grid-template-rows 360ms cubic-bezier(.2,.7,.2,1)"
            }}>
              <div style={{ overflow: "hidden" }}>
                <div style={{
                  paddingLeft: mobile ? 20 : 84, paddingRight: mobile ? 0 : 56,
                  paddingBottom: isOpen ? 28 : 0,
                  color: "var(--ink-2)", fontSize: 16, lineHeight: 1.55,
                  maxWidth: 860,
                  transition: "padding 360ms"
                }}>
                  {it.a}
                </div>
              </div>
            </div>
          </div>);

      })}
    </div>);

}

/* ─── export ───────────────────────────────────────────────────────── */
Object.assign(window, {
  VM_R2: R2,
  VM_SHOWREEL: SHOWREEL,
  VM_PORTFOLIO: PORTFOLIO,
  VM_STYLES: STYLES,
  VM_PACKAGES: PACKAGES,
  VM_ADDONS: ADDONS,
  VM_REVIEWS: REVIEWS,
  useMobile,
  Prism, Nav, Footer, VideoTile, ShowreelCard, Lightbox, Reveal, Marquee, Accordion
});