/* global React */
const { useState, useEffect, useRef } = React;

/* ============ DATA ============ */
const PACKAGES_TIERRA = [
{
  id: "lumen",
  name: "LUMEN",
  price: "195,000",
  currency: "CRC",
  tagline: "Despertar creativo · Punto de entrada",
  tier: "LOW",
  line: "TIERRA",
  description: "El primer destello. La musa llega y te muestra la luz. Ideal para creadores que están empezando a darle forma cinematográfica a su marca o evento.",
  includes: [
  "Pre-producción ligera · Brief + moodboard",
  "1 día de rodaje en locación",
  "Edición principal · 1 video master 60-90s",
  "Color grading cinematográfico base",
  "2 rondas de revisión",
  "Entrega en 4K · 14 días hábiles"],

  popular: false
},
{
  id: "anima",
  name: "ÁNIMA",
  price: "380,000",
  currency: "CRC",
  tagline: "Inspiración con alma · Sweet spot",
  tier: "MEDIUM",
  line: "TIERRA",
  description: "Cuando la musa ya no solo ilumina — le da alma a tu obra. El paquete donde la mayoría encuentra su lenguaje cinematográfico. Producción completa, narrativa con respiración propia.",
  includes: [
  "Pre-producción completa · Concepto + storyboard",
  "2 días de rodaje · Multi-locación",
  "Edición narrativa · 1 master + 3 cortes sociales",
  "Color grading premium · LUT propio",
  "Sound design original",
  "3 rondas de revisión",
  "Entrega en 4K · 21 días hábiles"],

  popular: true
},
{
  id: "olimpo",
  name: "OLIMPO",
  price: "780,000",
  currency: "CRC",
  tagline: "La morada de las musas · Premium",
  tier: "HIGH",
  line: "TIERRA",
  description: "El nivel donde la musa habita contigo. Producción integral, equipo completo, dirección creativa total. Para marcas y eventos que necesitan posicionarse al nivel de los grandes.",
  includes: [
  "Dirección creativa total · Concepto + guion",
  "3-5 días de rodaje · Equipo completo",
  "Edición + post extendida · Master + 6 versiones",
  "Color grading premium + VFX ligeros",
  "Score / sound design original a medida",
  "Revisiones ilimitadas dentro de scope",
  "Entrega en 4K/6K · 30 días hábiles"],

  popular: false
}];


const PACKAGES_WORLD = [
{
  id: "wander",
  name: "WANDER",
  price: "750",
  currency: "USD",
  tagline: "The muse who wanders · Entry tier",
  tier: "LOW",
  line: "WORLD",
  description: "The first wandering of the muse. A starting point for international brands or creators ready to build their cinematic identity from the ground up.",
  includes: [
  "Light pre-production · Brief + moodboard",
  "1 day shoot · Single location",
  "1 master cut · 60-90s",
  "Cinematic base grade",
  "2 rounds of revisions",
  "4K delivery · 14 business days"],

  popular: false
},
{
  id: "soul",
  name: "SOUL",
  price: "2,500",
  currency: "USD",
  tagline: "The muse becomes soul · Sweet spot",
  tier: "MEDIUM",
  line: "WORLD",
  description: "Where the muse becomes soul. Most international clients find their language here — fully produced narrative, original sound, the cinematic depth a serious brand demands.",
  includes: [
  "Full pre-production · Concept + storyboard",
  "2-day shoot · Multi-location",
  "Master cut + 3 social cuts",
  "Premium color grade · custom LUT",
  "Original sound design",
  "3 rounds of revisions",
  "4K delivery · 21 business days"],

  popular: false
},
{
  id: "eternal",
  name: "ETERNAL",
  price: "5,500",
  currency: "USD",
  tagline: "The muse turns eternal · Premium",
  tier: "HIGH",
  line: "WORLD",
  description: "The level where the muse turns eternal. End-to-end creative direction, full crew, no compromise. Built for brands that intend to be remembered.",
  includes: [
  "Total creative direction · Concept + script",
  "3-5 day shoot · Full crew",
  "Extended edit · Master + 6 versions",
  "Premium grade + light VFX",
  "Custom score / sound design",
  "Unlimited revisions within scope",
  "4K/6K delivery · 30 business days"],

  popular: false
}];


const PORTFOLIO = [
{
  name: "Daddy Yankee", emWord: "Yankee", kind: "Concierto", year: "2023",
  loc: "San José · CR", frame: "Farewell Tour", format: "6K · ARRI",
  brand: "Cartel de Estrellas · Live Nation",
  role: "Cinematografía multi-cámara · Edit highlights",
  story: "Cobertura del cierre del tour mundial en suelo costarricense. Multi-cam con switching en vivo, drone seguimiento y aftermovie editorial entregado en 72h. La cámara estuvo donde el público gritaba más fuerte.",
  bullets: ["3 cámaras cine A-cam + 2 mobile", "Aftermovie 90s · entrega 72h", "Sound design + mezcla a tiempo", "Master HDR + 4 cortes vertical"],
  link: "https://wa.me/50670470555"
},
{
  name: "Danny Ocean", emWord: "Ocean", kind: "Concierto", year: "2023",
  loc: "San José · CR", frame: "Antídoto", format: "4K · CINE",
  brand: "Ocean Music · Promotor regional",
  role: "Dirección de cámara · Color grading",
  story: "Tour latinoamericano. Captura cinematográfica con ópticas anamórficas, look development definido en pre-producción. Cada show editado como pieza independiente con identidad visual única.",
  bullets: ["Ópticas anamórficas 2.39:1", "Color grading propio · LUT BWAY", "Edit narrativo por canción", "Delivery social + master archivo"],
  link: "https://wa.me/50670470555"
},
{
  name: "Flowfest", emWord: "Flowfest", kind: "Festival", year: "2024",
  loc: "Costa Rica", frame: "Front Row", format: "8K · MULTICAM",
  brand: "Flowfest · Producción festival",
  role: "Aftermovie principal · Highlights por artista",
  story: "El festival de música urbana más grande del país. Cobertura simultánea de 3 escenarios, equipo de 8 operadores. Aftermovie principal de 2 minutos + un highlight personalizado por cada headliner.",
  bullets: ["8 operadores en simultáneo", "8K front + 4K running", "Edit por escenario + master", "Tiempo de entrega: 5 días"],
  link: "https://wa.me/50670470555"
},
{
  name: "Ocaso", emWord: "Ocaso", kind: "Festival", year: "2024",
  loc: "Playa Hermosa", frame: "Golden Hour", format: "4K · ANAMORPHIC",
  brand: "Ocaso Festival · Boutique brand",
  role: "Cinematografía hora dorada · Color editorial",
  story: "Festival boutique de electrónica en playa. Concepto visual construido alrededor de la golden hour. Movimiento de cámara meditativo, color grading saturado tipo film. Lookbook para la edición 2025.",
  bullets: ["Captura golden hour exclusiva", "Look film 35mm emulado", "Gimbal + drone hora azul", "Lookbook + reel principal"],
  link: "https://wa.me/50670470555"
},
{
  name: "Ritvales", emWord: "Ritvales", kind: "Internacional", year: "2024",
  loc: "LATAM Tour", frame: "Archivo", format: "6K · MOBILE",
  brand: "Ritvales Records · Sello LATAM",
  role: "Documental de gira · Behind the scenes",
  story: "Documentación de gira por 6 países. Unidad ligera, formato vertical y horizontal. Material editado en serie de 8 episodios cortos para plataformas más documental largo en pos-producción.",
  bullets: ["6 países · 14 ciudades", "Unidad compacta full-frame", "Serie 8 episodios + largo", "Master para festivales 2025"],
  link: "https://wa.me/50670470555"
}];


/* ============ HOOKS ============ */
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll(".reveal");
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.15, rootMargin: "0px 0px -60px 0px" }
    );
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, []);
}

function useScrolled(threshold = 50) {
  const [s, setS] = useState(false);
  useEffect(() => {
    const onScroll = () => setS(window.scrollY > threshold);
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [threshold]);
  return s;
}

function useScrollY() {
  const [y, setY] = useState(0);
  useEffect(() => {
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        setY(window.scrollY);
        raf = null;
      });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => {
      window.removeEventListener("scroll", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  return y;
}

// Respect users that prefer reduced motion — kills JS-driven motion globally
const REDUCED_MOTION =
  typeof window !== "undefined" &&
  window.matchMedia &&
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// Precision pointer (desktop) — gates cursor-aware depth and smooth scroll
const FINE_POINTER =
  typeof window !== "undefined" &&
  window.matchMedia &&
  window.matchMedia("(pointer: fine)").matches;

// V2 — Lenis inertial scroll. Drives native scroll, so every existing
// rAF/scroll-listener system keeps working untouched. Motion-safe only.
if (!REDUCED_MOTION && FINE_POINTER && typeof Lenis !== "undefined") {
  // Block Lenis from treating hash routes (#/login, #/dashboard/...) as
  // in-page anchors — `#/login` is not a valid querySelector. Intercept the
  // click in capture phase so Lenis never sees it; let the hash change
  // naturally so the hash-router responds.
  document.addEventListener("click", (ev) => {
    const a = ev.target.closest && ev.target.closest("a[href^='#/']");
    if (!a) return;
    ev.stopPropagation();
    window.location.hash = a.getAttribute("href").slice(1);
    ev.preventDefault();
  }, true);
  const lenis = new Lenis({
    duration: 1.9,
    easing: (t) => Math.min(1, 1.001 - Math.pow(2, -12 * t)),
    smoothWheel: true,
    wheelMultiplier: 0.78,
    touchMultiplier: 1.4,
    lerp: 0.065,
    syncTouch: true,
    anchors: { offset: -20 },
  });
  const lenisRaf = (time) => {
    lenis.raf(time);
    requestAnimationFrame(lenisRaf);
  };
  requestAnimationFrame(lenisRaf);
  // Expose so dashboard.jsx can stop/start Lenis when its overlay mounts.
  // Lenis hijacks wheel events globally — if it runs during dashboard the
  // .dash-main scroll appears frozen.
  window.bwayLenis = lenis;

  // ---------------------------------------------------------
  // Global scroll-driven reveal observer.
  // Any element with [data-reveal] (and its children with [data-stagger])
  // animate in when entering the viewport. Drives the "every element has
  // its own scroll animation" feel without rewriting every component.
  // ---------------------------------------------------------
  const seen = new WeakSet();
  const revealObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting && !seen.has(entry.target)) {
        seen.add(entry.target);
        entry.target.classList.add("is-in");
        // Stagger children
        const kids = entry.target.querySelectorAll("[data-stagger]");
        kids.forEach((k, i) => {
          k.style.setProperty("--stagger-i", i);
          requestAnimationFrame(() => k.classList.add("is-in"));
        });
      }
    });
  }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });

  function bindReveals() {
    document.querySelectorAll("[data-reveal]:not(.reveal-bound)").forEach((el) => {
      el.classList.add("reveal-bound");
      revealObserver.observe(el);
    });
  }
  // Auto-tag every direct section so they reveal naturally even without explicit data-reveal.
  function autoTagSections() {
    document.querySelectorAll("section:not([data-reveal]):not(.bn-hero):not(.bway-showroom)").forEach((s) => {
      s.setAttribute("data-reveal", "auto");
    });
  }
  // Run once on mount + observe DOM additions
  setTimeout(() => { autoTagSections(); bindReveals(); }, 100);
  const mutationObserver = new MutationObserver(() => bindReveals());
  mutationObserver.observe(document.body, { childList: true, subtree: true });

  // ---------------------------------------------------------
  // Scroll-velocity-driven body class. Lets CSS react to fast vs slow scroll.
  // ---------------------------------------------------------
  let lastY = window.scrollY;
  let lastT = performance.now();
  let velRaf;
  function tickVel() {
    const y = window.scrollY;
    const t = performance.now();
    const dy = Math.abs(y - lastY);
    const dt = Math.max(1, t - lastT);
    const v = dy / dt;
    document.body.classList.toggle("is-scrolling-fast", v > 1.2);
    lastY = y; lastT = t;
    velRaf = requestAnimationFrame(tickVel);
  }
  velRaf = requestAnimationFrame(tickVel);
}

// Smoothed scroll velocity (px/frame, signed). Drives skew / inertia effects.
function useScrollVelocity() {
  const [v, setV] = useState(0);
  useEffect(() => {
    if (REDUCED_MOTION) return;
    let lastY = window.scrollY;
    let vel = 0;
    let raf;
    let idle = 0;
    const tick = () => {
      const y = window.scrollY;
      const dy = y - lastY;
      lastY = y;
      // low-pass filter → buttery, no jitter
      vel += (dy - vel) * 0.12;
      if (Math.abs(dy) < 0.1) idle++; else idle = 0;
      if (Math.abs(vel) < 0.05 && idle > 30) vel = 0;
      setV(vel);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);
  return v;
}

function useTimecode() {
  const [tc, setTc] = useState("00:00:00:00");
  useEffect(() => {
    const start = Date.now();
    const i = setInterval(() => {
      const t = Math.floor((Date.now() - start) / 1000);
      const h = String(Math.floor(t / 3600)).padStart(2, "0");
      const m = String(Math.floor(t % 3600 / 60)).padStart(2, "0");
      const s = String(t % 60).padStart(2, "0");
      const f = String(Math.floor((Date.now() - start) % 1000 / 41.66)).padStart(2, "0");
      setTc(`${h}:${m}:${s}:${f}`);
    }, 80);
    return () => clearInterval(i);
  }, []);
  return tc;
}

/* ============ COMPONENTS ============ */
function useLoadingProgress(duration = 2700) {
  const [count, setCount] = useState(0);
  const [done, setDone] = useState(false);
  useEffect(() => {
    let raf;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      setCount(Math.floor(t * 100));
      if (t < 1) raf = requestAnimationFrame(tick);
      else setTimeout(() => setDone(true), 400);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [duration]);
  return [count, done];
}

function LoadingScreen({ onComplete }) {
  const [count, done] = useLoadingProgress(2700);
  const words = ["Invocar", "Canalizar", "Eternizar"];
  const [wIdx, setWIdx] = useState(0);
  useEffect(() => {
    const i = setInterval(() => setWIdx((w) => (w + 1) % words.length), 900);
    return () => clearInterval(i);
  }, []);
  useEffect(() => { if (done) onComplete?.(); }, [done, onComplete]);

  return (
    <div className={"loader " + (done ? "loader-out" : "")} aria-hidden={done}>
      <div className="loader-tl">
        <span className="loader-dot"></span>
        <span>BWAY · PROD / EST. 2019</span>
      </div>

      <div className="loader-center">
        <div className="loader-word-stack">
          {words.map((w, i) =>
          <span
            key={i}
            className={"loader-word " + (i === wIdx ? "on" : "")}>

              {w}
            </span>
          )}
        </div>
      </div>

      <div className="loader-br">
        <div className="loader-count">{String(count).padStart(3, "0")}</div>
      </div>

      <div className="loader-bar">
        <div className="loader-bar-fill" style={{ transform: `scaleX(${count / 100})` }}></div>
      </div>

      <div className="loader-meta">
        <span>SAN JOSÉ · COSTA RICA</span>
        <span>RITUAL CINEMATOGRÁFICO</span>
      </div>
    </div>);

}

function Nav() {
  const scrolled = useScrolled(60);
  return (
    <nav className={"nav-min-wrap " + (scrolled ? "nav-min-scrolled" : "")}>
      <div className="nav-min">
        <div className="nav-min-side nav-min-left">
          <a href="#origen" className="nav-min-link">Origen</a>
          <a href="#portfolio" className="nav-min-link">Portafolio</a>
          <a href="#services" className="nav-min-link">Membresías</a>
        </div>
        <a href="#top" className="nav-min-logo" aria-label="BWAY PROD · Inicio">
          <img src="assets/bway-logo.png" alt="BWAY PROD" />
        </a>
        <div className="nav-min-side nav-min-right">
          <a href="#shop" className="nav-min-link">Tienda</a>
          <span className="nav-min-socials-cluster">
            <a href="https://instagram.com/bwayprod" target="_blank" rel="noopener noreferrer" className="nav-min-social" aria-label="Instagram">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                <rect x="3" y="3" width="18" height="18" rx="5"/>
                <circle cx="12" cy="12" r="4"/>
                <circle cx="17.5" cy="6.5" r="0.8" fill="currentColor"/>
              </svg>
            </a>
            <a href="https://facebook.com/bwayprod" target="_blank" rel="noopener noreferrer" className="nav-min-social" aria-label="Facebook">
              <svg viewBox="0 0 24 24" fill="currentColor">
                <path d="M13.5 21v-7h2.4l.35-2.8h-2.75V9.4c0-.8.22-1.34 1.37-1.34h1.46V5.55c-.25-.03-1.12-.1-2.13-.1-2.1 0-3.55 1.28-3.55 3.64v2.11H8.25V14h2.4v7h2.85z"/>
              </svg>
            </a>
          </span>
          <a href="#/login" className="nav-min-link nav-min-login">
            <span className="nav-min-dot" aria-hidden="true"></span>
            Acceder
          </a>
        </div>
      </div>
    </nav>);

}

/* ============ FUTURE LAYER ============ */

// Floating gold dust — canvas particle field with mouse + scroll depth parallax.
// Sits between the bg video and the content, like dust caught in a projector beam.
function GoldDust() {
  const canvasRef = useRef(null);
  useEffect(() => {
    if (REDUCED_MOTION) return;
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0;
    let parts = [];
    let mx = 0.5, my = 0.5;        // raw mouse (0..1)
    let smx = 0.5, smy = 0.5;      // smoothed mouse
    let raf;

    const resize = () => {
      W = window.innerWidth;
      H = window.innerHeight;
      canvas.width = W * dpr;
      canvas.height = H * dpr;
      canvas.style.width = W + "px";
      canvas.style.height = H + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };

    const N = window.innerWidth < 768 ? 28 : 64;
    const seed = () => {
      parts = Array.from({ length: N }, () => {
        const depth = 0.25 + Math.random() * 0.75; // 0.25 far → 1 near
        return {
          x: Math.random(),
          y: Math.random(),
          depth,
          r: 0.6 + depth * 1.8,
          vy: (0.06 + Math.random() * 0.1) * depth,   // slow upward drift
          ph: Math.random() * Math.PI * 2,             // sway phase
          tw: 0.5 + Math.random() * 0.5,               // twinkle
          hue: Math.random() < 0.7 ? "242,161,30" : "212,175,55"
        };
      });
    };

    const onMove = (e) => {
      mx = e.clientX / W;
      my = e.clientY / H;
    };

    let t = 0;
    const draw = () => {
      t += 0.016;
      smx += (mx - smx) * 0.04;
      smy += (my - smy) * 0.04;
      const scroll = window.scrollY;
      ctx.clearRect(0, 0, W, H);
      for (const p of parts) {
        p.y -= p.vy / H;                      // drift up
        if (p.y < -0.05) { p.y = 1.05; p.x = Math.random(); }
        const sway = Math.sin(t * 0.6 + p.ph) * 14 * p.depth;
        // Parallax: near particles react more to mouse + scroll
        const px = p.x * W + sway + (smx - 0.5) * 60 * p.depth;
        const py = (p.y * H + (smy - 0.5) * 40 * p.depth + scroll * 0.04 * p.depth) % (H + 40);
        const alpha = (0.16 + 0.5 * p.depth) * (0.7 + 0.3 * Math.sin(t * p.tw * 2 + p.ph));
        ctx.beginPath();
        ctx.fillStyle = `rgba(${p.hue},${Math.max(0, alpha).toFixed(3)})`;
        ctx.arc(px, py < 0 ? py + H + 40 : py, p.r, 0, Math.PI * 2);
        ctx.fill();
      }
      raf = requestAnimationFrame(draw);
    };

    const onVisibility = () => {
      cancelAnimationFrame(raf);
      if (!document.hidden) raf = requestAnimationFrame(draw);
    };

    resize();
    seed();
    raf = requestAnimationFrame(draw);
    window.addEventListener("resize", resize);
    window.addEventListener("mousemove", onMove, { passive: true });
    document.addEventListener("visibilitychange", onVisibility);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
      window.removeEventListener("mousemove", onMove);
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, []);
  return <canvas ref={canvasRef} className="gold-dust" aria-hidden="true" />;
}

// V2 — Atmosphere: slow-drifting light orbs + occasional diagonal light sweep.
// Pure CSS transforms on a fixed layer; the room's practical lighting.
function Atmosphere() {
  if (REDUCED_MOTION || !FINE_POINTER) return null;
  return (
    <div className="atmosphere" aria-hidden="true">
      <span className="atmo-orb atmo-orb-a" />
      <span className="atmo-orb atmo-orb-b" />
      <span className="atmo-orb atmo-orb-c" />
      <span className="atmo-sweep" />
    </div>);

}

// V2.1 — SceneDivider: cinematic punctuation between scenes.
// Hairlines wipe outward when entering the viewport.
function SceneDivider({ n, title, phrase }) {
  return (
    <div className="scene-div reveal" aria-hidden="true">
      <div className="scene-div-glow"></div>
      <span className="scene-div-line l"></span>
      <span className="scene-div-tag">
        <span className="scene-div-num">ESC&nbsp;·&nbsp;{n}</span>
        <span className="scene-div-title">{title}</span>
      </span>
      <span className="scene-div-line r"></span>
      {phrase && <span className="scene-div-phrase">{phrase}</span>}
    </div>);

}

// Magnetic wrapper — pulls the element toward the cursor, springs back on leave.
function Magnetic({ children, strength = 0.32 }) {
  const ref = useRef(null);
  const onMove = (e) => {
    if (REDUCED_MOTION) return;
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = e.clientX - (r.left + r.width / 2);
    const y = e.clientY - (r.top + r.height / 2);
    el.style.transform = `translate3d(${(x * strength).toFixed(1)}px, ${(y * strength).toFixed(1)}px, 0)`;
  };
  const onLeave = () => {
    const el = ref.current;
    if (el) el.style.transform = "translate3d(0,0,0)";
  };
  return (
    <span className="magnetic" ref={ref} onMouseMove={onMove} onMouseLeave={onLeave}>
      {children}
    </span>);
}

// Giant outlined chapter numeral — echoes the brand manual's editorial "05" pages.
// Drifts opposite to scroll behind each section head.
function ChapterNum({ n, side = "right" }) {
  const [ref, offset] = useParallaxItem(-0.13);
  return (
    <div
      ref={ref}
      className={"chapter-num " + side}
      aria-hidden="true"
      style={{ transform: `translate3d(0, ${offset}px, 0)` }}>
      {n}
    </div>);
}

/* ============ SCROLLYTELLING ============ */

const clamp01 = (v) => Math.max(0, Math.min(1, v));
const seg = (p, a, b) => clamp01((p - a) / (b - a));

// Progress 0→1 through a tall pinned track (sticky stage inside).
function usePinProgress() {
  const trackRef = useRef(null);
  const [p, setP] = useState(0);
  useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    let raf = null;
    const frame = () => {
      raf = null;
      const rect = el.getBoundingClientRect();
      const total = rect.height - (window.innerHeight || 800);
      if (total <= 0) return;
      setP(clamp01(-rect.top / total));
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(frame); };
    frame();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  return [trackRef, p];
}

const ORIGEN_SCENES = [
{
  year: "2019",
  kicker: "ESC 01 · EL PRIMER CLICK",
  title: ["El primer", "click"],
  em: 1,
  copy: "Una cámara prestada y un acto de valentía personal. No nació como negocio — nació como necesidad de contar.",
  tags: ["SAN JOSÉ · CR", "PRIMER ROLLO", "F/1.8"]
},
{
  year: "2023",
  kicker: "ESC 02 · GRANDES ESCENARIOS",
  title: ["Grandes", "escenarios"],
  em: 1,
  copy: "La cámara llegó donde la historia estaba pasando.",
  names: [
  { t: "Daddy Yankee", x: 10, y: 16, d: 0.95 },
  { t: "Danny Ocean", x: 66, y: 12, d: 0.55 },
  { t: "Ozuna", x: 78, y: 66, d: 1 },
  { t: "Solomun", x: 12, y: 74, d: 0.6 },
  { t: "Flowfest", x: 42, y: 22, d: 0.4 },
  { t: "Picnic", x: 60, y: 80, d: 0.8 },
  { t: "Ocaso", x: 26, y: 55, d: 0.35 },
  { t: "Ritvales", x: 84, y: 36, d: 0.68 }]
},
{
  year: "2025",
  kicker: "ESC 03 · SILENCIO ESTRATÉGICO",
  title: ["Silencio", "estratégico"],
  em: 0,
  copy: "Construcción en calidad, no en ruido. El silencio como decisión — afilar el oficio lejos del algoritmo.",
  tags: ["COLOR", "DIRECCIÓN", "CRAFT"]
},
{
  year: "2026",
  kicker: "ESC 04 · FORMALIZACIÓN",
  title: ["El", "movimiento"],
  em: 1,
  copy: "Marca legal en mayo 2026. De una persona a un equipo. BWAY PROD deja de ser un nombre — se vuelve un movimiento.",
  tags: ["EST. 2019 — FORM. 2026"]
}];


// Pinned cinematic chapter — the screen locks and scroll becomes a dolly
// through four scenes of the brand's origin story.
function OrigenStory() {
  const [trackRef, p] = usePinProgress();
  const N = ORIGEN_SCENES.length;

  if (REDUCED_MOTION) {
    // Static fallback: simple vertical timeline
    return (
      <section className="origen-static" id="origen">
        <div className="section-inner">
          <div className="eyebrow">Capítulo 00 · Origen</div>
          {ORIGEN_SCENES.map((s) =>
          <div className="ogs-block" key={s.year}>
              <span className="ogs-year">{s.year}</span>
              <h3>{s.title.join(" ")}</h3>
              <p>{s.copy}</p>
            </div>
          )}
        </div>
      </section>);
  }

  return (
    <section className="origen" id="origen">
      <div className="origen-track" ref={trackRef}>
        <div className="origen-stage">
          {/* Letterbox bars */}
          <div className="og-bar og-bar-top" aria-hidden="true"></div>
          <div className="og-bar og-bar-bottom" aria-hidden="true"></div>

          {/* Chapter header */}
          <div className="og-head">
            <span className="og-head-l"><span className="rec"></span> CAPÍTULO 00 · ORIGEN</span>
            <span className="og-head-r">BWAY · LA HISTORIA</span>
          </div>

          {/* Gold flare sweeping with progress */}
          <div
            className="og-flare"
            aria-hidden="true"
            style={{ transform: `translate3d(${(p - 0.5) * 130}%, 0, 0)` }}>
          </div>

          {/* Progress rail with year ticks */}
          <div className="og-rail" aria-hidden="true">
            <div className="og-rail-fill" style={{ height: p * 100 + "%" }}></div>
            {ORIGEN_SCENES.map((s, i) => {
              const active = p >= i / N && p < (i + 1) / N;
              return (
                <div
                  key={s.year}
                  className={"og-tick" + (active ? " on" : "")}
                  style={{ top: (i + 0.5) / N * 100 + "%" }}>
                  <span className="og-tick-dot"></span>
                  <span className="og-tick-year">{s.year}</span>
                </div>);
            })}
          </div>

          {/* Scenes */}
          {ORIGEN_SCENES.map((s, i) => {
            const a = i / N, b = (i + 1) / N;
            const lp = seg(p, a, b);
            const fadeIn = i === 0 ? 1 : seg(lp, 0, 0.2);
            const fadeOut = i === N - 1 ? 0 : seg(lp, 0.82, 1);
            const op = Math.min(fadeIn, 1 - fadeOut);
            if (op <= 0.001) return null;
            // Dolly push: scene grows as you scroll through it, blurs on exit
            const scale = 0.94 + lp * 0.14;
            const blur = (1 - fadeIn) * 6 + fadeOut * 10;
            const yearY = (0.5 - lp) * 180;          // far layer — slow
            const titleY = (1 - fadeIn) * 70;        // mid layer
            const copyY = (1 - fadeIn) * 120;        // near layer — fast
            return (
              <div
                key={s.year}
                className="og-scene"
                style={{
                  opacity: op,
                  transform: `scale(${scale.toFixed(4)})`,
                  filter: blur > 0.3 ? `blur(${blur.toFixed(1)}px)` : "none"
                }}>

                <div className="og-year" style={{ transform: `translate3d(0, ${yearY}px, 0)` }}>
                  {s.year}
                </div>

                {s.names &&
                <div className="og-names" aria-hidden="true">
                    {s.names.map((nm) =>
                  <span
                    key={nm.t}
                    className="og-name"
                    style={{
                      left: nm.x + "%",
                      top: nm.y + "%",
                      fontSize: 14 + nm.d * 30 + "px",
                      opacity: op * (0.25 + nm.d * 0.7),
                      transform: `translate3d(0, ${(0.5 - lp) * 320 * nm.d}px, 0)`,
                      filter: nm.d < 0.5 ? "blur(1.5px)" : "none"
                    }}>
                        {nm.t}
                      </span>
                  )}
                  </div>
                }

                <div className="og-content">
                  <span className="og-kicker" style={{ transform: `translate3d(0, ${titleY * 0.6}px, 0)` }}>
                    {s.kicker}
                  </span>
                  <h3 className="og-title display" style={{ transform: `translate3d(0, ${titleY}px, 0)` }}>
                    {s.title.map((w, wi) =>
                    <span key={wi} className={wi === s.em ? "em" : ""}>{w} </span>
                    )}
                  </h3>
                  <p className="og-copy" style={{ transform: `translate3d(0, ${copyY}px, 0)` }}>
                    {s.copy}
                  </p>
                  {s.tags &&
                  <div className="og-tags" style={{ transform: `translate3d(0, ${copyY}px, 0)` }}>
                      {s.tags.map((t) => <span key={t}>{t}</span>)}
                    </div>
                  }
                </div>
              </div>);
          })}

          {/* Scroll hint at chapter start */}
          <div className="og-hint" style={{ opacity: Math.max(0, 1 - p * 8) }}>
            <span>SCROLL PARA AVANZAR</span>
            <span className="line"></span>
          </div>
        </div>
      </div>
    </section>);
}

// Pinned horizontal travelling — vertical scroll drives a lateral camera move
// through the six MUSA packages. Desktop only; classic grid elsewhere.
function ServicesCinema({ onOpen }) {
  const [trackRef, p] = usePinProgress();
  const railRef = useRef(null);
  const [dist, setDist] = useState(0);

  useEffect(() => {
    const measure = () => {
      const rail = railRef.current;
      if (!rail) return;
      setDist(Math.max(0, rail.scrollWidth - window.innerWidth));
    };
    measure();
    window.addEventListener("resize", measure);
    // re-measure after fonts/layout settle
    const t = setTimeout(measure, 600);
    return () => { window.removeEventListener("resize", measure); clearTimeout(t); };
  }, []);

  const x = -p * dist;
  const counter = p * dist * 0.16; // intro words drift slower → depth

  return (
    <section className="sc-section" id="services">
      <div
        className="sc-track"
        ref={trackRef}
        style={{ height: `calc(100vh + ${Math.round(dist * 1.25)}px)` }}>

        <div className="sc-stage">
          <div className="sc-rail" ref={railRef} style={{ transform: `translate3d(${x}px, 0, 0)` }}>

            <div className="sc-intro">
              <div className="sc-intro-inner" style={{ transform: `translate3d(${counter}px, 0, 0)` }}>
                <div className="eyebrow">Paquetes MUSA · 06 caminos</div>
                <h2 className="display sc-intro-big">
                  01 / <span className="em">TIERRA</span>
                </h2>
                <p className="sc-intro-sub">Costa Rica · Centroamérica<br />Lumen → Ánima → Olimpo</p>
              </div>
            </div>

            {PACKAGES_TIERRA.map((pk, i) =>
            <div className="sc-item" key={pk.id} style={{ transform: `translate3d(0, ${(i % 2 === 0 ? -1 : 1) * (0.5 - p) * 36}px, 0)` }}>
                <ServiceCard pkg={pk} idx={i} onOpen={onOpen} parallaxOn={false} />
              </div>
            )}

            <div className="sc-intro">
              <div className="sc-intro-inner" style={{ transform: `translate3d(${counter}px, 0, 0)` }}>
                <h2 className="display sc-intro-big">
                  02 / <span className="em">WORLD</span>
                </h2>
                <p className="sc-intro-sub">Internacional · Global<br />Wander → Soul → Eternal</p>
              </div>
            </div>

            {PACKAGES_WORLD.map((pk, i) =>
            <div className="sc-item" key={pk.id} style={{ transform: `translate3d(0, ${(i % 2 === 0 ? 1 : -1) * (0.5 - p) * 36}px, 0)` }}>
                <ServiceCard pkg={pk} idx={i + 3} onOpen={onOpen} parallaxOn={false} />
              </div>
            )}

            <div className="sc-outro">
              <div className="eyebrow">¿Listo?</div>
              <h3 className="display sc-outro-h">Un solo <span className="em">ritual</span>.</h3>
              <Magnetic>
                <a href="#cta" className="btn btn-gold">
                  Cotizar Proyecto <span className="arrow"></span>
                </a>
              </Magnetic>
            </div>
          </div>

          <div className="sc-progress" aria-hidden="true">
            <span className="sc-progress-label">TIERRA</span>
            <div className="sc-progress-track">
              <div className="sc-progress-fill" style={{ transform: `scaleX(${p})` }}></div>
            </div>
            <span className="sc-progress-label">WORLD</span>
          </div>
        </div>
      </div>
    </section>);
}

// Chooses cinema (pinned horizontal) on desktop, classic grid otherwise.
function ServicesSwitch(props) {
  const [wide] = useState(() =>
  typeof window !== "undefined" && window.innerWidth >= 1024 && !REDUCED_MOTION
  );
  return wide ? <ServicesCinema {...props} /> : <Services {...props} />;
}

function PageBgVideo({ tweaks }) {
  const y = useScrollY();
  const videoARef = useRef(null);
  const videoBRef = useRef(null);
  const activeRef = useRef("A");
  const preRollRef = useRef(false);
  const FADE = 2.0; // smooth crossfade window
  const PRE_ROLL = 0.6;

  // Seamless crossfade loop — clean dissolve, no blur or dimming
  useEffect(() => {
    const a = videoARef.current;
    const b = videoBRef.current;
    if (!a || !b) return;
    a.play().catch(() => {});
    let raf;
    const tick = () => {
      const cur = activeRef.current === "A" ? a : b;
      const nxt = activeRef.current === "A" ? b : a;
      if (cur.duration) {
        const remaining = cur.duration - cur.currentTime;
        if (!preRollRef.current && remaining <= FADE + PRE_ROLL) {
          nxt.currentTime = 0;
          nxt.playbackRate = 1;
          nxt.play().catch(() => {});
          preRollRef.current = true;
        }
        if (remaining <= FADE && !nxt.classList.contains("on")) {
          nxt.classList.add("on");
          cur.classList.remove("on");
        }
        if (remaining <= 0.02) {
          activeRef.current = activeRef.current === "A" ? "B" : "A";
          preRollRef.current = false;
        }
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  // Scroll-driven 3D parallax — drives the video across the whole page
  const intensity = tweaks?.parallax ?? 0.4;
  const intMul = intensity / 0.4;
  const docH = typeof document !== "undefined" ?
  document.documentElement.scrollHeight - window.innerHeight :
  1;
  const progress = docH > 0 ? Math.min(1, Math.max(0, y / docH)) : 0;
  const vh = window.innerHeight || 900;
  const heroProgress = Math.min(1, y / (vh * 1.0));

  // Hero: 1 → 1.2 scale, 0 → 6 blur, 1 → 0.55 opacity over first viewport
  // After hero: continue subtle scale 1.2 → 1.35, blur 6 → 10, opacity stays low
  const scale = 1 + heroProgress * 0.20 * intMul + progress * 0.15 * intMul;
  const blur = (heroProgress * 6 + progress * 4) * intMul;
  // Visible in hero, fades to subtle background through page
  const opacity = Math.max(0.18, 1 - heroProgress * 0.55);

  return (
    <>
      <div className="page-bg-base" aria-hidden="true"></div>
      <div
        className="page-bg-video"
        aria-hidden="true"
        style={{
          transform: `scale(${scale})`,
          filter: `blur(${blur}px)`,
          opacity
        }}>

        <video
          ref={videoARef}
          className="page-bg-vid on"
          src="assets/hero-reel-2.mp4"
          poster="assets/hero-poster.png"
          muted
          playsInline
          autoPlay
          preload="auto" />

        <video
          ref={videoBRef}
          className="page-bg-vid"
          src="assets/hero-reel-2.mp4"
          muted
          playsInline
          preload="auto" />

      </div>
      <div className="page-bg-veil" aria-hidden="true"></div>
    </>);

}

function Hero({ tweaks }) {
  const tc = useTimecode();
  const y = useScrollY();
  const heroRef = useRef(null);

  // Cursor-follow glow + scroll pulse
  useEffect(() => {
    const el = heroRef.current;
    if (!el) return;

    let scrollTimeout;
    let lastDir = 0;
    let lastY = window.scrollY;

    const onMove = (e) => {
      const rect = el.getBoundingClientRect();
      const mx = ((e.clientX - rect.left) / rect.width) * 100;
      const my = ((e.clientY - rect.top) / rect.height) * 100;
      el.style.setProperty("--mx", mx + "%");
      el.style.setProperty("--my", my + "%");
      // Normalized -1..1 — drives mouse-depth parallax on title / sub layers
      el.style.setProperty("--hx", (mx / 50 - 1).toFixed(3));
      el.style.setProperty("--hy", (my / 50 - 1).toFixed(3));
      el.classList.add("is-hovering");
    };
    const onLeave = () => {
      el.classList.remove("is-hovering");
      el.style.setProperty("--hx", "0");
      el.style.setProperty("--hy", "0");
    };
    const onScroll = () => {
      const dy = window.scrollY - lastY;
      const dir = Math.sign(dy);
      if (dir !== 0) {
        el.classList.add("is-scrolling");
        el.dataset.scrollDir = dir > 0 ? "down" : "up";
        if (dir !== lastDir) {
          el.classList.remove("is-scrolling");
          // force reflow to restart animation
          void el.offsetWidth;
          el.classList.add("is-scrolling");
          lastDir = dir;
        }
      }
      lastY = window.scrollY;
      clearTimeout(scrollTimeout);
      scrollTimeout = setTimeout(() => {
        el.classList.remove("is-scrolling");
        lastDir = 0;
      }, 700);
    };

    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
      window.removeEventListener("scroll", onScroll);
      clearTimeout(scrollTimeout);
    };
  }, []);

  // Multi-layer parallax: page-bg video handles its own scale/blur,
  // hero just orchestrates content + decor parallax on top.
  const intensity = tweaks?.parallax ?? 0.4;
  const intMul = intensity / 0.4;
  const vh = window.innerHeight || 900;
  const vw = window.innerWidth || 1024;
  const mobileMul = vw < 768 ? 0.5 : vw < 1024 ? 0.7 : 1;
  const M = intMul * mobileMul;
  const px = y;

  // Layer 2 — decor (REC, timecode, corners) drift opposite to scroll
  const decorY = -Math.min(px, vh) * 0.25 * M;

  // Layer 3/4 — content moves faster than scroll
  const contentY = Math.min(px, vh) * 0.30 * M;
  const contentOpacity = Math.max(0, 1 - px / 150);

  // Accent (eyebrow) — fastest layer
  const accentY = Math.min(px, vh) * 0.45 * M;

  // Veil intensifies as you scroll past hero
  const veilOpacity = Math.min(0.95, 0.45 + px / 600);

  return (
    <section className="hero" id="top" ref={heroRef}>
      <div className="hero-bg">
        <div className="hero-veil" style={{ opacity: veilOpacity }}></div>
        <div className="hero-cursor-glow" aria-hidden="true"></div>
        <div className="hero-scan-line" aria-hidden="true"></div>

        <div
          className="hero-cine"
          aria-hidden="true"
          style={{ transform: `translate3d(0, ${decorY * 0.55}px, 0)` }}>
          <div className="hero-rays"></div>
          <div className="hero-rings">
            <i></i><i></i><i></i>
          </div>
          <div className="hero-flare"></div>
        </div>
        <div className="hero-vignette" aria-hidden="true"></div>

        <div className="hero-decor" style={{ transform: `translate3d(0, ${decorY}px, 0)` }}>
          <div className="hero-corners">
            <span className="tl"></span>
            <span className="tr"></span>
            <span className="bl"></span>
            <span className="br"></span>
          </div>
          <div className="hero-meta">
            <span className="rec"></span> REC · BWAY-001 · COSTA RICA
          </div>
          <div className="hero-timecode">TC {tc}</div>
        </div>

        <div className="scroll-cue" style={{ opacity: contentOpacity }}>
          <span>SCROLL</span>
          <span className="line"></span>
        </div>
      </div>

      <div
        className="hero-content"
        style={{ transform: `translate3d(0, ${-contentY}px, 0)`, opacity: contentOpacity }}>

        <div className="eyebrow" style={{ transform: `translate3d(0, ${-accentY}px, 0)` }}>
          PRODUCCIÓN AUDIOVISUAL · EST. 2019
        </div>
        <h1 className="display hero-title" aria-label="El valor no está en lo que dura el evento">
          <span className="hero-line">
            {["El", "valor", "no", "está"].map((w, i) =>
            <span className="hw-clip" key={w + i}>
                <span className="hw" style={{ animationDelay: 120 + i * 75 + "ms" }}>{w}</span>
              </span>
            )}
          </span>
          <br />
          <span className="hero-line">
            {[
            { t: "en" }, { t: "lo" }, { t: "que" },
            { t: "dura", em: true }, { t: "el" }, { t: "evento" }].
            map((w, i) =>
            <span className="hw-clip" key={w.t + i}>
                <span
                className={"hw" + (w.em ? " em" : "")}
                style={{ animationDelay: 420 + i * 75 + "ms" }}>
                  {w.t}
                </span>
              </span>
            )}
          </span>
        </h1>
        <p className="sub hero-sub-depth">Está en lo que queda después.</p>
        <div className="hero-ctas">
          <Magnetic>
            <a href="#portfolio" className="btn btn-gold">
              Ver Portafolio <span className="arrow"></span>
            </a>
          </Magnetic>
          <Magnetic>
            <a href="#cta" className="btn btn-ghost">
              Cotizar Proyecto <span className="arrow"></span>
            </a>
          </Magnetic>
        </div>
      </div>
    </section>);

}

function CountUp({ to, prefix = "", pad = 0, duration = 1800 }) {
  const [val, setVal] = useState(0);
  const [done, setDone] = useState(false);
  const [flash, setFlash] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting && !done) {
            const start = performance.now();
            const animate = (now) => {
              const t = Math.min(1, (now - start) / duration);
              // expoOut for fast feeling at start, with subtle overshoot
              const easeOutExpo = t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
              // small back ease — overshoots target by 4% then settles
              const overshoot = 1.04;
              const back = t < 0.85
                ? easeOutExpo * overshoot
                : overshoot - (t - 0.85) / 0.15 * (overshoot - 1);
              setVal(Math.round(to * back));
              if (t < 1) requestAnimationFrame(animate);
              else {
                setVal(to);
                setDone(true);
                setFlash(true);
                setTimeout(() => setFlash(false), 700);
              }
            };
            requestAnimationFrame(animate);
            io.disconnect();
          }
        });
      },
      { threshold: 0.4 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [to, duration, done]);

  const display = String(val).padStart(pad, "0");
  return (
    <span ref={ref} className={"bn-countup" + (flash ? " is-flash" : "")}>
      {prefix}{display}
    </span>
  );
}

function StatCard({ idx, value, prefix, pad, duration, suffix, label, description, fillTo = 0.7 }) {
  const [seed, setSeed] = useState(0);
  const [pos, setPos] = useState({ x: 50, y: 50 });
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const [hover, setHover] = useState(false);
  const cardRef = useRef(null);

  const onMove = (e) => {
    const el = cardRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width;
    const y = (e.clientY - rect.top) / rect.height;
    setPos({ x: x * 100, y: y * 100 });
    setTilt({ x: (0.5 - y) * 4, y: (x - 0.5) * 6 });
  };

  return (
    <div
      ref={cardRef}
      className={"stat-card " + (hover ? "is-hover" : "")}
      onClick={() => setSeed((s) => s + 1)}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => { setHover(false); setTilt({ x: 0, y: 0 }); }}
      onMouseMove={onMove}
      role="button"
      tabIndex={0}
      style={{
        "--gx": pos.x + "%",
        "--gy": pos.y + "%",
        transform: `perspective(1400px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`
      }}>

      <div className="stat-card-grid" aria-hidden="true"></div>
      <div className="stat-card-glow" aria-hidden="true"></div>
      <div className="stat-card-border" aria-hidden="true"></div>

      <div className="stat-card-top">
        <span className="stat-idx">/ {String(idx).padStart(2, "0")}</span>
        <span className="stat-live">
          <span className="stat-live-dot"></span>
          <span className="stat-live-label">Live</span>
        </span>
      </div>

      <div className="stat-card-num">
        <span className="num">
          {value !== undefined ?
          <CountUp key={seed} to={value} prefix={prefix} pad={pad} duration={duration} /> :
          suffix
          }
        </span>
        <span className="stat-card-arrow" aria-hidden="true">↗</span>
      </div>

      <div className="stat-card-bottom">
        <div className="stat-card-label">{label}</div>
        <div className="stat-card-desc">{description}</div>
        <div className="stat-card-bar" aria-hidden="true">
          <div className="stat-card-bar-fill" style={{ width: (hover ? 1 : fillTo) * 100 + "%" }}></div>
        </div>
      </div>
    </div>);

}

/* ────────── Bento Stats — multi-card layout ───────────────── */
function Sparkle({ size = 12, className = "" }) {
  return (
    <svg
      width={size} height={size} viewBox="0 0 24 24"
      fill="none" stroke="currentColor" strokeWidth="1.4"
      strokeLinecap="round" strokeLinejoin="round"
      className={className}
      aria-hidden="true">

      <path d="M12 2 L13.4 10.6 L22 12 L13.4 13.4 L12 22 L10.6 13.4 L2 12 L10.6 10.6 Z" />
    </svg>);

}

function SectionTag({ children, justify = "center" }) {
  return (
    <div className={"bn-tag bn-tag-" + justify}>
      <Sparkle size={11} />
      <span>{children}</span>
      <Sparkle size={11} />
    </div>);

}

const CINEMA_ICONS = [
{
  k: "aperture",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><path d="M14.31 8 19.18 16.5M11.16 3.02l5.43 9.4M3 12h11.95M3.95 14 9.4 4.5M16.55 21 11.05 11.6M21 12H9.05M19.95 10 14.6 19.4" /></svg>
},
{
  k: "camera",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" /><circle cx="12" cy="13" r="4" /></svg>
},
{
  k: "film",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M7 3v18M17 3v18M3 7.5h4M3 12h4M3 16.5h4M17 7.5h4M17 12h4M17 16.5h4" /></svg>
},
{
  k: "lens",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><circle cx="12" cy="12" r="9" /><circle cx="12" cy="12" r="5" /><circle cx="12" cy="12" r="1.6" fill="currentColor" /></svg>
},
{
  k: "wand",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 4 4 15l5 5L20 9z" /><path d="M14 7l3 3M5 3v4M3 5h4M19 17v4M17 19h4" /></svg>
},
{
  k: "color",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="9" r="6" /><circle cx="15" cy="9" r="6" /><circle cx="12" cy="15" r="6" /></svg>
},
{
  k: "edit",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M14.06 2.94 5 12v7h7l9.06-9.06a2 2 0 0 0 0-2.83l-3.17-3.17a2 2 0 0 0-2.83 0z" /><path d="M13 5l5 5" /></svg>
},
{
  k: "box",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M21 7.5 12 3 3 7.5v9L12 21l9-4.5z" /><path d="M3 7.5 12 12l9-4.5M12 12v9" /></svg>
},
{
  k: "spark",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2 L13.4 10.6 L22 12 L13.4 13.4 L12 22 L10.6 13.4 L2 12 L10.6 10.6 Z" /></svg>
},
{
  k: "mic",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="2" width="6" height="13" rx="3" /><path d="M5 11a7 7 0 0 0 14 0M12 19v3" /></svg>
},
{
  k: "play",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><path d="M10 8.5v7l6-3.5z" /></svg>
},
{
  k: "sun",
  svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><circle cx="12" cy="12" r="4" /><path d="M12 2v3M12 19v3M5 5l2 2M17 17l2 2M2 12h3M19 12h3M5 19l2-2M17 7l2-2" /></svg>
}];


function IconTile({ icon }) {
  return (
    <span className="bn-icon-tile liquid-glass">
      <span className="bn-icon">{icon.svg}</span>
    </span>);

}

const ARC_CARDS = [
{ code: "BWY-001", name: "Daddy Yankee", kind: "Concierto", year: "2023", hue: 32, pattern: "halftone" },
{ code: "BWY-002", name: "Danny Ocean", kind: "Concierto", year: "2023", hue: 18, pattern: "stripes" },
{ code: "BWY-003", name: "Flowfest", kind: "Festival", year: "2024", hue: 48, pattern: "halftone" },
{ code: "BWY-004", name: "MUSA Method", kind: "Ritual", year: "2026", hue: 40, pattern: "beam", center: true },
{ code: "BWY-005", name: "Ocaso", kind: "Festival", year: "2024", hue: 12, pattern: "stripes" },
{ code: "BWY-006", name: "Ritvales", kind: "Internacional", year: "2024", hue: 60, pattern: "halftone" },
{ code: "BWY-007", name: "Studio Ritual", kind: "Behind", year: "2024", hue: 28, pattern: "stripes" }];


function ArcCard({ card, idx, total, hover, setHover, openProgress, sectionProgress = 0 }) {
  const center = (total - 1) / 2;
  const offset = idx - center;
  const absOffset = Math.abs(offset);
  // Faster unfold: cards finish opening in the first 30% of the section's
  // scroll-through, leaving the remaining 70% to drive the rotation loop.
  const perCardOp = Math.max(0, Math.min(1, openProgress * (total + 2) - idx));
  const angleStep = 26;
  const radius = 580;
  // Rotation drifts right then returns — sine wave so cards never flip past 90°.
  // Peak at the section midpoint, back to 0 by the end → cards stay clickable.
  const loopAngle = Math.sin(sectionProgress * Math.PI) * 30;
  const baseAngle = offset * angleStep;
  const finalAngle = (baseAngle * perCardOp) + (loopAngle * perCardOp);
  const finalZ = radius * perCardOp;
  const entryY = (1 - perCardOp) * 30;
  // z-index recomputed from angle so cards closest to the camera stack on top
  const cosNorm = Math.cos(finalAngle * Math.PI / 180);
  const z = Math.round(50 + cosNorm * 30);
  const isHovered = hover === idx;
  return (
    <div
      className={"bn-arc-card " + (card.center ? "is-center " : "") + (isHovered ? "is-hover " : "") + (perCardOp > 0.05 ? "is-open" : "")}
      style={{
        transform: isHovered ?
        `rotateY(${finalAngle}deg) translateZ(${finalZ + 30}px) scale(1.06)` :
        `translate3d(0, ${entryY}px, 0) rotateY(${finalAngle}deg) translateZ(${finalZ}px)`,
        zIndex: isHovered ? 99 : z,
        opacity: perCardOp * 0.85 + 0.15,
        filter: `brightness(${0.6 + cosNorm * 0.4})`,
        "--float-delay": (idx * 0.4).toFixed(2) + "s",
        "--float-amp": (3 + absOffset * 1.2).toFixed(0) + "px"
      }}
      onMouseEnter={() => setHover(idx)}
      onMouseLeave={() => setHover(null)}
      role="button"
      tabIndex={0}>

      <div
        className="bn-arc-card-bg"
        style={{
          background: `radial-gradient(ellipse at 30% 20%, oklch(0.32 0.06 ${card.hue}), oklch(0.07 0.02 ${card.hue}))`
        }}>
      </div>
      {card.pattern === "halftone" && <div className="bn-arc-pattern bn-arc-halftone"></div>}
      {card.pattern === "stripes" && <div className="bn-arc-pattern bn-arc-stripes"></div>}
      {card.center && <div className="bn-arc-beam"></div>}
      <div className="bn-arc-noise"></div>
      <div className="bn-arc-veil"></div>
      <div className="bn-arc-card-shine" aria-hidden="true"></div>

      <div className="bn-arc-top">
        <span className="bn-arc-code">{card.code}</span>
        {card.center && <span className="bn-arc-live">● ACTIVO</span>}
      </div>
      <div className="bn-arc-bottom">
        <div className="bn-arc-name">{card.name}</div>
        <div className="bn-arc-meta">
          <span>{card.kind}</span>
          <span>·</span>
          <span>{card.year}</span>
        </div>
      </div>
    </div>);

}

function Stats() {
  const sectionRef = useRef(null);
  const stageRef = useRef(null);
  const [visible, setVisible] = useState(false);
  const [angle, setAngle] = useState(0);
  const [openCard, setOpenCard] = useState(null);
  const dragRef = useRef({ active: false, startX: 0, startAngle: 0, moved: false });
  const autoRef = useRef({ paused: false, raf: null });
  const n = ARC_CARDS.length;
  const step = 360 / n;

  useEffect(() => {
    const el = sectionRef.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => entries.forEach((e) => e.isIntersecting && setVisible(true)),
      { threshold: 0.15 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  useEffect(() => {
    const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced || !visible) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = now - last;
      last = now;
      if (!autoRef.current.paused && !dragRef.current.active && openCard === null) {
        setAngle((a) => a + dt * 0.008);
      }
      autoRef.current.raf = requestAnimationFrame(tick);
    };
    autoRef.current.raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(autoRef.current.raf);
  }, [visible, openCard]);

  const onPointerDown = (ev) => {
    if (ev.button !== undefined && ev.button !== 0) return;
    dragRef.current = { active: true, startX: ev.clientX, startAngle: angle, moved: false };
    autoRef.current.paused = true;
  };
  const onPointerMove = (ev) => {
    if (!dragRef.current.active) return;
    const dx = ev.clientX - dragRef.current.startX;
    if (Math.abs(dx) > 6) dragRef.current.moved = true;
    if (dragRef.current.moved) setAngle(dragRef.current.startAngle + dx * 0.32);
  };
  const onPointerUp = () => {
    if (!dragRef.current.active) return;
    dragRef.current.active = false;
    autoRef.current.paused = false;
    setTimeout(() => { if (!dragRef.current.active) dragRef.current.moved = false; }, 50);
  };
  const handleCardClick = (card, i) => {
    if (dragRef.current.moved) return;
    setAngle(-step * i);
    autoRef.current.paused = true;
    setOpenCard(card);
  };
  const closeArcModal = () => {
    setOpenCard(null);
    autoRef.current.paused = false;
  };

  return (
    <section className={"bento-stats " + (visible ? "in" : "")} ref={sectionRef}>
      <ChapterNum n="01" />
      <div className="bn-section-inner bn-arc-section">
        <header className="bn-arc-header">
          <span className="bn-arc-eyebrow">
            <Sparkle size={11} />
            <span>Portafolio · 06 años en cámara</span>
            <Sparkle size={11} />
          </span>
          <h2 className="bn-arc-h">
            Cinematografía que <span className="bn-arc-h-em">posiciona marca</span>.
          </h2>
          <p className="bn-arc-p">
            Productora con base en San José, Costa Rica. Eventos, festivales y artistas cubiertos desde 2019. El valor no está en lo que dura el evento — está en lo que queda después.
          </p>
          <a href="#cta" className="bn-cta-glow">
            <span className="bn-cta-glow-inner">
              <span>Hablar con Russell</span>
              <span className="bn-cta-arrow">→</span>
            </span>
          </a>
        </header>

        <div
          className="arc-cyl-stage"
          ref={stageRef}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onPointerCancel={onPointerUp}
          onMouseEnter={() => { autoRef.current.paused = true; }}
          onMouseLeave={() => { autoRef.current.paused = false; }}>

          <div className="arc-cyl-floor"></div>
          <div className="arc-cyl-rays" aria-hidden="true">
            <span></span><span></span><span></span><span></span><span></span>
          </div>
          <div
            className="arc-cyl"
            style={{ transform: `rotateY(${angle}deg)` }}>

            {ARC_CARDS.map((card, i) => (
              <button
                key={card.code}
                type="button"
                className={"arc-cyl-card" + (card.center ? " is-center" : "")}
                onClick={() => handleCardClick(card, i)}
                style={{
                  transform: `rotateY(${step * i}deg) translateZ(420px)`,
                  background: `radial-gradient(ellipse at 30% 20%, oklch(0.32 0.06 ${card.hue}), oklch(0.07 0.02 ${card.hue}))`
                }}>
                <div className="arc-cyl-card-shine" aria-hidden="true"></div>
                <div className="arc-cyl-card-top">
                  <span className="arc-cyl-card-code">{card.code}</span>
                  {card.center && <span className="arc-cyl-card-live">● ACTIVO</span>}
                </div>
                <div className="arc-cyl-card-bottom">
                  <div className="arc-cyl-card-name">{card.name}</div>
                  <div className="arc-cyl-card-meta">
                    <span>{card.kind}</span><span>·</span><span>{card.year}</span>
                  </div>
                </div>
              </button>
            ))}
          </div>
          <div className="arc-cyl-help">← arrastra para rotar · click para abrir →</div>
        </div>

        <div className="bn-arc-features bn-arc-monoliths">
          <div className="bn-monolith" data-idx="01">
            <span className="bn-monolith-tag">/ ESC 01</span>
            <span className="bn-monolith-num">
              <CountUp to={100} prefix="+" duration={1800} />
            </span>
            <span className="bn-monolith-suffix">marcas</span>
            <span className="bn-monolith-label">Atendidas con cámara</span>
            <span className="bn-monolith-desc">Eventos, festivales y artistas desde 2019 — cada marca con su propio look development.</span>
            <span className="bn-monolith-glow" aria-hidden="true"></span>
            <span className="bn-monolith-edge" aria-hidden="true"></span>
          </div>
          <div className="bn-monolith bn-monolith-feature" data-idx="02">
            <span className="bn-monolith-tag">/ ESC 02</span>
            <span className="bn-monolith-num">
              <CountUp to={6} pad={2} duration={1600} />
            </span>
            <span className="bn-monolith-suffix">años</span>
            <span className="bn-monolith-label">De cámara en mano</span>
            <span className="bn-monolith-desc">Formalizada legalmente en mayo 2026. Seis años antes ya estaba el ritual.</span>
            <span className="bn-monolith-glow" aria-hidden="true"></span>
            <span className="bn-monolith-edge" aria-hidden="true"></span>
          </div>
          <div className="bn-monolith" data-idx="03">
            <span className="bn-monolith-tag">/ ESC 03</span>
            <span className="bn-monolith-num bn-monolith-num-letters">
              <span className="bn-mn-l">C</span><span className="bn-mn-l">R</span>
              <span className="bn-mn-arrow">→</span>
              <span className="bn-mn-l">L</span><span className="bn-mn-l">A</span><span className="bn-mn-l">T</span>
            </span>
            <span className="bn-monolith-suffix">global</span>
            <span className="bn-monolith-label">San José · Mundo</span>
            <span className="bn-monolith-desc">Base en Costa Rica con alcance internacional — la musa no tiene pasaporte.</span>
            <span className="bn-monolith-glow" aria-hidden="true"></span>
            <span className="bn-monolith-edge" aria-hidden="true"></span>
          </div>
        </div>
      </div>

      {openCard && (
        <div className="arc-modal-wrap" onClick={closeArcModal} data-lenis-prevent>
          <div className="arc-modal-backdrop" onClick={closeArcModal} />
          <article
            className="arc-modal"
            onClick={(ev) => ev.stopPropagation()}
            style={{
              background: `radial-gradient(ellipse at 20% 10%, oklch(0.32 0.06 ${openCard.hue}), oklch(0.05 0.02 ${openCard.hue})) padding-box`
            }}>
            <button type="button" className="arc-modal-close" onClick={closeArcModal} aria-label="Cerrar">×</button>
            <span className="arc-modal-code">{openCard.code}{openCard.center ? " · ● ACTIVO" : ""}</span>
            <h3 className="arc-modal-name">{openCard.name}</h3>
            <div className="arc-modal-meta">
              <span>{openCard.kind}</span><span className="arc-modal-dot">·</span><span>{openCard.year}</span>
            </div>
            <div className="arc-modal-frame">
              <div className="arc-modal-frame-corner tl"></div>
              <div className="arc-modal-frame-corner tr"></div>
              <div className="arc-modal-frame-corner bl"></div>
              <div className="arc-modal-frame-corner br"></div>
              <span className="arc-modal-frame-label">REEL · BWY</span>
            </div>
            <p className="arc-modal-quote">
              "El valor no está en lo que dura el evento — está en lo que queda después."
            </p>
            <a href="#cta" className="arc-modal-cta" onClick={() => { closeArcModal(); }}>
              Hablar con Russell <span>→</span>
            </a>
          </article>
        </div>
      )}
    </section>);

}

const IMMERSIVE_CLIPS = [
  { code: "BWY-MM26", name: "MUSA Method", tag: "Ritual · 2026", src: "assets/musa-method.mp4" },
  { code: "BWY-Y23",  name: "Daddy Yankee", tag: "Live · 2023",  src: "assets/musa-method.mp4" },
  { code: "BWY-DO23", name: "Danny Ocean",  tag: "Live · 2023",  src: "assets/musa-method.mp4" },
  { code: "BWY-FF24", name: "Flowfest",     tag: "Festival · 2024", src: "assets/musa-method.mp4" },
  { code: "BWY-OC24", name: "Ocaso",        tag: "Festival · 2024", src: "assets/musa-method.mp4" },
  { code: "BWY-RT24", name: "Ritvales",     tag: "Doc · 2024",   src: "assets/musa-method.mp4" }
];

function MusaImmersive() {
  const ref = useRef(null);
  const videoRef = useRef(null);
  const [progress, setProgress] = useState(0);
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const [audio, setAudio] = useState(false);
  const [activeClip, setActiveClip] = useState(0);
  const [autoRotate, setAutoRotate] = useState(true);
  // 0→1 progress of the video itself, driven by scroll
  const [scrubProgress, setScrubProgress] = useState(0);

  // Swap video src when activeClip changes — restarts from frame 0
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const newSrc = IMMERSIVE_CLIPS[activeClip].src;
    v.src = newSrc;
    v.load();
    v.play().catch(() => {});
  }, [activeClip]);

  // Auto-advance: when the current clip ENDS, jump to the next.
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const onEnded = () => {
      if (!autoRotate) return;
      setActiveClip((i) => (i + 1) % IMMERSIVE_CLIPS.length);
    };
    v.addEventListener("ended", onEnded);
    return () => v.removeEventListener("ended", onEnded);
  }, [autoRotate]);

  // When autoRotate is true, loop OFF so "ended" fires
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    v.loop = !autoRotate;
  }, [autoRotate]);

  const selectClip = (i) => {
    setAutoRotate(false);
    setActiveClip(i);
  };

  const toggleAudio = (e) => {
    e.stopPropagation();
    const v = videoRef.current;
    if (!v) return;
    const next = !audio;
    v.muted = !next;
    v.volume = 1;
    if (next) {
      // Ensure playback continues (some browsers pause on user gesture)
      const p = v.play();
      if (p && p.catch) p.catch(() => {});
    }
    setAudio(next);
  };

  // V6 — video plays continuously (looped) instead of scroll-scrub.
  // Track natural playback progress for the scrub indicator.
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    v.play().catch(() => {});
    const onTime = () => {
      if (v.duration && Number.isFinite(v.duration)) {
        setScrubProgress(v.currentTime / v.duration);
      }
    };
    v.addEventListener("timeupdate", onTime);
    return () => v.removeEventListener("timeupdate", onTime);
  }, []);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const center = rect.top + rect.height / 2;
      // -1 (below viewport) → 0 (centered) → 1 (above viewport)
      const p = (vh / 2 - center) / (vh * 0.8);
      setProgress(Math.max(-1, Math.min(1, p)));
    };
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        update();
        raf = null;
      });
    };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  // Mouse-based 3D tilt
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const rect = el.getBoundingClientRect();
      const x = (e.clientX - rect.left) / rect.width - 0.5;
      const y = (e.clientY - rect.top) / rect.height - 0.5;
      setTilt({ x: -y * 6, y: x * 8 });
    };
    const onLeave = () => setTilt({ x: 0, y: 0 });
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
    };
  }, []);

  // Scroll-driven 3D parallax: deep Z-axis emergence + tilt
  const scrollRotX = -progress * 14;
  const scrollY = -progress * 100;
  // translateZ goes from -300 (far back) → 0 (centered) → -300 (recedes again)
  const scrollZ = -300 + (1 - Math.abs(progress)) * 300;
  const scale = 0.85 + (1 - Math.abs(progress)) * 0.15;
  const finalRotX = scrollRotX + tilt.x;
  const finalRotY = tilt.y;

  return (
    <div className="musa-immersive" ref={ref}>
      <div className="musa-immersive-particles" aria-hidden="true">
        <span className="mip mip-1"></span>
        <span className="mip mip-2"></span>
        <span className="mip mip-3"></span>
      </div>
      <div className="musa-immersive-stage">
        <div
          className="musa-immersive-shadow"
          style={{
            transform: `translateY(${scrollY + 40}px) scale(${scale * 0.95})`,
            opacity: 0.4 + (1 - Math.abs(progress)) * 0.4
          }}>
        </div>
        <div
          className="musa-immersive-card"
          style={{
            transform: `perspective(1800px) translateZ(${scrollZ}px) rotateX(${finalRotX}deg) rotateY(${finalRotY}deg) translateY(${scrollY}px) scale(${scale})`
          }}>

          <video
            ref={videoRef}
            className="musa-immersive-video"
            src="assets/musa-method.mp4"
            autoPlay
            muted={!audio}
            preload="auto"
            playsInline />



          <div className="musa-immersive-veil"></div>
          <div className="musa-immersive-frame">
            <span className="ift tl"></span>
            <span className="ift tr"></span>
            <span className="ift bl"></span>
            <span className="ift br"></span>
          </div>

          <div className="musa-immersive-meta tl">
            <span className="dot"></span>
            <span>RITUAL · CH 01</span>
          </div>
          <div className="musa-immersive-meta tr">
            <span>LA MUSA SE INVOCA</span>
          </div>

          <button
            type="button"
            className={"musa-immersive-audio " + (audio ? "is-on" : "")}
            onClick={toggleAudio}
            aria-label={audio ? "Silenciar audio" : "Activar audio"}>

            <span className="audio-icon">
              {audio ?
              <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
                  <path d="M2 5h3l4-3v12L5 11H2V5z" />
                  <path d="M11 4.5c1.2.9 2 2.4 2 3.9 0 1.5-.8 3-2 3.9" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
                  <path d="M13.5 2.5c2 1.5 3 4 3 6.5s-1 5-3 6.5" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" opacity="0.6" />
                </svg> :

              <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
                  <path d="M2 5h3l4-3v12L5 11H2V5z" />
                  <line x1="11" y1="5" x2="15" y2="11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
                  <line x1="15" y1="5" x2="11" y2="11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
                </svg>
              }
            </span>
            <span className="audio-label">
              <span className="audio-bars" aria-hidden="true">
                <span></span><span></span><span></span><span></span>
              </span>
              {audio ? "Audio · ON" : "Audio · OFF"}
            </span>
          </button>

          <div className="musa-immersive-caption">
            <span className="cap-em">M U S A</span>
            <span className="cap-line"></span>
            <span className="cap-sub">canalizar — invocar — eternizar</span>
          </div>
          <div className="musa-immersive-meta br">
            <span>TC 00:01:24:12</span>
          </div>
          <div className="musa-immersive-scrub" aria-hidden="true">
            <span className="scrub-label">LIVE</span>
            <span className="scrub-track"><i style={{ transform: `scaleX(${scrubProgress})` }}></i></span>
            <span className="scrub-pct">{String(Math.round(scrubProgress * 100)).padStart(2, "0")}%</span>
          </div>
        </div>
      </div>
      <div className="musa-folder" aria-label="Archivo de clips">
        <div className="musa-folder-head">
          <span className="musa-folder-tag">FOLDER · /BWAY/IMMERSIVE/</span>
          <span className="musa-folder-count">
            {String(activeClip + 1).padStart(2, "0")} / {String(IMMERSIVE_CLIPS.length).padStart(2, "0")}
            {autoRotate ? " · AUTO" : " · MANUAL"}
          </span>
        </div>
        <div className="musa-folder-strip">
          {IMMERSIVE_CLIPS.map((c, i) => (
            <button
              key={c.code}
              className={"musa-folder-clip" + (i === activeClip ? " is-active" : "")}
              type="button"
              onClick={() => selectClip(i)}
              aria-label={`Reproducir ${c.name}`}
            >
              <span className="musa-folder-clip-fill" style={{ "--hue": (20 + i * 14) + "deg" }}>
                <span className="musa-folder-clip-play" aria-hidden="true">▶</span>
              </span>
              <span className="musa-folder-clip-meta">
                <span className="musa-folder-clip-code">{c.code}</span>
                <span className="musa-folder-clip-name">{c.name}</span>
                <span className="musa-folder-clip-tag">{c.tag}</span>
              </span>
            </button>
          ))}
        </div>
      </div>
    </div>);

}

const MARQUEE_TILES = [
  { code: "BWY-001", name: "Daddy Yankee", kind: "Concierto", year: "2023", hue: 36 },
  { code: "BWY-002", name: "Danny Ocean", kind: "Concierto", year: "2023", hue: 18 },
  { code: "BWY-003", name: "Flowfest", kind: "Festival", year: "2024", hue: 48 },
  { code: "BWY-004", name: "Ocaso", kind: "Festival", year: "2024", hue: 12 },
  { code: "BWY-005", name: "Ritvales", kind: "Internacional", year: "2024", hue: 60 },
  { code: "BWY-006", name: "Studio Ritual", kind: "Behind", year: "2024", hue: 28 },
  { code: "BWY-007", name: "Sound Stage", kind: "Test", year: "2025", hue: 44 },
  { code: "BWY-008", name: "Cámara Negra", kind: "Color", year: "2025", hue: 8 },
  { code: "BWY-009", name: "Ánima", kind: "Branded", year: "2025", hue: 52 },
  { code: "BWY-010", name: "Olimpo", kind: "Doc", year: "2025", hue: 24 },
  { code: "BWY-011", name: "Lumen Cuts", kind: "Reel", year: "2026", hue: 40 }
];

function MarqueeTile({ tile }) {
  return (
    <div className="mq-tile">
      <div
        className="mq-tile-bg"
        style={{
          background: `radial-gradient(ellipse at 30% 20%, oklch(0.22 0.06 ${tile.hue}), oklch(0.06 0.02 ${tile.hue}))`
        }}>
      </div>
      <div className="mq-tile-stripes"></div>
      <div className="mq-tile-grain"></div>
      <div className="mq-tile-corners">
        <span></span><span></span><span></span><span></span>
      </div>
      <div className="mq-tile-top">
        <span className="mq-code">{tile.code}</span>
        <span className="mq-rec"><span className="mq-rec-dot"></span>REC</span>
      </div>
      <div className="mq-tile-bottom">
        <div className="mq-name">
          {tile.name}
        </div>
        <div className="mq-kind">{tile.kind} · {tile.year}</div>
      </div>
    </div>);

}

function MusaMarquee() {
  const sectionRef = useRef(null);
  const [offset, setOffset] = useState(0);
  const vel = useScrollVelocity();
  // Velocity → skew: fast scroll bends the film strip, settles back on stop
  const skew = Math.max(-7, Math.min(7, vel * 0.35));

  useEffect(() => {
    const el = sectionRef.current;
    if (!el) return;
    let raf;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      // when section top hits bottom of viewport: progress 0
      // when section bottom hits top of viewport: progress 1
      const total = rect.height + vh;
      const p = (vh - rect.top) / total;
      const clamped = Math.max(0, Math.min(1, p));
      setOffset(clamped);
    };
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { update(); raf = null; });
    };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const row1 = MARQUEE_TILES.slice(0, 6);
  const row2 = MARQUEE_TILES.slice(5);

  // Translation: tiles are tiled 3x, move ~33% of full track based on offset
  const tileW = 360; // approx tile + gap, used for translate
  const trackW1 = row1.length * tileW;
  const trackW2 = row2.length * tileW;
  const t1 = -trackW1 + offset * trackW1 * 1.6;
  const t2 = -offset * trackW2 * 1.6;

  return (
    <div className="musa-marquee" ref={sectionRef}>
      <div className="mq-row mq-row-right">
        <div className="mq-track" style={{ transform: `translate3d(${t1}px, 0, 0) skewX(${-skew}deg)` }}>
          {[...row1, ...row1, ...row1].map((t, i) =>
          <MarqueeTile key={"r1-" + i} tile={t} />
          )}
        </div>
      </div>
      <div className="mq-row mq-row-left">
        <div className="mq-track" style={{ transform: `translate3d(${t2}px, 0, 0) skewX(${skew}deg)` }}>
          {[...row2, ...row2, ...row2].map((t, i) =>
          <MarqueeTile key={"r2-" + i} tile={t} />
          )}
        </div>
      </div>
      <div className="mq-edge mq-edge-l"></div>
      <div className="mq-edge mq-edge-r"></div>
    </div>);

}

function Pillar({ pillar, idx, delay }) {
  const [ref, offset] = useParallaxItem(idx % 2 === 0 ? 0.05 : 0.09);
  const cardRef = useRef(null);
  const [tilt, setTilt] = useState({ x: 0, y: 0, px: 50, py: 50 });

  const onMove = (e) => {
    const el = cardRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width;
    const y = (e.clientY - rect.top) / rect.height;
    setTilt({
      x: (0.5 - y) * 6,
      y: (x - 0.5) * 8,
      px: x * 100,
      py: y * 100
    });
  };
  const onLeave = () => setTilt({ x: 0, y: 0, px: 50, py: 50 });

  return (
    <div
      ref={ref}
      className="pillar-wrap"
      style={{ transform: `translate3d(0, ${offset}px, 0)`, animationDelay: delay + "ms" }}>

      <div
        className="pillar-glow"
        style={{ background: pillar.gradient }}>
      </div>
      <article
        ref={cardRef}
        className="pillar-card"
        onMouseMove={onMove}
        onMouseLeave={onLeave}
        style={{
          background: `linear-gradient(#1A1A1C, #1A1A1C) padding-box, ${pillar.gradient} border-box`,
          transform: `perspective(1200px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
          "--gx": tilt.px + "%",
          "--gy": tilt.py + "%"
        }}>

        <div className="pillar-shimmer" aria-hidden="true"></div>
        <div className="pillar-cursor" aria-hidden="true"></div>

        <div className="pillar-top">
          <div className="pillar-icon">{pillar.icon}</div>
          <span className="pillar-idx">
            <span className="pillar-step">{pillar.step || String(idx + 1).padStart(2, "0")}</span>
            <span className="pillar-sep">/</span>
            <span className="pillar-total">03</span>
          </span>
        </div>

        <div className="pillar-bottom">
          <span className="pillar-process">{pillar.process || ""}</span>
          <h3 className="pillar-title">{pillar.title}</h3>
          <p className="pillar-desc">{pillar.description}</p>
          {pillar.detail && (
            <ul className="pillar-steps">
              {pillar.detail.map((d, i) => (
                <li key={i} style={{ "--i": i }}>
                  <span className="pillar-step-num">{String(i + 1).padStart(2, "0")}</span>
                  <span>{d}</span>
                </li>
              ))}
            </ul>
          )}
        </div>
      </article>
    </div>);

}

const PILLARS_DATA = [
  {
    title: "Captura",
    step: "01",
    process: "Pre-light",
    description: "Cámara, óptica e iluminación calibradas antes de rodar. Cada locación se lee como un set: bloqueos, dirección de luz, lentes elegidos para esa historia, no para el inventario.",
    detail: [
      "Pre-producción · scouting + lookboard",
      "Cuerpos full-frame · ópticas anamórficas",
      "Sonido directo + monitor calibrado"
    ],
    gradient: "linear-gradient(137deg, #F0A500 0%, #FFD66B 45%, #C97B3C 100%)"
  },
  {
    title: "Color",
    step: "02",
    process: "Grading",
    description: "Color grading propio y LUTs a medida. La paleta es lenguaje: define el clima emocional, sostiene la marca y deja la firma BWAY en cada frame.",
    detail: [
      "Conform · normalización ACES",
      "Look development por proyecto",
      "Master HDR + delivery por plataforma"
    ],
    gradient: "linear-gradient(137deg, #E9C46A 0%, #F0A500 45%, #8B7530 100%)"
  },
  {
    title: "Movimiento",
    step: "03",
    process: "Edit · ritmo",
    description: "Dirección de edición, ritmo de montaje y narrativa. La diferencia entre filmar y contar: cada corte respira con la música, cada secuencia construye un arco.",
    detail: [
      "Selección · A-roll + B-roll guiados",
      "Sound design + mezcla a tiempo",
      "Cortes finales pensados para loop"
    ],
    gradient: "linear-gradient(137deg, #FFB347 0%, #FFE6A1 45%, #B8932C 100%)"
  }
];

function Pillars() {
  const pillars = PILLARS_DATA.map((p) => ({
    ...p,
    icon: p.title === "Captura" ?
      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
        <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" />
        <circle cx="12" cy="13" r="4" />
      </svg> :
      p.title === "Color" ?
      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
        <circle cx="9" cy="9" r="6" />
        <circle cx="15" cy="9" r="6" />
        <circle cx="12" cy="15" r="6" />
      </svg> :
      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
        <path d="M15 4 4 15l5 5L20 9z" />
        <path d="M14 7l3 3M5 3v4M3 5h4M19 17v4M17 19h4" />
      </svg>
  }));

  return (
    <section className="pillars-section">
      <ChapterNum n="02" side="left" />
      <div className="pillars-head">
        <span className="pillars-eyebrow">
          <Sparkle size={11} />
          <span>Pilares creativos · 03</span>
          <Sparkle size={11} />
        </span>
        <h2 className="pillars-h">
          La inspiración y la creatividad no se buscan, se conectan con la <span className="pillars-em">musa</span>.
        </h2>
        <p className="pillars-p">
          Captura, color y movimiento — el ritual donde la musa se vuelve frame. No vendemos horas; vendemos el oficio que les da peso cinematográfico.
        </p>
      </div>

      <div className="pillars-grid">
        {pillars.map((p, i) =>
        <Pillar key={p.title} pillar={p} idx={i} delay={i * 120 + 80} />
        )}
      </div>
    </section>);

}

const MUSA_STEPS = [
  { n: "01", kicker: "RITUAL · 01", title: "Invocación",   sub: "Brief · Estrategia",
    desc: "Escuchamos la marca. Diagnóstico, referencias, posicionamiento y look development. No empezamos a filmar sin saber qué historia se está contando." },
  { n: "02", kicker: "RITUAL · 02", title: "Canalización", sub: "Pre-producción",
    desc: "Scouting de locaciones, casting, lookboard, dirección de arte y plan de rodaje. Cada decisión se documenta para que el rodaje sea ejecución, no improvisación." },
  { n: "03", kicker: "RITUAL · 03", title: "Manifestación", sub: "Rodaje",
    desc: "Cámara, luz, sonido directo. Cuerpos full-frame, ópticas anamórficas y un set bloqueado. La cámara llega donde la historia ya está pasando." },
  { n: "04", kicker: "RITUAL · 04", title: "Alquimia",     sub: "Color · Sonido",
    desc: "Color grading con LUT propio, normalización ACES, sound design original, master HDR. Donde el oficio se vuelve estilo BWAY: cinematográfico, no genérico." },
  { n: "05", kicker: "RITUAL · 05", title: "Eternización",  sub: "Entrega · Loop",
    desc: "Master 4K + cortes sociales, deliverables por plataforma, archivo del proyecto. Lo que queda después del evento es la obra que se sigue compartiendo." }
];

function MusaStepsTrail() {
  const sectionRef = useRef(null);
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    const el = sectionRef.current;
    if (!el) return;
    let raf;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      // "Focus line" sits at 55% of the viewport. Path completes a bit before the section exits
      // so the last station still reveals comfortably.
      const focusLine = vh * 0.55;
      const focusY = focusLine - rect.top;
      const span = Math.max(1, rect.height - vh * 0.35);
      const p = Math.max(0, Math.min(1, focusY / span));
      setProgress(p);
    };
    const onScroll = () => { if (raf) return; raf = requestAnimationFrame(() => { update(); raf = null; }); };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const N = MUSA_STEPS.length;
  const walked = progress * N;                          // 0..N
  const activeIdx = Math.min(N - 1, Math.max(0, Math.floor(walked)));
  const overallPct = Math.round(progress * 100);

  return (
    <section className="mr2 mr2-v50" ref={sectionRef} aria-label="Recorrido del MUSA Method">
      {/* Atmosphere */}
      <div className="mr2-grid" aria-hidden="true"></div>
      <div className="mr2-grain" aria-hidden="true"></div>

      {/* Head */}
      <header className="mr2-head">
        <span className="mr2-kicker">/ Recorrido del ritual · {String(N).padStart(2,"0")} etapas</span>
        <h3 className="mr2-h">Del <em>brief</em> a la <em>obra.</em></h3>
        <p className="mr2-sub">El método MUSA, paso a paso. Recorré el camino con scroll.</p>
      </header>

      {/* The path — vertical road with stations */}
      <div className="mr2-path">
        <span className="mr2-path-rail" aria-hidden="true"></span>
        <span className="mr2-path-fill" aria-hidden="true" style={{ height: `${progress * 100}%` }}></span>
        <span className="mr2-path-head" aria-hidden="true" style={{ top: `${progress * 100}%`, opacity: progress > 0.01 && progress < 0.99 ? 1 : 0 }}></span>

        {MUSA_STEPS.map((s, i) => {
          // Reveal each station as the focus line passes it. Center of station = i + 0.5.
          const stepReveal = Math.max(0, Math.min(1, walked - i));
          const isReached = stepReveal > 0.15;
          const isActive = i === activeIdx;
          const side = i % 2 === 0 ? "right" : "left";
          return (
            <div
              key={s.n}
              className={"mr2-station mr2-station--" + side + (isReached ? " is-reached" : "") + (isActive ? " is-active" : "")}
              style={{ "--reveal": stepReveal }}
            >
              <div className="mr2-station-node" aria-hidden="true">
                <span className="mr2-station-pulse"></span>
                <span className="mr2-station-dot"></span>
                <span className="mr2-station-num">{s.n}</span>
              </div>
              <article className="mr2-station-card">
                <div className="mr2-frame-stamp">
                  <span className="rec"></span>
                  <span>REC · RITUAL {s.n}</span>
                </div>
                <span className="mr2-frame-kicker">{s.kicker}</span>
                <span className="mr2-frame-sub">{s.sub}</span>
                <h4 className="mr2-frame-title">{s.title}</h4>
                <p className="mr2-frame-desc">{s.desc}</p>
                <span className="mr2-corner tl"></span>
                <span className="mr2-corner tr"></span>
                <span className="mr2-corner bl"></span>
                <span className="mr2-corner br"></span>
                <div className="mr2-frame-num" aria-hidden="true">{s.n}</div>
              </article>
            </div>
          );
        })}
      </div>

      {/* Compact HUD pinned at bottom of viewport while section is visible */}
      <div className="mr2-hud" aria-hidden="true">
        <span className="mr2-hud-rec"><i></i>REC</span>
        <span className="mr2-hud-tc">{String(overallPct).padStart(3, "0")}%</span>
        <span className="mr2-hud-sep">·</span>
        <span className="mr2-hud-step">ETAPA {String(activeIdx + 1).padStart(2, "0")} / {String(N).padStart(2, "0")}</span>
      </div>
    </section>
  );
}

function MusaMethod() {
  useReveal();

  return <MusaMethodExpanding />;
}

// V5 — MusaMethod expands from compact frame to fullscreen as you scroll.
// Pin the section, then drive width/height + corner radius from pin progress.
function MusaMethodExpanding() {
  useReveal();
  const [trackRef, p] = usePinProgress();
  // Eased curve so most of the growth happens in the first half of the pin
  const grow = p < 0.5 ? p * 2 : 1;
  const eased = 1 - Math.pow(1 - grow, 3);

  return (
    <section className="section dark2 method-section" id="method">
      <ChapterNum n="03" />
      <div className="section-inner method-head-inner">
        <Parallax depth={0.06} className="section-head reveal">
          <div className="eyebrow">Metodología · MUSA Method</div>
          <h2 className="display">
            El método detrás de cada <span className="em">obra</span> BWAY.
          </h2>
          <p className="lead">
            En BWAY PROD no producimos contenido. Invocamos a la musa. Cada proyecto es un ritual donde la inspiración toma forma cinematográfica. MUSA METHOD es esa filosofía hecha sistema — cada paquete representa un nivel distinto de profundidad en la canalización creativa.
          </p>
        </Parallax>
      </div>

      <div className="method-pin" ref={trackRef}>
        <div className="method-pin-stage">
          <div
            className="method-frame"
            style={{
              "--p": eased,
              transform: `scale(${0.7 + eased * 0.3})`,
              borderRadius: `${(1 - eased) * 24}px`
            }}>
            <div className="method-frame-glow" aria-hidden="true"></div>
            <div className="method-frame-inner">
              <MusaMarquee />
            </div>
            <div className="method-frame-meta" aria-hidden="true">
              <span className="method-frame-tag">MUSA METHOD · RITUAL</span>
              <span className="method-frame-counter">
                <i className="rec"></i>
                <span>EXPAND</span>
                <span className="sep">·</span>
                <span>{String(Math.round(eased * 100)).padStart(2, "0")}%</span>
              </span>
            </div>
          </div>
        </div>
      </div>

      <MusaStepsTrail />
    </section>);

}

function useParallaxItem(intensity = 0.08) {
  const ref = useRef(null);
  const [offset, setOffset] = useState(0);
  const cur = useRef(0);
  useEffect(() => {
    const el = ref.current;
    if (!el || REDUCED_MOTION || intensity === 0) return;
    let raf = null;
    const frame = () => {
      raf = null;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const center = rect.top + rect.height / 2 - vh / 2;
      // Only animate in viewport vicinity for performance
      if (Math.abs(center) > vh * 1.5) return;
      const target = -center * intensity;
      // Lerp toward target — inertia makes the drift feel weighted, not glued
      const next = cur.current + (target - cur.current) * 0.14;
      cur.current = Math.abs(target - next) < 0.05 ? target : next;
      setOffset(cur.current);
      if (cur.current !== target) raf = requestAnimationFrame(frame);
    };
    const onScroll = () => {
      if (!raf) raf = requestAnimationFrame(frame);
    };
    frame();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [intensity]);
  return [ref, offset];
}

// Generic parallax layer — wraps children in a div that translates based on scroll.
// `depth` is a multiplier (negative = moves opposite to scroll). intensity scales globally.
function Parallax({ children, depth = 0.08, className = "", style = {}, as = "div" }) {
  const [ref, offset] = useParallaxItem(depth);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={className}
      style={{
        ...style,
        transform: `translate3d(0, ${offset}px, 0)`,
        willChange: "transform"
      }}>

      {children}
    </Tag>);

}

// V2 — shared cursor tilt: material response for cards. Fine pointer only.
function useTilt(ref, { rx = 4, ry = 6 } = {}) {
  const [tilt, setTilt] = useState({ x: 0, y: 0, gx: 50, gy: 50 });
  const enabled = !REDUCED_MOTION && FINE_POINTER;
  const onMove = (e) => {
    if (!enabled) return;
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - r.left) / r.width;
    const y = (e.clientY - r.top) / r.height;
    setTilt({ x: (0.5 - y) * rx, y: (x - 0.5) * ry, gx: x * 100, gy: y * 100 });
  };
  const onLeave = () => setTilt({ x: 0, y: 0, gx: 50, gy: 50 });
  return [tilt, onMove, onLeave];
}

function ServiceCard({ pkg, idx, onOpen, parallaxOn }) {
  const tierClass = pkg.tier === "LOW" ? "tier-low" : pkg.tier === "MEDIUM" ? "tier-med" : "tier-high";
  // Stagger intensity per column position so cards drift apart slightly
  const col = idx % 3;
  const intensity = parallaxOn ? (col === 0 ? 0.06 : col === 1 ? 0.11 : 0.08) : 0;
  const [ref, offset] = useParallaxItem(intensity);
  const [tilt, onTiltMove, onTiltLeave] = useTilt(ref, { rx: 3.5, ry: 5 });
  return (
    <article
      ref={ref}
      className={"svc-card reveal" + (pkg.popular ? " is-popular" : "")}
      onClick={() => onOpen(pkg)}
      onMouseMove={onTiltMove}
      onMouseLeave={onTiltLeave}
      style={{
        "--gx": tilt.gx + "%",
        "--gy": tilt.gy + "%",
        transform: `translate3d(0, ${offset}px, 0) perspective(1200px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`
      }}>
      <div className="svc-sheen" aria-hidden="true"></div>

      <div className="svc-head">
        <span className="svc-num">PKG · {String(idx + 1).padStart(2, "0")}</span>
        <div className="svc-badges">
          {pkg.popular && <span className="badge popular">Más Popular</span>}
          <span className={"badge " + tierClass}>{pkg.tier}</span>
        </div>
      </div>
      <h3 className="svc-name">
        <span className="musa">MUSA</span>
        {pkg.name}
      </h3>
      <p className="svc-tagline">{pkg.tagline}</p>
      <div className="svc-foot">
        <div className="svc-price">
          <span className="ccy">{pkg.currency}</span>
          {pkg.price}
        </div>
        <div className="svc-arrow">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
            <path d="M2 12 L12 2 M5 2 L12 2 L12 9" />
          </svg>
        </div>
      </div>
      <span className="svc-cta-hint" aria-hidden="true">VER DETALLE →</span>
    </article>);

}

function Services({ onOpen, tweaks }) {
  const parallaxOn = (tweaks?.parallax ?? 0.4) > 0.05;
  return (
    <section className="section" id="services">
      <ChapterNum n="04" side="left" />
      <div className="section-inner">
        <Parallax depth={0.06} className="section-head reveal">
          <div className="eyebrow">Paquetes MUSA · 06 caminos</div>
          <h2 className="display">
            Seis niveles.<br />
            Un solo <span className="em">ritual</span> cinematográfico.
          </h2>
          <p className="lead">
            De Costa Rica al mundo. Dos líneas, seis caminos. Cada paquete es un grado distinto de canalización — no un menú de precios, sino un mapa de profundidad creativa.
          </p>
        </Parallax>

        <Parallax depth={0.04} className="line-head reveal">
          <div className="lhs">
            <span className="idx">01 / TIERRA</span>
            <span className="title">Costa Rica · Centroamérica</span>
          </div>
          <div className="rhs">Lumen → Ánima → Olimpo</div>
        </Parallax>
        <div className="services-grid">
          {PACKAGES_TIERRA.map((p, i) =>
          <ServiceCard key={p.id} pkg={p} idx={i} onOpen={onOpen} parallaxOn={parallaxOn} />
          )}
        </div>

        <Parallax depth={0.04} className="line-head reveal">
          <div className="lhs">
            <span className="idx">02 / WORLD</span>
            <span className="title">Internacional · Global</span>
          </div>
          <div className="rhs">Wander → Soul → Eternal</div>
        </Parallax>
        <div className="services-grid">
          {PACKAGES_WORLD.map((p, i) =>
          <ServiceCard key={p.id} pkg={p} idx={i + 3} onOpen={onOpen} parallaxOn={parallaxOn} />
          )}
        </div>

        <div style={{ textAlign: "center", marginTop: 56 }} className="reveal">
          <a href="#cta" className="btn btn-ghost">
            Ver catálogo completo <span className="arrow"></span>
          </a>
        </div>
      </div>
    </section>);

}

function PortRow({ row, i }) {
  const intensity = i % 2 === 0 ? 0.05 : 0.09;
  const [ref, offset] = useParallaxItem(intensity);
  return (
    <div
      ref={ref}
      className="port-row"
      style={{ transform: `translate3d(0, ${offset}px, 0)` }}>

      <span className="idx">/ {String(i + 1).padStart(2, "0")}</span>
      <span className="name">
        {row.name.split(row.emWord).map((p, idx, arr) =>
        <React.Fragment key={idx}>
            {p}
            {idx < arr.length - 1 && <span className="em">{row.emWord}</span>}
          </React.Fragment>
        )}
      </span>
      <span className="kind">{row.kind}</span>
      <span className="year">{row.year}</span>
      <span className="arr">→</span>
    </div>);

}

const BENTO_GRID = [
  { ...PORTFOLIO[0], span: 7, hue: 36 }, // Daddy Yankee
  { ...PORTFOLIO[1], span: 5, hue: 18 }, // Danny Ocean
  { ...PORTFOLIO[2], span: 5, hue: 48 }, // Flowfest
  { ...PORTFOLIO[3], span: 7, hue: 12 }  // Ocaso
];

function BentoCard({ item, idx }) {
  const intensity = idx % 2 === 0 ? 0.04 : 0.07;
  const [ref, offset] = useParallaxItem(intensity);
  const [tilt, setTilt] = useState({ x: 0, y: 0 });

  const onMove = (e) => {
    if (REDUCED_MOTION) return;
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - r.left) / r.width - 0.5;
    const y = (e.clientY - r.top) / r.height - 0.5;
    setTilt({ x: -y * 5, y: x * 7 });
  };
  const onLeave = () => setTilt({ x: 0, y: 0 });

  return (
    <article
      ref={ref}
      className={"bento-card span-" + item.span}
      onMouseMove={onMove}
      onMouseLeave={onLeave}
      style={{
        transform: `translate3d(0, ${offset}px, 0) perspective(1400px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`
      }}>

      <div className="bento-fill" style={{
        background: `radial-gradient(circle at 30% 20%, oklch(0.18 0.04 ${item.hue}), oklch(0.07 0.02 ${item.hue}))`
      }}>
        <div className="bento-halftone"></div>
        <div className="bento-noise"></div>
        <div className="bento-stripes"></div>
      </div>
      <div className="bento-meta">
        <span className="bento-num">/ {String(idx + 1).padStart(2, "0")}</span>
        <span className="bento-kind">{item.kind} · {item.year}</span>
      </div>
      <div className="bento-title">
        <span className="bento-name">{item.name}</span>
        <span className="bento-cta">
          <span className="bento-cta-inner">
            Ver — <em>{item.name.split(" ")[0]}</em>
          </span>
        </span>
      </div>
    </article>);

}

// PortPiece — a single archive poster. Click opens the BrandModal
// with the project's full brand story. Hover still hints with a flip preview.
function PortPiece({ item, i, baseTransform, isFeature, onOpen }) {
  return (
    <article
      className={"port-piece" + (isFeature ? " is-feature" : "")}
      style={{ transform: baseTransform }}
      onClick={() => onOpen && onOpen(item)}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpen && onOpen(item); } }}
      tabIndex={0}
      role="button"
      aria-label={`Abrir ficha de ${item.name}`}>
      <div className="port-piece-3d">
        <div className="port-piece-frame port-piece-front">
          <div className="port-piece-fill" style={{ "--hue": (24 + i * 12) + "deg" }}>
            <div className="port-piece-grid"></div>
            <div className="port-piece-beam"></div>
            <span className="port-piece-mark" aria-hidden="true">{String(i + 1).padStart(2, "0")}</span>
            <span className="port-piece-take" aria-hidden="true">TAKE · 0{i + 1}</span>
            <div className="port-piece-grain"></div>
            <div className="port-piece-rim"></div>
          </div>
          <div className="port-piece-meta-top">
            <span className="port-piece-num">N° {String(i + 1).padStart(2, "0")} / 05</span>
            <span className="port-piece-format">{item.format}</span>
          </div>
          <div className="port-piece-body">
            <div className="port-piece-kicker">{item.kind} · {item.year}</div>
            <h3 className="port-piece-name">
              {item.name.split(item.emWord).map((part, idx, arr) => (
                <React.Fragment key={idx}>
                  {part}
                  {idx < arr.length - 1 && <span className="em">{item.emWord}</span>}
                </React.Fragment>
              ))}
            </h3>
            <div className="port-piece-frame-name">"{item.frame}"</div>
          </div>
          <div className="port-piece-meta-bot">
            <span className="port-piece-loc">{item.loc}</span>
            <span className="port-piece-arrow" aria-hidden="true">↗</span>
          </div>
          <span className="port-piece-flip-hint" aria-hidden="true">VER FICHA →</span>
        </div>
      </div>
      <div className="port-piece-shadow" aria-hidden="true"></div>
    </article>
  );
}

// V4 — Signature portfolio moment: pinned 3D archive of cinematic posters.
// Vertical scroll drives a horizontal camera-dolly through floating pieces
// at varying depths, like walking through a dark exhibition hall.
function PortfolioArchive() {
  const [trackRef, p] = usePinProgress();
  const stageRef = useRef(null);
  const railRef = useRef(null);
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const [openBrand, setOpenBrand] = useState(null);

  // Eased progress — more time in the middle, less at the edges.
  const eased = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;

  // Translate the rail across its full overflow as we scroll the pin.
  // Math handled in CSS via calc so it adapts to viewport without JS.
  // eased flows as a CSS custom property `--p`.

  const onMove = (e) => {
    if (REDUCED_MOTION || !FINE_POINTER) return;
    const el = stageRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - r.left) / r.width - 0.5;
    const y = (e.clientY - r.top) / r.height - 0.5;
    setTilt({ x: -y * 2.4, y: x * 3.6 });
  };
  const onLeave = () => setTilt({ x: 0, y: 0 });

  return (
    <section className="section dark2 portfolio-archive" id="portfolio">
      <ChapterNum n="05" />

      <div className="port-archive-pin" ref={trackRef}>
        <div className="port-archive-stage" ref={stageRef} onMouseMove={onMove} onMouseLeave={onLeave}>

          <div className="port-archive-bg" aria-hidden="true">
            <div className="port-archive-floor"></div>
            <div className="port-archive-spot"></div>
            <div className="port-archive-grain"></div>
          </div>

          <div className="port-archive-overlay" aria-hidden="true">
            <span className="port-archive-kicker">/ ESC 05 · ARCHIVO · LATAM 19—26</span>
            <span className="port-archive-counter">
              <i className="rec"></i>
              <span>{String(Math.min(PORTFOLIO.length, Math.floor(eased * PORTFOLIO.length) + 1)).padStart(2, "0")}</span>
              <span className="sep">/</span>
              <span>{String(PORTFOLIO.length).padStart(2, "0")}</span>
            </span>
          </div>

          <h2 className="port-archive-mega" aria-hidden="true"
              style={{ "--p": eased }}>
            <span className="port-archive-mega-pre">Lo que ya es</span>
            <span className="port-archive-mega-word">Eterno</span>
          </h2>

          <div className="port-archive-rail-wrap" style={{ "--p": eased, "--tx": tilt.x + "deg", "--ty": tilt.y + "deg" }}>
            <div
              ref={railRef}
              className="port-archive-rail">
              {PORTFOLIO.map((item, i) => {
                const depth = i % 2 === 0 ? 0 : -80;
                const rot = i % 2 === 0 ? -1.6 : 1.4;
                const scale = i === 2 ? 1.04 : 1;
                return (
                  <PortPiece
                    key={item.name}
                    item={item}
                    i={i}
                    baseTransform={`translate3d(0, ${i % 2 === 0 ? 0 : -22}px, ${depth}px) rotateY(${rot}deg) scale(${scale})`}
                    isFeature={i === 2}
                    onOpen={setOpenBrand}
                  />
                );
              })}
            </div>
          </div>

          <div className="port-archive-progress" aria-hidden="true">
            <span className="port-archive-progress-label">DOLLY</span>
            <span className="port-archive-progress-track"><i style={{ transform: `scaleX(${eased})` }}></i></span>
            <span className="port-archive-progress-pct">{String(Math.round(eased * 100)).padStart(2, "0")}%</span>
          </div>
        </div>
      </div>

      {openBrand && <BrandModal item={openBrand} onClose={() => setOpenBrand(null)} />}
    </section>);

}

function Portfolio() {
  return <PortfolioV2 />;
}

const PORTFOLIO_V2_PROJECTS = [
  { code: "BWY-001", name: "Daddy Yankee", role: "Cliente", kind: "Concierto · Live", year: "2023",
    desc: "Multicámara en vivo. Cobertura editorial del Farewell Tour con dos unidades aéreas y rig anamorfico. El cierre de una era.",
    hueA: 32, hueB: 18, tag: "FAREWELL · TOUR" },
  { code: "BWY-002", name: "Danny Ocean", role: "Cliente", kind: "Concierto · Live", year: "2023",
    desc: "Estética hora dorada, color grading editorial y dos cuts: un master cinematográfico + 3 cortes sociales optimizados para reels.",
    hueA: 18, hueB: 12, tag: "ANTÍDOTO · LIVE" },
  { code: "BWY-003", name: "Flowfest", role: "Cliente", kind: "Festival · Doc", year: "2024",
    desc: "Cobertura completa del festival con 4 cámaras móviles. Documento editorial del backstage + after movie de 90s y reel de marca.",
    hueA: 48, hueB: 60, tag: "FESTIVAL · CR" }
];
const PORTFOLIO_V2_GRID = [
  { code: "BWY-004", name: "MUSA Method", kind: "Ritual", year: "2026", hueA: 40, hueB: 28, tag: "RITUAL · BWY" },
  { code: "BWY-005", name: "Ocaso", kind: "Festival", year: "2024", hueA: 12, hueB: 24, tag: "FESTIVAL · LATAM" },
  { code: "BWY-006", name: "Ritvales", kind: "Internacional", year: "2024", hueA: 60, hueB: 48, tag: "DOC · GLOBAL" },
  { code: "BWY-007", name: "Studio Ritual", kind: "Behind", year: "2024", hueA: 28, hueB: 40, tag: "BTS · STUDIO" }
];

function PortfolioV2() {
  const sectionRef = useRef(null);
  const marqueeRef = useRef(null);
  const [marqueeOffset, setMarqueeOffset] = useState(0);
  const [openProject, setOpenProject] = useState(null);

  useEffect(() => {
    const el = marqueeRef.current;
    if (!el) return;
    let raf;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const enter = (vh - rect.top);
      setMarqueeOffset(enter * 0.3);
    };
    const onScroll = () => { if (raf) return; raf = requestAnimationFrame(() => { update(); raf = null; }); };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, []);

  return (
    <section className="pf2" id="portfolio" ref={sectionRef}>
      <div className="pf2-shell">
        <header className="pf2-head reveal">
          <span className="pf2-kicker">/ Portafolio · 06 años en cámara</span>
          <h2 className="pf2-h">Lo que queda <em>después.</em></h2>
          <p className="pf2-sub">El valor no está en lo que dura el evento — está en lo que queda después. Una selección curada de proyectos donde la cámara llegó donde la historia estaba pasando.</p>
        </header>

        <div className="pf2-marquee" ref={marqueeRef} aria-hidden="true">
          <div className="pf2-marquee-row pf2-marquee-row-a"
               style={{ transform: `translate3d(${marqueeOffset - 200}px, 0, 0)` }}>
            {[...PORTFOLIO_V2_PROJECTS, ...PORTFOLIO_V2_GRID, ...PORTFOLIO_V2_PROJECTS, ...PORTFOLIO_V2_GRID].map((p, i) => (
              <span key={"a-" + i} className="pf2-marquee-tile"
                    style={{ background: `radial-gradient(ellipse at 30% 20%, oklch(0.32 0.06 ${p.hueA}), oklch(0.07 0.02 ${p.hueB || p.hueA}))` }}>
                <span className="pf2-mq-code">{p.code}</span>
                <span className="pf2-mq-name">{p.name}</span>
                <span className="pf2-mq-tag">{p.tag}</span>
              </span>
            ))}
          </div>
          <div className="pf2-marquee-row pf2-marquee-row-b"
               style={{ transform: `translate3d(${-(marqueeOffset - 200)}px, 0, 0)` }}>
            {[...PORTFOLIO_V2_GRID, ...PORTFOLIO_V2_PROJECTS, ...PORTFOLIO_V2_GRID, ...PORTFOLIO_V2_PROJECTS].map((p, i) => (
              <span key={"b-" + i} className="pf2-marquee-tile"
                    style={{ background: `radial-gradient(ellipse at 70% 80%, oklch(0.32 0.06 ${p.hueB || p.hueA}), oklch(0.06 0.02 ${p.hueA}))` }}>
                <span className="pf2-mq-code">{p.code}</span>
                <span className="pf2-mq-name">{p.name}</span>
                <span className="pf2-mq-tag">{p.tag}</span>
              </span>
            ))}
          </div>
        </div>

        <div className="pf2-stack">
          {PORTFOLIO_V2_PROJECTS.map((p, i) => (
            <Pf2StackCard key={p.code} project={p} index={i} total={PORTFOLIO_V2_PROJECTS.length} onOpen={setOpenProject} />
          ))}
        </div>

        <div className="pf2-grid">
          <header className="pf2-grid-head reveal">
            <span className="pf2-kicker">/ Más obras · Archive</span>
            <h3 className="pf2-grid-h">Otros pasajes <em>·</em> 04</h3>
          </header>
          <div className="pf2-grid-list">
            {PORTFOLIO_V2_GRID.map((p, i) => (
              <article key={p.code} className="pf2-grid-card reveal" style={{ animationDelay: (i * 0.08) + "s" }} onClick={() => setOpenProject({ ...p, role: "Archive" })}>
                <div className="pf2-grid-thumb"
                     style={{ background: `radial-gradient(ellipse at 30% 20%, oklch(0.30 0.08 ${p.hueA}), oklch(0.05 0.02 ${p.hueB || p.hueA}))` }}>
                  <span className="pf2-grid-thumb-code">{p.code}</span>
                  <span className="pf2-grid-thumb-num">{String(i + 1 + PORTFOLIO_V2_PROJECTS.length).padStart(2, "0")}</span>
                </div>
                <div className="pf2-grid-body">
                  <div className="pf2-grid-meta"><span>{p.kind}</span><span>·</span><span>{p.year}</span></div>
                  <h4 className="pf2-grid-name">{p.name}</h4>
                  <span className="pf2-grid-tag">{p.tag}</span>
                </div>
              </article>
            ))}
          </div>
        </div>
      </div>
      {openProject && (
        <Pf2Modal project={openProject} onClose={() => setOpenProject(null)} />
      )}
    </section>
  );
}

function Pf2Modal({ project, onClose }) {
  const ref = useRef(null);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    try { window.bwayLenis?.stop?.(); } catch {}
    const el = ref.current;
    const onWheel = (e) => {
      if (!el) return;
      e.stopPropagation();
      const atTop = el.scrollTop <= 0;
      const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
      if ((e.deltaY > 0 && !atBottom) || (e.deltaY < 0 && !atTop)) {
        e.preventDefault();
        el.scrollTop += e.deltaY;
      }
    };
    el?.addEventListener("wheel", onWheel, { passive: false });
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
      el?.removeEventListener("wheel", onWheel);
      try { window.bwayLenis?.start?.(); } catch {}
    };
  }, [onClose]);

  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const onMove = (e) => {
    const r = ref.current?.getBoundingClientRect();
    if (!r) return;
    const x = (e.clientX - r.left) / r.width - 0.5;
    const y = (e.clientY - r.top) / r.height - 0.5;
    setTilt({ x: -y * 6, y: x * 8 });
  };
  const onLeave = () => setTilt({ x: 0, y: 0 });

  const handleWrapClick = (ev) => {
    if (ref.current && !ref.current.contains(ev.target)) onClose();
  };
  return (
    <div className="pf2-modal-wrap" onClick={handleWrapClick} data-lenis-prevent>
      <div className="pf2-modal-backdrop" onClick={onClose} />
      <article
        ref={ref}
        className="pf2-modal"
        onClick={(ev) => ev.stopPropagation()}
        onMouseMove={onMove}
        onMouseLeave={onLeave}
        style={{
          transform: `perspective(1600px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
          background: `linear-gradient(168deg, oklch(0.18 0.04 ${project.hueA}) 0%, #0a0805 100%)`
        }}
      >
        <button type="button" className="pf2-modal-close" onClick={onClose} aria-label="Cerrar">×</button>

        <header className="pf2-modal-head">
          <div className="pf2-modal-code-block">
            <span className="pf2-modal-code">{project.code}</span>
            <span className="pf2-modal-role">{project.role || "Cliente"}</span>
          </div>
          <h3 className="pf2-modal-name">{project.name}</h3>
          <div className="pf2-modal-meta-row">
            <span>{project.kind}</span><span>·</span><span>{project.year}</span><span>·</span><span>{project.tag}</span>
          </div>
        </header>

        {project.desc ? (
          <p className="pf2-modal-desc">{project.desc}</p>
        ) : (
          <p className="pf2-modal-desc">
            Proyecto en archivo. Detalles del rodaje, locaciones, equipo y entregables disponibles bajo solicitud.
          </p>
        )}

        <div className="pf2-modal-frames">
          <div className="pf2-modal-frame" style={{ background: `radial-gradient(ellipse at 30% 30%, oklch(0.36 0.10 ${project.hueA}), oklch(0.06 0.02 ${project.hueB || project.hueA}))` }}>
            <span className="pf2-modal-frame-num">01</span>
            <span className="pf2-modal-frame-tag">FRAME · {project.tag}</span>
            <span className="pf2-modal-frame-scan"></span>
          </div>
          <div className="pf2-modal-frame" style={{ background: `radial-gradient(ellipse at 70% 70%, oklch(0.32 0.09 ${project.hueB || project.hueA}), oklch(0.05 0.02 ${project.hueA}))` }}>
            <span className="pf2-modal-frame-num">02</span>
            <span className="pf2-modal-frame-tag">{project.kind || "ARCHIVE"}</span>
            <span className="pf2-modal-frame-scan"></span>
          </div>
          <div className="pf2-modal-frame pf2-modal-frame-wide" style={{ background: `radial-gradient(ellipse at 50% 50%, oklch(0.36 0.10 ${project.hueA}), oklch(0.04 0.02 ${project.hueB || project.hueA}))` }}>
            <span className="pf2-modal-frame-num pf2-modal-frame-num-big">{project.code}</span>
            <span className="pf2-modal-frame-tag">{project.year} · {project.tag}</span>
            <span className="pf2-modal-frame-scan"></span>
          </div>
        </div>

        <div className="pf2-modal-specs">
          <div><dt>Categoría</dt><dd>{project.kind}</dd></div>
          <div><dt>Año</dt><dd>{project.year}</dd></div>
          <div><dt>Locación</dt><dd>San José · CR</dd></div>
          <div><dt>Equipo</dt><dd>BWAY PROD</dd></div>
        </div>

        <footer className="pf2-modal-foot">
          <span className="pf2-modal-quote">"El valor no está en lo que dura el evento — está en lo que queda después."</span>
          <a href="#cta" className="pf2-modal-cta" onClick={() => onClose()}>
            Hablar con Russell <span>↗</span>
          </a>
        </footer>
      </article>
    </div>
  );
}

function Pf2StackCard({ project, index, total, onOpen }) {
  const ref = useRef(null);
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const p = 1 - Math.max(0, Math.min(1, (rect.top + rect.height * 0.2) / vh));
      setProgress(p);
    };
    const onScroll = () => { if (raf) return; raf = requestAnimationFrame(() => { update(); raf = null; }); };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, []);

  const targetScale = 1 - (total - 1 - index) * 0.03;
  const scale = 1 - progress * (1 - targetScale);
  const topOffset = index * 28;

  return (
    <div className="pf2-stack-wrap" style={{ top: `${topOffset}px` }}>
      <article ref={ref} className="pf2-stack-card" onClick={() => onOpen && onOpen(project)}
               style={{ transform: `scale(${scale})`,
                        background: `linear-gradient(168deg, oklch(0.18 0.04 ${project.hueA}) 0%, #0a0805 100%)` }}>
        <div className="pf2-stack-head">
          <div className="pf2-stack-num">{String(index + 1).padStart(2, "0")}</div>
          <div className="pf2-stack-meta">
            <span className="pf2-stack-cat">{project.role}</span>
            <h3 className="pf2-stack-name">{project.name}</h3>
          </div>
          <a href="#cta" className="pf2-stack-cta" onClick={(ev) => ev.stopPropagation()}>Ver proyecto <span>↗</span></a>
        </div>
        <p className="pf2-stack-desc">{project.desc}</p>
        <div className="pf2-stack-grid">
          <div className="pf2-stack-col pf2-stack-col-a">
            <div className="pf2-stack-thumb pf2-stack-thumb-a"
                 style={{ background: `radial-gradient(ellipse at 40% 30%, oklch(0.38 0.08 ${project.hueA}), oklch(0.08 0.02 ${project.hueB}))` }}>
              <span className="pf2-stack-thumb-meta">N° {String(index + 1).padStart(2, "0")} / 03 · A</span>
              <span className="pf2-stack-thumb-tag">{project.tag}</span>
            </div>
            <div className="pf2-stack-thumb pf2-stack-thumb-b"
                 style={{ background: `radial-gradient(ellipse at 70% 70%, oklch(0.30 0.06 ${project.hueB}), oklch(0.06 0.02 ${project.hueA}))` }}>
              <span className="pf2-stack-thumb-meta">FRAME · 02</span>
              <span className="pf2-stack-thumb-tag">{project.kind}</span>
            </div>
          </div>
          <div className="pf2-stack-col pf2-stack-col-b">
            <div className="pf2-stack-thumb pf2-stack-thumb-c"
                 style={{ background: `radial-gradient(ellipse at 50% 30%, oklch(0.36 0.09 ${project.hueA}), oklch(0.05 0.02 ${project.hueB}))` }}>
              <span className="pf2-stack-thumb-num">{String(index + 1).padStart(2, "0")}</span>
              <span className="pf2-stack-thumb-meta">{project.year}</span>
              <span className="pf2-stack-thumb-tag">{project.code}</span>
            </div>
          </div>
        </div>
      </article>
    </div>
  );
}

// V6 — Brand detail modal opened from any port-piece click.
// Portal-mounted to body so its `position: fixed` isn't trapped by the
// perspective/transform on the portfolio stage's ancestors.
function BrandModal({ item, onClose }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = prev;
    };
  }, [onClose]);
  const node = (
    <div className="brand-modal" onClick={onClose}>
      <div className="brand-modal-backdrop" aria-hidden="true"></div>
      <div className="brand-modal-card" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label={`Ficha de ${item.name}`}>
        <button className="brand-modal-close" onClick={onClose} aria-label="Cerrar">
          <span></span><span></span>
        </button>
        <div className="brand-modal-poster">
          <div className="brand-modal-poster-fill"></div>
          <span className="brand-modal-poster-mark">{item.frame}</span>
          <span className="brand-modal-poster-format">{item.format}</span>
          <div className="brand-modal-poster-grain"></div>
          <div className="brand-modal-poster-rim"></div>
        </div>
        <div className="brand-modal-body">
          <div className="brand-modal-kicker">{item.kind} · {item.year} · {item.loc}</div>
          <h2 className="brand-modal-name">
            {item.name.split(item.emWord).map((part, idx, arr) => (
              <React.Fragment key={idx}>
                {part}
                {idx < arr.length - 1 && <span className="em">{item.emWord}</span>}
              </React.Fragment>
            ))}
          </h2>
          <div className="brand-modal-frame">"{item.frame}"</div>

          <dl className="brand-modal-meta">
            <div><dt>Marca</dt><dd>{item.brand}</dd></div>
            <div><dt>Rol</dt><dd>{item.role}</dd></div>
            <div><dt>Formato</dt><dd>{item.format}</dd></div>
            <div><dt>Año</dt><dd>{item.year}</dd></div>
          </dl>

          <p className="brand-modal-story">{item.story}</p>

          {item.bullets && (
            <ul className="brand-modal-bullets">
              {item.bullets.map((b, i) => (
                <li key={i} style={{ "--i": i }}>
                  <span className="brand-modal-bullet-num">{String(i + 1).padStart(2, "0")}</span>
                  <span>{b}</span>
                </li>
              ))}
            </ul>
          )}

          <div className="brand-modal-foot">
            <a href={item.link || "#cta"} className="btn btn-gold">
              Hablar de un proyecto similar <span className="arrow"></span>
            </a>
            <span className="brand-modal-tag">BWAY · ARCHIVO</span>
          </div>
        </div>
      </div>
    </div>
  );
  return ReactDOM.createPortal(node, document.body);
}

function PixelatedPortrait({
  src,
  width = 400,
  height = 500,
  cellSize = 3,
  dotScale = 0.9,
  shape = "square",
  backgroundColor = "#0a0805",
  dropoutStrength = 0.4,
  distortionStrength = 3,
  distortionRadius = 80,
  distortionMode = "swirl",
  followSpeed = 0.2,
  jitterStrength = 4,
  jitterSpeed = 4,
  sampleAverage = true,
  tintColor = "#f4d68a",
  tintStrength = 0.2,
  className = ""
}) {
  const canvasRef = useRef(null);
  const stateRef = useRef({
    samples: null,
    mouse: { x: -9999, y: -9999, tx: -9999, ty: -9999 },
    raf: null,
    t0: 0,
    loaded: false,
    dpr: typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1
  });

  const parseHex = (hex) => {
    const h = hex.replace("#", "");
    return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = stateRef.current.dpr;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    canvas.style.width = width + "px";
    canvas.style.height = height + "px";
    const ctx = canvas.getContext("2d");
    ctx.scale(dpr, dpr);

    const off = document.createElement("canvas");
    off.width = width;
    off.height = height;
    const offCtx = off.getContext("2d", { willReadFrequently: true });

    const tint = parseHex(tintColor);
    let img = new Image();
    img.crossOrigin = "anonymous";

    const compute = () => {
      offCtx.fillStyle = backgroundColor;
      offCtx.fillRect(0, 0, width, height);
      const r = Math.max(width / img.naturalWidth, height / img.naturalHeight);
      const w = img.naturalWidth * r;
      const h = img.naturalHeight * r;
      offCtx.drawImage(img, (width - w) / 2, (height - h) / 2, w, h);
      const pixels = offCtx.getImageData(0, 0, width, height).data;
      const samples = [];
      for (let y = 0; y < height; y += cellSize) {
        for (let x = 0; x < width; x += cellSize) {
          if (Math.random() < dropoutStrength) continue;
          let R = 0, G = 0, B = 0, A = 0, n = 0;
          if (sampleAverage) {
            for (let dy = 0; dy < cellSize; dy++) {
              for (let dx = 0; dx < cellSize; dx++) {
                const px = ((y + dy) * width + (x + dx)) * 4;
                R += pixels[px]; G += pixels[px + 1]; B += pixels[px + 2]; A += pixels[px + 3];
                n++;
              }
            }
            R /= n; G /= n; B /= n; A /= n;
          } else {
            const px = (y * width + x) * 4;
            R = pixels[px]; G = pixels[px + 1]; B = pixels[px + 2]; A = pixels[px + 3];
          }
          R = R * (1 - tintStrength) + tint[0] * tintStrength;
          G = G * (1 - tintStrength) + tint[1] * tintStrength;
          B = B * (1 - tintStrength) + tint[2] * tintStrength;
          samples.push({ x: x + cellSize / 2, y: y + cellSize / 2, r: R | 0, g: G | 0, b: B | 0, a: A / 255 });
        }
      }
      stateRef.current.samples = samples;
      stateRef.current.loaded = true;
      stateRef.current.t0 = performance.now();
    };
    img.onload = compute;
    img.onerror = () => {
      const g = offCtx.createLinearGradient(0, 0, width, height);
      g.addColorStop(0, "#3a2d1c");
      g.addColorStop(1, "#0a0805");
      offCtx.fillStyle = g;
      offCtx.fillRect(0, 0, width, height);
      const data = offCtx.getImageData(0, 0, width, height).data;
      const samples = [];
      for (let y = 0; y < height; y += cellSize) {
        for (let x = 0; x < width; x += cellSize) {
          if (Math.random() < dropoutStrength) continue;
          const px = (y * width + x) * 4;
          samples.push({
            x: x + cellSize / 2, y: y + cellSize / 2,
            r: data[px] * (1 - tintStrength) + tint[0] * tintStrength | 0,
            g: data[px+1] * (1 - tintStrength) + tint[1] * tintStrength | 0,
            b: data[px+2] * (1 - tintStrength) + tint[2] * tintStrength | 0,
            a: 0.85
          });
        }
      }
      stateRef.current.samples = samples;
      stateRef.current.loaded = true;
      stateRef.current.t0 = performance.now();
    };
    img.src = src;

    const draw = (now) => {
      const st = stateRef.current;
      if (!st.loaded) { st.raf = requestAnimationFrame(draw); return; }
      st.mouse.x += (st.mouse.tx - st.mouse.x) * followSpeed;
      st.mouse.y += (st.mouse.ty - st.mouse.y) * followSpeed;
      ctx.fillStyle = backgroundColor;
      ctx.fillRect(0, 0, width, height);
      const t = (now - st.t0) / 1000;
      const radius2 = distortionRadius * distortionRadius;
      const samples = st.samples;
      const dotSize = cellSize * dotScale;
      for (let i = 0; i < samples.length; i++) {
        const s = samples[i];
        let px = s.x, py = s.y;
        const dx = px - st.mouse.x;
        const dy = py - st.mouse.y;
        const d2 = dx * dx + dy * dy;
        if (d2 < radius2) {
          const falloff = 1 - Math.sqrt(d2) / distortionRadius;
          if (distortionMode === "swirl") {
            const angle = falloff * distortionStrength * 0.6;
            const c = Math.cos(angle), sn = Math.sin(angle);
            px = st.mouse.x + dx * c - dy * sn;
            py = st.mouse.y + dx * sn + dy * c;
          } else {
            const f = falloff * distortionStrength * 2;
            const m = Math.sqrt(d2) || 1;
            px += (dx / m) * f;
            py += (dy / m) * f;
          }
        }
        if (jitterStrength > 0) {
          px += Math.sin(t * jitterSpeed + s.x * 0.03) * jitterStrength * 0.5;
          py += Math.cos(t * jitterSpeed + s.y * 0.03) * jitterStrength * 0.5;
        }
        ctx.fillStyle = `rgba(${s.r},${s.g},${s.b},${s.a})`;
        if (shape === "circle") {
          ctx.beginPath();
          ctx.arc(px, py, dotSize / 2, 0, Math.PI * 2);
          ctx.fill();
        } else {
          ctx.fillRect(px - dotSize / 2, py - dotSize / 2, dotSize, dotSize);
        }
      }
      st.raf = requestAnimationFrame(draw);
    };
    stateRef.current.raf = requestAnimationFrame(draw);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      stateRef.current.mouse.tx = ((e.clientX - r.left) / r.width) * width;
      stateRef.current.mouse.ty = ((e.clientY - r.top) / r.height) * height;
    };
    const onLeave = () => {
      stateRef.current.mouse.tx = -9999;
      stateRef.current.mouse.ty = -9999;
    };
    canvas.addEventListener("mousemove", onMove);
    canvas.addEventListener("mouseleave", onLeave);
    return () => {
      cancelAnimationFrame(stateRef.current.raf);
      canvas.removeEventListener("mousemove", onMove);
      canvas.removeEventListener("mouseleave", onLeave);
    };
  }, [src, width, height, cellSize, dotScale, shape, dropoutStrength, distortionStrength, distortionRadius, distortionMode, followSpeed, jitterStrength, jitterSpeed, sampleAverage, tintColor, tintStrength, backgroundColor]);

  return (
    <canvas
      ref={canvasRef}
      className={"pixel-canvas " + className}
      aria-label="Russell — retrato pixelado interactivo"
    />
  );
}

function Founder() {
  return (
    <section className="section founder-section" id="founder">
      <ChapterNum n="06" side="left" />
      <div className="section-inner founder-inner">
        <div className="founder-card reveal">
          <div className="founder-card-photo">
            <div className="founder-photo-frame founder-photo-frame-canvas">
              <PixelatedPortrait
                src="assets/founder.jpg"
                width={400}
                height={500}
                cellSize={3}
                dotScale={0.9}
                shape="square"
                backgroundColor="#0a0805"
                dropoutStrength={0.4}
                distortionStrength={3}
                distortionRadius={80}
                distortionMode="swirl"
                followSpeed={0.2}
                jitterStrength={4}
                jitterSpeed={4}
                sampleAverage
                tintColor="#f4d68a"
                tintStrength={0.18}
              />
              <span className="founder-photo-corner tl"></span>
              <span className="founder-photo-corner tr"></span>
              <span className="founder-photo-corner bl"></span>
              <span className="founder-photo-corner br"></span>
            </div>
            <div className="founder-photo-meta">
              <span><i className="rec"></i> EN VIVO</span>
              <span>SAN JOSÉ · CR</span>
            </div>
          </div>

          <div className="founder-card-bio">
            <span className="founder-kicker">Fundador · 01</span>
            <h2 className="founder-name">
              <span>Russell</span>
              <span className="founder-name-em">Mart.</span>
            </h2>
            <p className="founder-role">Director Creativo · Cinematógrafo · BWAY PROD</p>

            <dl className="founder-facts">
              <div><dt>Base</dt><dd>San José · Costa Rica</dd></div>
              <div><dt>Activo desde</dt><dd>2019</dd></div>
              <div><dt>Formalización</dt><dd>Mayo 2026</dd></div>
              <div><dt>Equipo</dt><dd>Cinematografía · Color · Edit</dd></div>
            </dl>

            <div className="founder-bio">
              <p>
                Lo que empezó con inseguridades y una cámara prestada, hoy es una productora que ha cubierto a Daddy Yankee, Danny Ocean, y festivales internacionales.
              </p>
              <p>
                El nombre <span className="serif italic" style={{ color: "var(--gold-light)" }}>"BWAY"</span> viene de la infancia — un apodo en patua que significa <span className="serif italic">"hombre"</span>. Ese nombre de niño se convirtió en la identidad de una productora.
              </p>
              <p>
                Dirección creativa, fotografía, videografía, postproducción y color grading. El oficio antes que el algoritmo.
              </p>
            </div>

            <blockquote className="founder-quote">
              <span className="founder-quote-mark">"</span>
              BWAY PROD es un movimiento, no solo una productora.
            </blockquote>

            <div className="founder-actions">
              <a href="https://wa.me/50670470555" className="btn btn-gold">
                Hablar con Russell <span className="arrow"></span>
              </a>
              <a href="#cta" className="btn btn-ghost">
                Ver paquetes <span className="arrow"></span>
              </a>
            </div>
          </div>
        </div>
      </div>
      <div className="founder-cta-bridge" aria-hidden="true">
        <div className="founder-cta-line"></div>
        <div className="founder-cta-glow"></div>
      </div>
    </section>);

}

function CTA() {
  const [openQuote, setOpenQuote] = useState(false);
  return (
    <section className="cta" id="cta">
      <div className="cta-bar cta-bar-top" aria-hidden="true"></div>
      <div className="cta-bar cta-bar-bottom" aria-hidden="true"></div>
      <div className="cta-slate" aria-hidden="true">
        <span className="cta-slate-l"><span className="rec"></span>ESCENA FINAL</span>
        <span className="cta-slate-r">BWAY · 2026</span>
      </div>
      <div className="cta-flare" aria-hidden="true"></div>
      <div className="cta-horizon" aria-hidden="true"></div>
      <div className="cta-vignette" aria-hidden="true"></div>
      <Parallax depth={-0.06} className="reveal">
        <div className="eyebrow" style={{ justifyContent: "center", display: "inline-flex" }}>Próximo paso</div>
        <h2>
          ¿Querés tu <span className="em">cotización</span> personalizada?
        </h2>
        <p className="sub">Contanos qué proyecto tenés en mente. Diseñamos un paquete a medida y te llega un acceso exclusivo a tu dashboard.</p>
        <div className="cta-ctas">
          <Magnetic>
            <button
              type="button"
              className="btn btn-gold"
              onClick={() => setOpenQuote(true)}
            >
              Registrarme · Pedir cotización <span className="arrow"></span>
            </button>
          </Magnetic>
          <Magnetic>
            <a href="https://wa.me/50670470555" className="btn btn-ghost">
              Hablar por WhatsApp <span className="arrow"></span>
            </a>
          </Magnetic>
        </div>
      </Parallax>
      {openQuote && <RequestQuoteModal onClose={() => setOpenQuote(false)} />}
    </section>);

}

// ==========================================================
// Request-quote modal — public form that creates a row in
// client_requests via the submit_client_request RPC.
// No auth required. Admin sees it in the inbox and invites.
// ==========================================================
function RequestQuoteModal({ onClose }) {
  const [form, setForm] = useState({
    full_name: "", email: "", company: "", phone: "",
    project_brief: "", budget_hint: "",
    // Honeypot field — invisible to humans, bots will fill it.
    // Named "website" because that's what crawlers expect.
    website: ""
  });
  const [state, setState] = useState({ status: "idle", err: null });

  // Track when the modal was opened — used to detect bots that submit instantly.
  const openedAtRef = useRef(Date.now());

  // Lock body scroll while modal is open
  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    if (window.bwayLenis?.stop) window.bwayLenis.stop();
    return () => {
      document.body.style.overflow = prev;
      if (window.bwayLenis?.start) window.bwayLenis.start();
    };
  }, []);

  function set(k, v) { setForm(f => ({ ...f, [k]: v })); }

  function isValid() {
    if (form.full_name.trim().length < 2) return false;
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email.trim())) return false;
    return true;
  }

  async function submit(ev) {
    ev.preventDefault();
    if (!isValid() || !window.bwaySupabase) return;
    setState({ status: "loading", err: null });
    try {
      const interactionMs = Date.now() - openedAtRef.current;
      const { data, error } = await window.bwaySupabase.rpc("submit_client_request", {
        p_email: form.email,
        p_full_name: form.full_name,
        p_company: form.company || null,
        p_phone: form.phone || null,
        p_project_brief: form.project_brief || null,
        p_budget_hint: form.budget_hint || null,
        p_source: "landing_cta",
        p_user_agent: navigator.userAgent.slice(0, 200),
        p_honeypot: form.website || null,
        p_interaction_ms: interactionMs,
        p_origin: window.location.origin
      });
      if (error) throw error;

      // Fire-and-forget: trigger confirmation + admin notification emails.
      // We don't block the success UI on email delivery — the row is already
      // saved in the DB and the admin will see it regardless.
      const cfg = window.__BWAY_CONFIG__ || {};
      const supabaseUrl = cfg.SUPABASE_URL || (window.bwaySupabase?.supabaseUrl);
      const anonKey = cfg.SUPABASE_ANON;
      if (supabaseUrl && anonKey && data) {
        fetch(supabaseUrl + "/functions/v1/notify-request", {
          method: "POST",
          headers: {
            "apikey":       anonKey,
            "Authorization":"Bearer " + anonKey,
            "content-type": "application/json"
          },
          body: JSON.stringify({ request_id: data })
        }).catch((err) => console.warn("notify-request failed (non-blocking)", err));
      }

      setState({ status: "ok", err: null, requestId: data });
    } catch (e) {
      // Translate server errors to friendly UX messages
      const code = (e && e.message) || "";
      const friendly =
        /tiempo_muy_corto/.test(code)   ? "Tomate un momento más antes de enviar — el formulario detectó actividad automática." :
        /email_invalido/.test(code)     ? "Revisá tu email, parece tener un formato no válido." :
        /nombre_invalido/.test(code)    ? "Por favor, ingresá tu nombre completo." :
        /email_desechable/.test(code)   ? "Por favor usá un email corporativo o personal real." :
        /rate_limit_email/.test(code)   ? "Ya enviaste varias solicitudes recientemente. Te contactamos pronto." :
        /rate_limit_device/.test(code)  ? "Detectamos varias solicitudes desde este dispositivo. Probá en un rato." :
        /rate_limit_global/.test(code)  ? "El servidor está procesando muchas solicitudes. Probá en unos minutos." :
        "No pudimos enviar tu solicitud. Intentá de nuevo o escribinos por WhatsApp.";
      setState({ status: "error", err: friendly });
    }
  }

  function close() { onClose && onClose(); }

  return (
    <div className="rq-backdrop" onClick={(ev) => { if (ev.target === ev.currentTarget) close(); }}>
      <div className="rq-modal" role="dialog" aria-labelledby="rq-title">
        <button type="button" className="rq-close" onClick={close} aria-label="Cerrar">×</button>

        {state.status === "ok" ? (
          <div className="rq-success">
            <div className="rq-success-mark" aria-hidden="true">✓</div>
            <h3 id="rq-title">Solicitud recibida</h3>
            <p>
              Te enviamos esta confirmación a <b>{form.email}</b>. Nuestro equipo
              la revisa en menos de 24h y te envía un acceso exclusivo para
              que veas tu cotización en tu dashboard.
            </p>
            <button className="btn btn-gold" onClick={close}>Cerrar</button>
          </div>
        ) : (
          <form className="rq-form" onSubmit={submit} noValidate autoComplete="off">
            {/* Honeypot field — invisible to humans, bots fill it.
                Wrapped in a div with aria-hidden + off-screen styles. */}
            <div aria-hidden="true" style={{ position: "absolute", left: "-9999px", top: "auto", width: 1, height: 1, overflow: "hidden" }}>
              <label>
                Sitio web (no llenar)
                <input
                  type="text"
                  name="website"
                  tabIndex={-1}
                  autoComplete="off"
                  value={form.website}
                  onChange={(ev) => set("website", ev.target.value)}
                />
              </label>
            </div>

            <header className="rq-head">
              <span className="rq-kicker">/ COTIZACIÓN PERSONALIZADA</span>
              <h3 id="rq-title">Contanos sobre tu proyecto</h3>
              <p>Diseñamos un paquete a medida con los servicios del catálogo MUSA. Después recibís un acceso a tu dashboard exclusivo.</p>
            </header>

            <div className="rq-grid">
              <label className="rq-field">
                <span>Nombre completo *</span>
                <input
                  type="text" required
                  value={form.full_name}
                  onChange={ev => set("full_name", ev.target.value)}
                  placeholder="Tu nombre" autoFocus
                />
              </label>
              <label className="rq-field">
                <span>Email *</span>
                <input
                  type="email" required
                  value={form.email}
                  onChange={ev => set("email", ev.target.value)}
                  placeholder="vos@empresa.com"
                />
              </label>
              <label className="rq-field">
                <span>Empresa / Marca</span>
                <input
                  type="text"
                  value={form.company}
                  onChange={ev => set("company", ev.target.value)}
                  placeholder="Opcional"
                />
              </label>
              <label className="rq-field">
                <span>WhatsApp / Teléfono</span>
                <input
                  type="tel"
                  value={form.phone}
                  onChange={ev => set("phone", ev.target.value)}
                  placeholder="+506 …"
                />
              </label>
              <label className="rq-field rq-field-full">
                <span>Contanos tu proyecto</span>
                <textarea
                  rows={4}
                  value={form.project_brief}
                  onChange={ev => set("project_brief", ev.target.value)}
                  placeholder="Tipo de proyecto, fechas tentativas, locaciones, referencias visuales…"
                />
              </label>
              <label className="rq-field rq-field-full">
                <span>Rango de presupuesto</span>
                <select
                  value={form.budget_hint}
                  onChange={ev => set("budget_hint", ev.target.value)}
                >
                  <option value="">Elegí un rango (opcional)</option>
                  <option value="<500k">Menos de ₡500K</option>
                  <option value="500k-1M">₡500K — ₡1M</option>
                  <option value="1M-2.5M">₡1M — ₡2.5M</option>
                  <option value=">2.5M">Más de ₡2.5M</option>
                  <option value="USD">Proyecto internacional (USD)</option>
                  <option value="unsure">Aún no sé</option>
                </select>
              </label>
            </div>

            {state.err && <div className="rq-err">{state.err}</div>}

            <div className="rq-actions">
              <button type="button" className="btn btn-ghost" onClick={close}>Cancelar</button>
              <button
                type="submit"
                className="btn btn-gold"
                disabled={!isValid() || state.status === "loading"}
              >
                {state.status === "loading" ? "Enviando…" : "Enviar solicitud"}
                <span className="arrow"></span>
              </button>
            </div>
            <p className="rq-fine">
              Al enviar aceptás que BWAY PROD te contacte por email para coordinar tu cotización.
              Tus datos quedan privados y no se comparten con terceros.
            </p>
          </form>
        )}
      </div>
    </div>
  );
}

function Footer() {
  const marqueeWords = "DONDE LA MUSA HABITA · LA OBRA SE VUELVE ETERNA";
  return (
    <footer className="footer">
      <div className="footer-marquee" aria-hidden="true">
        <div className="footer-marquee-track">
          {Array.from({ length: 8 }).map((_, i) =>
          <span key={i} className="footer-marquee-item">
              {marqueeWords} <span className="footer-marquee-dot">✦</span>
            </span>
          )}
        </div>
      </div>

      <div className="footer-grid">
        <div>
          <div className="footer-brand">
            BWAY <span className="em">PROD</span>
          </div>
          <p style={{ color: "var(--ink-2)", fontSize: 14, lineHeight: 1.6, maxWidth: 380, margin: 0 }}>
            Productora cinematográfica fundada en San José, Costa Rica. Cobertura nacional e internacional desde 2019. Formalizada en mayo 2026.
          </p>
          <p className="footer-tag">
            "Donde la musa habita,<br />la obra se vuelve eterna."
          </p>
        </div>
        <div>
          <h4>Navegación</h4>
          <ul>
            <li><a href="#services">Servicios</a></li>
            <li><a href="#portfolio">Portafolio</a></li>
            <li><a href="#method">Metodología</a></li>
            <li><a href="#founder">Fundador</a></li>
          </ul>
        </div>
        <div>
          <h4>Contacto</h4>
          <ul>
            <li><a href="https://wa.me/50670470555">WhatsApp · 7047 · 0555</a></li>
            <li><a href="https://instagram.com/bwayprod">Instagram · @bwayprod</a></li>
            <li><a href="https://bwayprod.com">bwayprod.com</a></li>
            <li><a href="mailto:hola@bwayprod.com">hola@bwayprod.com</a></li>
          </ul>
        </div>
      </div>
      <div className="footer-bottom">
        <span>© 2026 BWAY PROD · Todos los derechos reservados</span>
        <span className="footer-status">
          <span className="status-dot"></span>
          Disponible para proyectos · 2026
        </span>
        <span>San José · Costa Rica · 9°56′N 84°05′W</span>
      </div>
    </footer>);

}

function Modal({ pkg, onClose }) {
  useEffect(() => {
    const onKey = (e) => e.key === "Escape" && onClose();
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [onClose]);

  if (!pkg) return null;
  const tierClass = pkg.tier === "LOW" ? "tier-low" : pkg.tier === "MEDIUM" ? "tier-med" : "tier-high";

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <span className="corner-tr"></span>
        <span className="corner-bl"></span>
        <button className="modal-close" onClick={onClose} aria-label="Close">✕</button>

        <div className="eyebrow">Paquete MUSA · {pkg.line}</div>
        <h3>
          <span className="musa">MUSA</span>
          {pkg.name}
        </h3>
        <div style={{ display: "flex", gap: 8, marginTop: 14 }}>
          <span className={"badge " + tierClass}>{pkg.tier}</span>
          {pkg.popular && <span className="badge popular">Más Popular</span>}
        </div>
        <p className="modal-tag">{pkg.tagline}</p>
        <p style={{ color: "var(--ink-2)", fontSize: 15, lineHeight: 1.6, margin: "0 0 28px" }}>
          {pkg.description}
        </p>

        <div className="modal-price-row">
          <div>
            <div className="ccy">DESDE · {pkg.currency}</div>
            <span className="price">{pkg.price}</span>
          </div>
          <div style={{ marginLeft: "auto", fontFamily: "JetBrains Mono", fontSize: 11, letterSpacing: "0.2em", color: "var(--ink-3)", textTransform: "uppercase" }}>
            Línea {pkg.line}
          </div>
        </div>

        <div className="eyebrow" style={{ marginBottom: 18 }}>Incluye</div>
        <ul className="modal-includes">
          {pkg.includes.map((i) => <li key={i}>{i}</li>)}
        </ul>

        <div className="modal-ctas">
          <a href="#" className="btn btn-gold">Agendar llamada <span className="arrow"></span></a>
          <a href="#" className="btn btn-ghost">Cotizar por WhatsApp <span className="arrow"></span></a>
        </div>
        <a href="#" className="modal-link">Ver detalles completos →</a>
      </div>
    </div>);

}

/* ============ APP ============ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "goldPalette": ["#F2A11E", "#D4AF37", "#E9C46A"],
  "headlineFont": "Instrument Serif",
  "bgTone": "pure",
  "grain": 0.06,
  "parallax": 0.4,
  "rhythm": "regular",
  "italicEmphasis": true,
  "showCorners": true,
  "showTimecode": true
}/*EDITMODE-END*/;

const HEADLINE_FONTS = {
  "Instrument Serif": "'Instrument Serif', serif",
  "Anton": "'Anton', 'Arial Narrow', sans-serif",
  "Cormorant Garamond": "'Cormorant Garamond', serif",
  "Playfair Display": "'Playfair Display', serif",
  "Fraunces": "'Fraunces', serif"
};

const BG_TONES = {
  pure:  { bg: "#0A0A0A", bg2: "#0F0F0F" },
  warm:  { bg: "#0B0907", bg2: "#120F0A" },
  cool:  { bg: "#08090C", bg2: "#0D0F13" },
  green: { bg: "#080A09", bg2: "#0C0F0D" }
};

const RHYTHM = {
  compact:  { section: 90,  stats: 40, head: 48 },
  regular:  { section: 140, stats: 56, head: 72 },
  spacious: { section: 200, stats: 84, head: 96 }
};

function applyTweaks(t) {
  const root = document.documentElement;
  const [gold, goldDeep, goldLight] = t.goldPalette || TWEAK_DEFAULTS.goldPalette;
  root.style.setProperty("--gold", gold);
  root.style.setProperty("--gold-deep", goldDeep);
  root.style.setProperty("--gold-light", goldLight);
  root.style.setProperty("--gold-soft", gold + "24");
  root.style.setProperty("--display-font", HEADLINE_FONTS[t.headlineFont] || HEADLINE_FONTS["Instrument Serif"]);
  const tone = BG_TONES[t.bgTone] || BG_TONES.pure;
  root.style.setProperty("--bg", tone.bg);
  root.style.setProperty("--bg-2", tone.bg2);
  root.style.setProperty("--grain-opacity", String(t.grain ?? 0.06));
  const r = RHYTHM[t.rhythm] || RHYTHM.regular;
  root.style.setProperty("--section-pad", r.section + "px");
  root.style.setProperty("--stats-pad", r.stats + "px");
  root.style.setProperty("--head-mb", r.head + "px");
  root.classList.toggle("no-italic-em", !t.italicEmphasis);
  root.classList.toggle("font-anton", t.headlineFont === "Anton");
  root.classList.toggle("no-corners", !t.showCorners);
  root.classList.toggle("no-tc", !t.showTimecode);
}

function ScrollThread() {
  const y = useScrollY();
  const docH = document.documentElement.scrollHeight - window.innerHeight;
  const pct = docH > 0 ? Math.min(1, Math.max(0, y / docH)) : 0;
  return (
    <div className="scroll-thread" aria-hidden="true">
      <div className="thread-line">
        <i className="thread-fill" style={{ height: pct * 100 + "%" }}></i>
      </div>
      <div className="thread-dot" style={{ top: pct * 100 + "%" }}></div>
      <div className="thread-meta">
        <span>{Math.round(pct * 100).toString().padStart(2, "0")}</span>
        <span className="dash"></span>
        <span>BWAY</span>
      </div>
    </div>);

}

// V2.2 — SceneHUD: the narrative spine. A fixed slate that tracks which
// scene of the film you are in, plus global progress. Also sets
// data-scene on <html> so the whole page regrades per scene.
const SCENE_MAP = [
  ["#top", "01", "El valor"],
  ["#origen", "01", "Origen"],
  [".bento-stats", "02", "Impacto"],
  [".musa-immersive", "03", "La musa"],
  ["#method", "03", "El método"],
  ["#services", "04", "Paquetes"],
  ["#portfolio", "05", "Portafolio"],
  ["#founder", "06", "Russell"],
  ["#cta", "07", "Escena final"]];


function SceneHUD() {
  const [scene, setScene] = useState({ n: "01", title: "El valor" });
  const barRef = useRef(null);

  useEffect(() => {
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting && e.target.__scene) {
            const [, n, title] = e.target.__scene;
            setScene({ n, title });
            document.documentElement.dataset.scene = n;
          }
        });
      },
      { rootMargin: "-42% 0px -42% 0px" }
    );
    SCENE_MAP.forEach((m) => {
      const el = document.querySelector(m[0]);
      if (el) {
        el.__scene = m;
        io.observe(el);
      }
    });

    let raf;
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const max = document.documentElement.scrollHeight - window.innerHeight;
        const p = max > 0 ? Math.min(1, window.scrollY / max) : 0;
        if (barRef.current) barRef.current.style.transform = `scaleX(${p})`;
      });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => {
      io.disconnect();
      window.removeEventListener("scroll", onScroll);
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <>
      <div className="scene-tint" aria-hidden="true"></div>
      <div className="scene-hud" aria-hidden="true">
        <div className="scene-hud-row">
          <span className="scene-hud-rec"></span>
          <span className="scene-hud-num">ESC&nbsp;{scene.n}&nbsp;/&nbsp;07</span>
          <span className="scene-hud-sep">·</span>
          <span className="scene-hud-title" key={scene.n + scene.title}>{scene.title}</span>
        </div>
        <div className="scene-hud-bar">
          <i ref={barRef}></i>
        </div>
      </div>
    </>);

}

// =====================================================================
// SHOP — public catalog of digital products (LUTs, presets, packs).
// Data is fetched from Supabase digital_products via anon REST endpoint;
// RLS only exposes is_active=true rows to anon.
// =====================================================================
const BWAY_PRODUCT_KIND_LABEL = {
  lut: "LUT", preset: "Preset", overlay: "Overlay", sound: "Sound design",
  pack: "Pack", template: "Template", tutorial: "Tutorial", other: "Material"
};

function Shop() {
  const [products, setProducts] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const cfg = window.__BWAY_CONFIG__;
    if (!cfg?.SUPABASE_URL || !cfg?.SUPABASE_ANON) { setProducts([]); return; }
    const url = cfg.SUPABASE_URL + "/rest/v1/digital_products?is_active=eq.true&order=is_featured.desc,sort_order.asc,created_at.desc&select=id,slug,name,kind,tagline,description,price_amount,price_currency,price_label,cover_path,preview_url,buy_url,is_featured,meta_specs";
    fetch(url, { headers: { apikey: cfg.SUPABASE_ANON, Authorization: "Bearer " + cfg.SUPABASE_ANON } })
      .then((r) => r.json())
      .then((d) => Array.isArray(d) ? setProducts(d) : setProducts([]))
      .catch((e) => { setError(e?.message || "load_failed"); setProducts([]); });
  }, []);

  const coverUrl = (p) => p?.cover_path
    ? window.__BWAY_CONFIG__.SUPABASE_URL + "/storage/v1/object/public/bway-shop/" + p.cover_path
    : null;
  const priceLabel = (p) => p.price_label
    ? p.price_label
    : `${p.price_currency || "USD"} ${Number(p.price_amount).toFixed(2)}`;

  const ready = products !== null;
  const isEmpty = ready && products.length === 0;

  return (
    <section className="section bway-shop-section" id="shop">
      <div className="container bway-shop-wrap">
        <header className="bway-shop-head" data-reveal>
          <div className="kicker bway-shop-kicker">/ MARKET · MATERIAL DIGITAL</div>
          <h2 className="display bway-shop-title">
            Tu paleta. <em>Lista para grabar.</em>
          </h2>
          <p className="bway-shop-sub">
            LUTs, presets y packs curados por BWAY PROD. Estética cinemática,
            instalación inmediata. Para directores, editores y creadores que
            quieren acelerar sin perder identidad.
          </p>
        </header>

        {!ready ? (
          <div className="bway-shop-loading">Cargando catálogo…</div>
        ) : isEmpty ? (
          <div className="bway-shop-empty">
            <p>Estamos preparando el primer lanzamiento.</p>
            <a className="bway-shop-cta" href="mailto:hola@bway.studio?subject=Notifícame%20cuando%20salga%20el%20material">
              Avísame del lanzamiento <span>→</span>
            </a>
          </div>
        ) : (
        <div className="bway-shop-grid" data-reveal>
          {products.map((p, i) => (
            <article key={p.id}
              className={
                "bway-shop-card" +
                (p.is_featured ? " is-featured" : "") +
                (i === 0 && p.is_featured ? " is-hero" : "")
              }>
              {coverUrl(p)
                ? <div className="bway-shop-cover">
                    <img src={coverUrl(p)} alt={p.name} loading="lazy" />
                    <span className="bway-shop-kind">{BWAY_PRODUCT_KIND_LABEL[p.kind] || p.kind}</span>
                  </div>
                : <div className="bway-shop-cover is-empty">
                    <span className="bway-shop-kind">{BWAY_PRODUCT_KIND_LABEL[p.kind] || p.kind}</span>
                  </div>
              }
              <div className="bway-shop-body">
                <h3 className="bway-shop-name">{p.name}</h3>
                {p.tagline ? <p className="bway-shop-tag">{p.tagline}</p> : null}
                <div className="bway-shop-foot">
                  <span className="bway-shop-price">{priceLabel(p)}</span>
                  {p.buy_url
                    ? <a className="bway-shop-cta" href={p.buy_url} target="_blank" rel="noopener noreferrer">
                        Comprar <span>→</span>
                      </a>
                    : <a className="bway-shop-cta" href={"mailto:hola@bway.studio?subject=" + encodeURIComponent("Compra · " + p.name)}>
                        Comprar <span>→</span>
                      </a>}
                </div>
                {p.preview_url
                  ? <a className="bway-shop-preview" href={p.preview_url} target="_blank" rel="noopener noreferrer">
                      ▶ Ver preview
                    </a>
                  : null}
              </div>
            </article>
          ))}
        </div>
        )}
      </div>
    </section>
  );
}

// =====================================================================
// V45 — BwayShowroom: viewport-height hero with boomerang video bg,
// liquid glass UI, fade-up entrance, and a Now Playing widget.
// Adapted from "quietpress" arch → BWAY identity (gold/black, Fraunces).
// =====================================================================
function BoomerangVideoBg({ src, maxWidth = 960 }) {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const framesRef = useRef([]);
  const [ready, setReady] = useState(false);
  const rafRef = useRef(null);

  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const frames = framesRef.current;
    let stopped = false;

    const capture = () => {
      if (stopped) return;
      const w = Math.min(maxWidth, v.videoWidth || maxWidth);
      const ratio = (v.videoHeight || 540) / (v.videoWidth || 960);
      const h = Math.round(w * ratio);
      const off = document.createElement("canvas");
      off.width = w; off.height = h;
      off.getContext("2d").drawImage(v, 0, 0, w, h);
      frames.push(off);
    };

    const onPlay = () => {
      if (v.requestVideoFrameCallback) {
        const cb = () => {
          if (stopped || v.ended) return;
          capture();
          v.requestVideoFrameCallback(cb);
        };
        v.requestVideoFrameCallback(cb);
      } else {
        const tick = () => {
          if (stopped || v.ended || v.paused) return;
          capture();
          rafRef.current = requestAnimationFrame(tick);
        };
        rafRef.current = requestAnimationFrame(tick);
      }
    };
    const onEnded = () => {
      v.style.display = "none";
      setReady(true);
    };
    v.addEventListener("play", onPlay);
    v.addEventListener("ended", onEnded);
    v.play().catch(() => {
      // If autoplay fails, just leave the video element visible
      setReady(false);
    });
    return () => {
      stopped = true;
      v.removeEventListener("play", onPlay);
      v.removeEventListener("ended", onEnded);
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
    };
  }, [src, maxWidth]);

  useEffect(() => {
    if (!ready) return;
    const c = canvasRef.current;
    if (!c) return;
    const ctx = c.getContext("2d");
    const frames = framesRef.current;
    if (!frames.length) return;
    c.width = frames[0].width;
    c.height = frames[0].height;
    let idx = 0;
    let dir = 1;
    let lastT = 0;
    const fps = 30;
    const interval = 1000 / fps;
    let raf;
    const draw = (t) => {
      if (t - lastT >= interval) {
        ctx.drawImage(frames[idx], 0, 0);
        idx += dir;
        if (idx >= frames.length - 1) { idx = frames.length - 1; dir = -1; }
        if (idx <= 0) { idx = 0; dir = 1; }
        lastT = t;
      }
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(raf);
  }, [ready]);

  return (
    <div className="bw-bg-wrap" aria-hidden="true">
      <video
        ref={videoRef}
        className="bw-bg-video"
        src={src}
        muted
        playsInline
        crossOrigin="anonymous"
        preload="auto"
      />
      <canvas ref={canvasRef} className="bw-bg-canvas" />
      <div className="bw-bg-veil"></div>
    </div>
  );
}

function BwayShowroom() {
  const [liked, setLiked] = useState(false);
  return (
    <section className="bw-showroom" id="showroom">
      <BoomerangVideoBg src="assets/musa-method.mp4" />

      {/* Floating badge */}
      <div className="bw-show-inner">
        <span className="liquid-glass bw-show-badge animate-fade-up delay-1">
          PRESS 04 · MUSA METHOD
        </span>

        <h2 className="bw-show-headline animate-fade-up delay-2">
          Producción cortada para<br />
          <em>marcas que importan.</em>
        </h2>

        <p className="bw-show-sub animate-fade-up delay-3">
          Cinematografía hecha ritual. Cada proyecto se rueda una sola vez —
          la obra que queda después, es para siempre.
        </p>

        <div className="bw-show-ctas animate-fade-up delay-4">
          <a href="#shop" className="bw-show-cta bw-show-cta-primary">
            Explorar el material
          </a>
          <a href="#portfolio" className="bw-show-cta liquid-glass bw-show-cta-ghost">
            Últimos estrenos
          </a>
        </div>
      </div>

      {/* Now Playing widget */}
      <div className="bw-now animate-fade-up delay-5">
        <div className="bw-now-card">
          <div className="bw-now-icon">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
              <path d="M3 3v18h18"/>
              <rect x="7"  y="13" width="3" height="5"/>
              <rect x="12" y="9"  width="3" height="9"/>
              <rect x="17" y="5"  width="3" height="13"/>
            </svg>
          </div>
          <div className="bw-now-body">
            <span className="bw-now-track">Helia Marsh — <em>Fern Light</em></span>
            <div className="bw-now-bar">
              <i className="bw-now-fill" style={{ width: "30%" }}></i>
            </div>
            <div className="bw-now-times">
              <span>0:33</span><span>−1:21</span>
            </div>
          </div>
        </div>
        <div className="bw-now-controls">
          <button type="button" className="bw-now-btn">Prev</button>
          <button
            type="button"
            className={"bw-now-heart" + (liked ? " is-liked" : "")}
            onClick={() => setLiked((v) => !v)}
            aria-label={liked ? "Quitar like" : "Me gusta"}
          >
            <svg viewBox="0 0 24 24" fill={liked ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
            </svg>
          </button>
          <button type="button" className="bw-now-btn">Next</button>
        </div>
      </div>
    </section>
  );
}

// ==========================================================
// PublicQuoteView — vista read-only de una cotización por token
// Accesible sin login en /#/q/<uuid>
// ==========================================================
function PublicQuoteView({ token }) {
  const [data, setData] = useState(null);
  const [err, setErr]   = useState(null);

  useEffect(() => {
    const cfg = window.__BWAY_CONFIG__ || {};
    if (!cfg.SUPABASE_URL || !cfg.SUPABASE_ANON) {
      setErr("Config faltante");
      return;
    }
    fetch(cfg.SUPABASE_URL + "/rest/v1/rpc/get_public_quote", {
      method: "POST",
      headers: {
        "apikey": cfg.SUPABASE_ANON,
        "Authorization": "Bearer " + cfg.SUPABASE_ANON,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ p_token: token })
    })
      .then(r => r.json())
      .then(arr => {
        if (Array.isArray(arr) && arr.length > 0) setData(arr[0]);
        else setErr("Cotización no encontrada o ya no está disponible.");
      })
      .catch(e => setErr(e.message));
  }, [token]);

  function fmtMoney(n, currency) {
    if (n == null || isNaN(n)) return "—";
    const v = Math.round(Number(n));
    if (currency === "USD") return "$" + v.toLocaleString("en-US");
    return "₡" + v.toLocaleString("es-CR");
  }

  if (err) {
    return (
      <div className="pq-screen">
        <div className="pq-card pq-error">
          <h2>No pudimos cargar esta cotización</h2>
          <p>{err}</p>
          <a className="btn btn-gold" href="/">Ir al sitio</a>
        </div>
      </div>);
  }

  if (!data) {
    return (
      <div className="pq-screen">
        <div className="pq-card">
          <div className="pq-loading">Cargando cotización…</div>
        </div>
      </div>);
  }

  const subtotal = Number(data.subtotal || 0);
  const discount = subtotal * (Number(data.discount_pct) || 0) / 100;
  const tax = (subtotal - discount) * (Number(data.tax_pct) || 0) / 100;
  const total = Number(data.total || 0);

  return (
    <div className="pq-screen">
      <div className="pq-grain" aria-hidden="true"></div>
      <div className="pq-card">
        <header className="pq-header">
          <div className="pq-brand">
            <span className="pq-kicker">/ COTIZACIÓN · BWAY PROD</span>
            <h1>BWAY PROD</h1>
            <p>Cinematografía que posiciona marca</p>
          </div>
          <div className={"pq-status pq-status-" + data.status}>
            {data.status === "sent"     ? "ENVIADA"   :
             data.status === "approved" ? "APROBADA"  :
             data.status === "rejected" ? "RECHAZADA" : data.status?.toUpperCase()}
          </div>
        </header>

        <h2 className="pq-title">{data.title}</h2>
        <div className="pq-meta">
          <div>
            <span className="pq-meta-k">Cliente</span>
            <b>{data.client_name || "—"}</b>
          </div>
          <div>
            <span className="pq-meta-k">Fecha</span>
            <b>{new Date(data.created_at).toLocaleDateString("es-CR", { day: "numeric", month: "long", year: "numeric" })}</b>
          </div>
          {data.expires_at &&
            <div>
              <span className="pq-meta-k">Vence</span>
              <b>{new Date(data.expires_at).toLocaleDateString("es-CR", { day: "numeric", month: "long", year: "numeric" })}</b>
            </div>}
        </div>

        <section className="pq-items">
          <header className="pq-items-head">
            <span>Servicio</span>
            <span className="pq-th-qty">Cant.</span>
            <span className="pq-th-price">Precio</span>
            <span className="pq-th-sub">Subtotal</span>
          </header>
          {(data.line_items || []).map((li, i) =>
            <div key={i} className="pq-line">
              <span className="pq-line-name">{li.name}</span>
              <span className="pq-line-qty">{li.qty}</span>
              <span className="pq-line-price">{fmtMoney(li.unit_price, data.currency)}</span>
              <span className="pq-line-sub">{fmtMoney(li.qty * li.unit_price, data.currency)}</span>
            </div>
          )}
        </section>

        <section className="pq-totals">
          <div className="pq-tot-row"><span>Subtotal</span><b>{fmtMoney(subtotal, data.currency)}</b></div>
          {discount > 0 && <div className="pq-tot-row"><span>Descuento {data.discount_pct}%</span><b className="pq-neg">− {fmtMoney(discount, data.currency)}</b></div>}
          {tax > 0      && <div className="pq-tot-row"><span>Impuesto {data.tax_pct}%</span><b>+ {fmtMoney(tax, data.currency)}</b></div>}
          <div className="pq-tot-final">
            <span>TOTAL</span>
            <b>{fmtMoney(total, data.currency)}</b>
          </div>
        </section>

        {data.notes && <section className="pq-notes">
          <h3>Notas</h3>
          <p>{data.notes}</p>
        </section>}

        <footer className="pq-footer">
          <p className="pq-quote">"El valor no está en lo que dura el evento, está en lo que queda después."</p>
          <a href="/" className="pq-link">bwayprod.vip</a>
        </footer>
      </div>
    </div>);
}

function App() {
  // ⚠️ All hooks must be declared BEFORE any conditional return (React rules).
  const [openPkg, setOpenPkg] = useState(null);
  const [loading, setLoading] = useState(true);
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRoute] = useState(() => window.location.hash);

  useReveal();

  useEffect(() => {
    const h = () => setRoute(window.location.hash);
    window.addEventListener("hashchange", h);
    return () => window.removeEventListener("hashchange", h);
  }, []);

  useEffect(() => { applyTweaks(t); }, [t]);

  useEffect(() => {
    document.body.style.overflow = loading ? "hidden" : "";
    if (!loading) document.documentElement.classList.add("app-ready");
    return () => { document.body.style.overflow = ""; };
  }, [loading]);

  // Public quote share route: #/q/<token> — checked AFTER all hooks
  const publicQuoteMatch = route.match(/^#\/q\/([0-9a-f-]{36})$/i);
  if (publicQuoteMatch) {
    return <PublicQuoteView token={publicQuoteMatch[1]} />;
  }

  return (
    <>
      {loading && <LoadingScreen onComplete={() => setLoading(false)} />}
      <PageBgVideo tweaks={t} />
      <Atmosphere />
      <GoldDust />
      <Nav />
      <ScrollThread />
      <SceneHUD />
      <Hero tweaks={t} />
      <OrigenStory />
      <Stats />
      <MusaImmersive />
      <Pillars />
      <MusaMethod />
      <ServicesSwitch onOpen={setOpenPkg} tweaks={t} />
      <Portfolio />
      <Shop />
      <Founder />
      <BwayShowroom />
      <CTA />
      <Footer />
      {openPkg && <Modal pkg={openPkg} onClose={() => setOpenPkg(null)} />}

      <TweaksPanel>
        <TweakSection label="Paleta · Oro" />
        <TweakColor
          label="Gold tones"
          value={t.goldPalette}
          options={[
            ["#F2A11E", "#D4AF37", "#E9C46A"],
            ["#F0A500", "#D4AF37", "#E9C46A"],
            ["#E5C24A", "#B8932C", "#F2DA8C"],
            ["#C97B3C", "#A0501F", "#E8A172"],
            ["#D4D4D4", "#9A9A9A", "#EFEFEF"],
            ["#BFA76A", "#8B7530", "#E0CE9C"]]
          }
          onChange={(v) => setTweak("goldPalette", v)} />


        <TweakSection label="Fondo" />
        <TweakRadio
          label="Tono"
          value={t.bgTone}
          options={["pure", "warm", "cool", "green"]}
          onChange={(v) => setTweak("bgTone", v)} />

        <TweakSlider
          label="Grano de película"
          value={t.grain}
          min={0}
          max={0.18}
          step={0.01}
          onChange={(v) => setTweak("grain", v)} />


        <TweakSection label="Tipografía" />
        <TweakSelect
          label="Display font"
          value={t.headlineFont}
          options={Object.keys(HEADLINE_FONTS)}
          onChange={(v) => setTweak("headlineFont", v)} />

        <TweakToggle
          label="Énfasis itálico dorado"
          value={t.italicEmphasis}
          onChange={(v) => setTweak("italicEmphasis", v)} />


        <TweakSection label="Movimiento · Ritmo" />
        <TweakSlider
          label="Parallax"
          value={t.parallax}
          min={0}
          max={0.9}
          step={0.05}
          onChange={(v) => setTweak("parallax", v)} />

        <TweakRadio
          label="Densidad"
          value={t.rhythm}
          options={["compact", "regular", "spacious"]}
          onChange={(v) => setTweak("rhythm", v)} />


        <TweakSection label="Detalles cinemáticos" />
        <TweakToggle
          label="Esquinas de viewfinder"
          value={t.showCorners}
          onChange={(v) => setTweak("showCorners", v)} />

        <TweakToggle
          label="Timecode + REC"
          value={t.showTimecode}
          onChange={(v) => setTweak("showTimecode", v)} />

      </TweaksPanel>
    </>);

}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);