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

/* =========================================================================
   CONFIG — el usuario edita esto. Cuando tengas el video, pon su ruta en
   VIDEO_SRC (ej. "noche.mp4"). Para música, pon la canción en AUDIO_SRC.
   ========================================================================= */
const VIDEO_SRC = "noche 2.mp4";     // ← pega aquí la ruta del video de noche estrellada
const AUDIO_SRC = "uploads/in%20love%20with%20life.mp3"; // ← canción de fondo

// Fotos de la quinceañera. Crea una carpeta "fotos" junto a este archivo y
// pon ahí tus imágenes. Si una falta, se muestra un marcador elegante.
const FOTOS = {
  portada: "uploads/portada.jpg",               // ← foto principal (portada)
  // Marcas por foto:
  //   feature -> foto grande destacada (con esquinas doradas)
  //   wide    -> foto horizontal a todo lo ancho
  //   gold    -> contorno dorado resaltado
  // El resto se muestra en celdas verticales, en filas de 2.
  // Orden 1→16 tal cual; solo se marca el tratamiento según la orientación:
  // la 1 abre sola (casi cuadrada -> destacada) y las horizontales (3 paisaje,
  // 14 casi cuadrada) rompen la fila; el resto (verticales) van en pares.
  galeria: [
    { src: "uploads/1.jpg",  feature: true, gold: true }, // apertura, sola (2630×2592)
    { src: "uploads/2.jpg" },                             // vertical
    { src: "uploads/3.jpg",  wide: true, gold: true },    // paisaje 6240×4160
    { src: "uploads/4.jpg" },                             // vertical
    { src: "uploads/6.jpg" },                             // vertical  ┐ par
    { src: "uploads/7.jpg" },                             // vertical  ┘
    { src: "uploads/8.jpg" },                             // vertical  ┐ par
    { src: "uploads/9.jpg" },                             // vertical  ┘
    { src: "uploads/10.jpg" },                            // vertical  ┐ par
    { src: "uploads/11.jpg" },                            // vertical  ┘
    { src: "uploads/12.jpg" },                            // vertical  ┐ par
    { src: "uploads/13.jpg" },                            // vertical  ┘
    { src: "uploads/14.jpg", feature: true },             // casi cuadrada 1817×1765
    { src: "uploads/15.jpg" },                            // vertical  ┐ par
    { src: "uploads/16.jpg", solo: true },                // vertical sola en una columna
  ],
};

// Arma la galería en bloques: las fotos "feature" (grandes) y "wide" (horizontales)
// rompen la fila y se muestran a todo lo ancho por su cuenta; el resto se agrupa
// en filas de 2 fotos verticales. Una fila con una sola foto se centra (solo).
function buildGalleryBlocks(fotos) {
  const blocks = [];
  let row = [];
  const flushRow = () => {
    if (row.length) { blocks.push({ type: "row", photos: row }); row = []; }
  };
  fotos.forEach((photo) => {
    if (photo.feature) {
      flushRow();
      blocks.push({ type: "feature", photo });
    } else if (photo.wide) {
      flushRow();
      blocks.push({ type: "wide", photo });
    } else {
      row.push(photo);
      if (row.length === 2) flushRow();
    }
  });
  flushRow();
  return blocks;
}
const GALLERY_BLOCKS = buildGalleryBlocks(FOTOS.galeria);

// Datos del evento (genéricos — cámbialos tú)
const EVENT = {
  nombre: "Hannah Isabella",
  evento: "Mis XV Años",
  fechaISO: "2026-09-26T19:00:00",
  fechaTexto: "Sábado 26 de Septiembre, 2026",
  recepcion: {
    lugar: "Centro de Eventos Coopeve",
    dir: "Salón Valle de la Luna",
    hora: "7:00 PM",
    mapa: "https://maps.app.goo.gl/poPRmj37EdSNpra39",
  },
  // Padres — reemplaza por los nombres reales
  padres: ["Walter Guerra", "Johanna Arosemena"],
  // Itinerario de la noche — edita horas, títulos y (opcional) descripción
  itinerario: [
    { hora: "7:00 PM",  titulo: "Recepción de invitados",   desc: "Las estrellas comienzan a brillar y los sueños dan la bienvenida a esta historia." },
    { hora: "8:00 PM",  titulo: "Entrada de la quinceañera", desc: "Como una pincelada de luz en el cielo, inicia la celebración." },
    { hora: "9:00 PM",  titulo: "Cena bajo las estrellas",   desc: "Un momento para compartir risas, arte y felicidad." },
    { hora: "10:00 PM", titulo: "Un deseo bajo las estrellas",        desc: "Un momento mágico para celebrar la vida y pedir un deseo rodeada de amor y felicidad." },
    { hora: "10:30 PM", titulo: "Hora loca estrellada",      desc: "Colores, música y energía que iluminan la noche." },
    { hora: "11:30 PM", titulo: "Baile sin fin",             desc: "La noche continúa, como un cielo que nunca deja de brillar." },
  ],
  // WhatsApp: cambia el número (formato internacional sin +) y el mensaje
  whatsapp: {
    numero: "50762211159",
    mensaje: "¡Hola! Confirmo mi asistencia a los XV años de Hannah Isabella. 🌟",
  },
};

const waLink = `https://wa.me/${EVENT.whatsapp.numero}?text=${encodeURIComponent(EVENT.whatsapp.mensaje)}`;

/* =========================================================================
   TWEAKS
   ========================================================================= */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": ["#e8a317", "#ffd76a", "#05060e"],
  "nameFont": "tangerine",
  "starDensity": 1,
  "showMoon": false,
  "vignette": 0.66,
  "smoothing": 0.12,
  "quote": "En esta noche azul, puedo tocar las estrellas con mis manos, puedo pedirlas prestadas al cielo y entregarlas una por una a mis familiares más queridos, y a mis amigos más sinceros, tú eres uno de ellos."
}/*EDITMODE-END*/;

const NAME_FONTS = {
  "serif-italic": '"Cormorant Garamond", serif',
  "script": '"Pinyon Script", cursive',
  "tangerine": '"Tangerine", cursive',
};

/* =========================================================================
   STARFIELD — capas de estrellas con box-shadow (CSS puro, animadas)
   ========================================================================= */
function makeStars(n, maxX, maxY, maxR) {
  let out = [];
  for (let i = 0; i < n; i++) {
    const x = (Math.random() * maxX).toFixed(0);
    const y = (Math.random() * maxY).toFixed(0);
    const r = (Math.random() * maxR + 0.4).toFixed(1);
    out.push(`${x}px ${y}px 0 ${r}px rgba(255,255,255,${(0.5 + Math.random() * 0.5).toFixed(2)})`);
  }
  return out.join(",");
}

function Star({ shadow, dur, depth, scrollRef }) {
  const ref = useRef(null);
  useEffect(() => {
    let raf;
    const tick = () => {
      const el = ref.current;
      if (el) el.style.transform = `translateY(${-(scrollRef.current * depth)}px)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [depth]);
  return (
    <div
      ref={ref}
      className="sky-layer star-layer"
      style={{
        boxShadow: shadow,
        width: 1, height: 1,
        left: 0, top: 0,
        animation: `twinkle ${dur}s ease-in-out infinite alternate`,
      }}
    />
  );
}

function Sky({ density, showMoon, scrollRef }) {
  const layers = useMemo(() => {
    const W = Math.max(window.innerWidth, 480);
    const H = window.innerHeight;
    const f = Math.max(0.15, density);
    return [
      { shadow: makeStars(Math.round(90 * f), W, H, 1.1), dur: 3.5, depth: 0.04 },
      { shadow: makeStars(Math.round(60 * f), W, H, 1.7), dur: 5, depth: 0.09 },
      { shadow: makeStars(Math.round(28 * f), W, H, 2.4), dur: 7, depth: 0.16 },
    ];
  }, [density]);

  const moonRef = useRef(null);
  useEffect(() => {
    let raf;
    const tick = () => {
      if (moonRef.current) moonRef.current.style.transform = `translateY(${-(scrollRef.current * 0.06)}px)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  return (
    <div className="sky-fallback">
      {layers.map((l, i) => (
        <Star key={i + "-" + density} shadow={l.shadow} dur={l.dur} depth={l.depth} scrollRef={scrollRef} />
      ))}
      {showMoon && <div className="moon" ref={moonRef} />}
      <div className="shooting-star" />
    </div>
  );
}

/* =========================================================================
   LOOP VIDEO — el video corre siempre en bucle, de fondo (sin scroll).
   ========================================================================= */
function useLoopVideo(videoRef) {
  useEffect(() => {
    const video = videoRef.current;
    if (!video || !VIDEO_SRC) return;

    const onReady = () => video.classList.add("is-ready");

    video.src = VIDEO_SRC;
    video.loop = true;
    video.muted = true;
    video.defaultMuted = true;
    // iOS viejo/navegadores embebidos (WhatsApp, Instagram) aún miran este atributo
    video.setAttribute("webkit-playsinline", "true");
    video.load();

    // Algunos navegadores móviles (ahorro de datos/batería, in-app browsers)
    // rechazan el autoplay aunque esté muted+playsinline. Si play() falla,
    // reintentamos en el primer toque/scroll del usuario y al volver de fondo,
    // así el video nunca se queda congelado en el primer fotograma sin aviso.
    let resumeBound = false;
    const bindResumeOnInteraction = () => {
      if (resumeBound) return;
      resumeBound = true;
      const resume = () => {
        tryPlay();
        document.removeEventListener("touchstart", resume);
        document.removeEventListener("click", resume);
        document.removeEventListener("scroll", resume);
      };
      document.addEventListener("touchstart", resume, { passive: true, once: true });
      document.addEventListener("click", resume, { once: true });
      document.addEventListener("scroll", resume, { passive: true, once: true });
    };

    const tryPlay = () => {
      const p = video.play();
      if (p && p.catch) p.catch(bindResumeOnInteraction);
    };

    const onVisibility = () => {
      if (!document.hidden && video.paused) tryPlay();
    };

    video.addEventListener("loadeddata", onReady);
    video.addEventListener("canplay", tryPlay);
    document.addEventListener("visibilitychange", onVisibility);
    if (video.readyState >= 2) { onReady(); tryPlay(); }

    return () => {
      video.removeEventListener("loadeddata", onReady);
      video.removeEventListener("canplay", tryPlay);
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, []);
}

/* =========================================================================
   PRELOADER — oculta la pantalla de carga cuando el video de fondo está listo
   (o de inmediato si no hay video). MAX_MS es un seguro: si el video falla o
   tarda demasiado, igual revela la app sobre el cielo CSS de respaldo.
   ========================================================================= */
function usePreloader(videoRef) {
  useEffect(() => {
    const MIN_MS = 380;
    const MAX_MS = 4500;
    const mountedAt = Date.now();
    let done = false;

    const hide = () => {
      if (done) return;
      done = true;
      const el = document.getElementById("preloader");
      if (!el) return;
      const wait = Math.max(0, MIN_MS - (Date.now() - mountedAt));
      setTimeout(() => {
        el.classList.add("is-done");
        setTimeout(() => el.remove(), 700);
      }, wait);
    };

    if (!VIDEO_SRC) { hide(); return; }

    const video = videoRef.current;
    const failsafe = setTimeout(hide, MAX_MS);
    if (video) {
      if (video.readyState >= 2) hide();
      else {
        video.addEventListener("loadeddata", hide);
        video.addEventListener("error", hide);
      }
    }
    return () => {
      clearTimeout(failsafe);
      if (video) {
        video.removeEventListener("loadeddata", hide);
        video.removeEventListener("error", hide);
      }
    };
  }, []);
}

/* =========================================================================
   REVEAL on scroll
   ========================================================================= */
function useReveal() {
  useEffect(() => {
    let raf;
    // initial=true: revela todo lo que esté dentro de la primera pantalla, para que la
    // portada completa (incl. "Mis XV Años") se vea al cargar sin esperar scroll.
    // En scroll usamos un umbral adelantado (0.86) para que la entrada se sienta suave.
    const check = (initial) => {
      const h = window.innerHeight;
      const limit = initial ? h : h * 0.86;
      document.querySelectorAll(".reveal:not(.in)").forEach((el) => {
        const r = el.getBoundingClientRect();
        if (r.top < limit && r.bottom > 0) el.classList.add("in");
      });
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => check(false)); };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    // pasadas iniciales (Babel/fuentes pueden reacomodar el layout tras montar)
    check(true);
    const t1 = setTimeout(() => check(true), 120);
    const t2 = setTimeout(() => check(true), 500);
    // recalcula cuando las fuentes (Tangerine) terminan de cargar y reposicionan el texto
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(() => check(true));
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      cancelAnimationFrame(raf);
      clearTimeout(t1); clearTimeout(t2);
    };
  }, []);
}

/* =========================================================================
   COUNTDOWN
   ========================================================================= */
function Countdown({ iso }) {
  const target = useMemo(() => new Date(iso).getTime(), [iso]);
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);
  const diff = Math.max(0, target - now);
  const d = Math.floor(diff / 86400000);
  const h = Math.floor((diff % 86400000) / 3600000);
  const m = Math.floor((diff % 3600000) / 60000);
  const s = Math.floor((diff % 60000) / 1000);
  const cells = [
    [d, "Días"], [h, "Horas"], [m, "Min"], [s, "Seg"],
  ];
  return (
    <div className="countdown">
      {cells.map(([n, lab], i) => (
        <div className="cd-cell" key={i}>
          <span className="cd-num">{String(n).padStart(2, "0")}</span>
          <span className="cd-lab">{lab}</span>
        </div>
      ))}
    </div>
  );
}

/* =========================================================================
   MUSIC BUTTON
   ========================================================================= */
function MusicButton() {
  const [playing, setPlaying] = useState(false);

  // Reproducir desde el inicio. Los navegadores bloquean el autoplay con sonido,
  // así que: 1) intentamos al cargar; 2) si falla, arrancamos en la primera
  // interacción del usuario (toque/clic/scroll/tecla).
  useEffect(() => {
    if (!AUDIO_SRC) return;
    const a = document.getElementById("bg-audio");
    if (!a) return;
    if (!a.src) a.src = AUDIO_SRC;
    a.volume = 0.7;

    const sync = () => setPlaying(!a.paused);
    a.addEventListener("play", sync);
    a.addEventListener("pause", sync);

    let unlocked = false;
    const tryPlay = () => {
      if (unlocked) return;
      const p = a.play();
      if (p && p.then) {
        p.then(() => {
          unlocked = true;
          removeListeners();
        }).catch(() => {});
      }
    };
    const events = ["pointerdown", "touchstart", "click", "keydown", "scroll"];
    const onInteract = () => tryPlay();
    const removeListeners = () => events.forEach((e) =>
      window.removeEventListener(e, onInteract));

    tryPlay(); // intento inmediato (funciona si el navegador lo permite)
    events.forEach((e) => window.addEventListener(e, onInteract, { passive: true }));

    return () => {
      a.removeEventListener("play", sync);
      a.removeEventListener("pause", sync);
      removeListeners();
    };
  }, []);

  const toggle = () => {
    const a = document.getElementById("bg-audio");
    if (!AUDIO_SRC) { setPlaying((p) => !p); return; } // visual only hasta tener canción
    if (!a.src) a.src = AUDIO_SRC;
    if (playing) { a.pause(); setPlaying(false); }
    else { a.play().then(() => setPlaying(true)).catch(() => setPlaying(false)); }
  };
  return (
    <button className={"music-btn" + (playing ? " playing" : "")} onClick={toggle}
            aria-label={playing ? "Silenciar música" : "Activar música"}>
      {playing ? (
        <div className="eq"><span></span><span></span><span></span><span></span></div>
      ) : (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
          <path d="M9 18V6l10-2v12" strokeLinecap="round" strokeLinejoin="round"/>
          <circle cx="6" cy="18" r="3"/><circle cx="16" cy="16" r="3"/>
        </svg>
      )}
    </button>
  );
}

/* =========================================================================
   GATE SKY — cielo animado de la pantalla de entrada: capas de estrellas que
   titilan + estrellas fugaces que cruzan en diagonal.
   ========================================================================= */
function GateSky() {
  const layers = useMemo(() => {
    const W = Math.max(window.innerWidth, 480);
    const H = Math.max(window.innerHeight, 600);
    return [
      { shadow: makeStars(70, W, H, 1.1), dur: 3.6 },
      { shadow: makeStars(46, W, H, 1.7), dur: 5.2 },
      { shadow: makeStars(22, W, H, 2.5), dur: 7 },
    ];
  }, []);
  const shoots = [
    { top: "12%", left: "2%",  "--tx": "62vw", "--ty": "42vh", "--rot": "30deg", "--dur": "5.5s", "--delay": "0.6s" },
    { top: "4%",  left: "46%", "--tx": "34vw", "--ty": "52vh", "--rot": "48deg", "--dur": "7s",   "--delay": "2.8s" },
    { top: "20%", left: "-8%", "--tx": "82vw", "--ty": "34vh", "--rot": "22deg", "--dur": "6.2s", "--delay": "4.6s" },
  ];
  return (
    <div className="gate-sky" aria-hidden="true">
      {layers.map((l, i) => (
        <div key={i} className="gate-star-layer"
             style={{ boxShadow: l.shadow, animationDuration: l.dur + "s" }} />
      ))}
      {shoots.map((s, i) => (
        <span key={"s" + i} className="gate-shoot" style={s} />
      ))}
    </div>
  );
}

/* =========================================================================
   ENTER GATE — primera pantalla. El toque del usuario desbloquea la música
   (los navegadores exigen una interacción para reproducir audio con sonido).
   ========================================================================= */
function EnterGate({ onEnter }) {
  const [leaving, setLeaving] = useState(false);
  const [gone, setGone] = useState(false);

  const enter = () => {
    if (onEnter) onEnter();
    const a = document.getElementById("bg-audio");
    if (a && AUDIO_SRC) {
      if (!a.src) a.src = AUDIO_SRC;
      a.volume = 0.7;
      a.play().catch(() => {});
    }
    // intenta también dar sonido/play al video de fondo si lo hubiera
    setLeaving(true);
    setTimeout(() => setGone(true), 850);
  };

  if (gone) return null;
  return (
    <div className={"enter-gate" + (leaving ? " is-leaving" : "")} onClick={enter} role="button" aria-label="Abrir invitación">
      <GateSky />
      <p className="enter-gate__phrase">Entre luces, estrellas y emociones, llega una noche que guardaré para siempre en mi corazón. Tu presencia hará que este sueño sea aún más especial.</p>
      <span className="enter-gate__btn">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
          <path d="M9 18V6l10-2v12" strokeLinecap="round" strokeLinejoin="round"/>
          <circle cx="6" cy="18" r="3"/><circle cx="16" cy="16" r="3"/>
        </svg>
        Toca para abrir
      </span>
    </div>
  );
}

/* =========================================================================
   SECTIONS
   ========================================================================= */
function Ornament() {
  return (
    <div className="ornament reveal">
      <span className="line"></span>
      <span className="dot">✦</span>
      <span className="line r"></span>
    </div>
  );
}

function VenueCard({ data, label }) {
  return (
    <div className="card reveal">
      <p className="label">{label}</p>
      <p className="sub"><strong>{data.lugar}</strong><br/>{data.dir}<br/>{data.hora}</p>
      <a className="map-link" href={data.mapa} target="_blank" rel="noopener">
        <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.6">
          <path d="M12 21s-7-6.3-7-11a7 7 0 1 1 14 0c0 4.7-7 11-7 11Z"/><circle cx="12" cy="10" r="2.4"/>
        </svg>
        Ver mapa
      </a>
    </div>
  );
}

/* =========================================================================
   FOTO — marco con imagen; si falta el archivo muestra un marcador elegante
   ========================================================================= */
function Photo({ src, alt, className = "", style, imgStyle }) {
  const [failed, setFailed] = useState(false);
  const ok = src && !failed;
  return (
    <div className={"frame " + className + (ok ? "" : " is-empty")} style={style}>
      {ok ? (
        <img src={src} alt={alt} loading="lazy" style={imgStyle} onError={() => setFailed(true)} />
      ) : (
        <div className="frame__hint">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
            <rect x="3" y="5" width="18" height="14" rx="2" />
            <path d="m3 16 5-5 4 4 3-3 6 6" strokeLinecap="round" strokeLinejoin="round" />
            <circle cx="9" cy="9" r="1.3" />
          </svg>
          <span>Tu foto aquí</span>
        </div>
      )}
    </div>
  );
}

/* =========================================================================
   APP
   ========================================================================= */
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const videoRef = useRef(null);
  const scrollRef = useRef(0);
  const progressRef = useRef(null);
  const [gateDismissed, setGateDismissed] = useState(false);
  const [scrollUnlocked, setScrollUnlocked] = useState(false);

  useLoopVideo(videoRef);
  usePreloader(videoRef);
  useReveal();

  useEffect(() => {
    if (scrollUnlocked) return;

    const html = document.documentElement;
    const body = document.body;
    const prevHtmlOverflow = html.style.overflow;
    const prevBodyOverflow = body.style.overflow;
    const prevHtmlTouchAction = html.style.touchAction;
    const prevBodyTouchAction = body.style.touchAction;

    html.style.overflow = "hidden";
    body.style.overflow = "hidden";
    html.style.touchAction = "none";
    body.style.touchAction = "none";

    return () => {
      html.style.overflow = prevHtmlOverflow;
      body.style.overflow = prevBodyOverflow;
      html.style.touchAction = prevHtmlTouchAction;
      body.style.touchAction = prevBodyTouchAction;
    };
  }, [scrollUnlocked]);

  // scroll tracking (for parallax + progress)
  useEffect(() => {
    let raf;
    const tick = () => {
      scrollRef.current = window.scrollY;
      const max = document.documentElement.scrollHeight - window.innerHeight;
      const p = max > 0 ? window.scrollY / max : 0;
      if (progressRef.current) progressRef.current.style.width = (p * Math.min(window.innerWidth, 460)) + "px";
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  // apply palette + tweaks to CSS vars
  const [accent, accentSoft, bg] = t.palette;
  useEffect(() => {
    const r = document.documentElement.style;
    r.setProperty("--accent", accent);
    r.setProperty("--accent-soft", accentSoft);
    r.setProperty("--bg", bg);
    r.setProperty("--name-font", NAME_FONTS[t.nameFont] || NAME_FONTS["serif-italic"]);
    r.setProperty("--vignette", String(t.vignette));
  }, [accent, accentSoft, bg, t.nameFont, t.vignette]);

  const nameMod = { script: " name--script", tangerine: " name--script name--tangerine" };
  const nameClass = "name" + (nameMod[t.nameFont] || "");

  return (
    <React.Fragment>
      <div className="progress" ref={progressRef}></div>
      {!gateDismissed && <EnterGate onEnter={() => { setScrollUnlocked(true); setGateDismissed(true); }} />}
      <MusicButton />
      {gateDismissed && (
        <p className="signature">
          Creado por <a href="https://instagram.com/apesoft" target="_blank" rel="noopener">Apesoft</a>
        </p>
      )}

      <div className={"stage" + (VIDEO_SRC ? " has-video" : "")}>
        {VIDEO_SRC && (
          <video ref={videoRef} className="stage__video"
                 muted loop autoPlay playsInline preload="auto" />
        )}
        <Sky density={t.starDensity} showMoon={t.showMoon} scrollRef={scrollRef} />
        <div className="vignette"></div>
      </div>

      <main>
        {/* 1 · APERTURA */}
        <section className="section stagger">
          <p className="kicker gold-shine reveal" style={{ "--d": "0ms" }}>Te invito a celebrar</p>
          <div className="portrait-wrap reveal" style={{ "--d": "140ms" }}>
            <Photo src={FOTOS.portada} className="portrait" alt={"Foto de " + EVENT.nombre} />
          </div>
          <h1 className={nameClass + " reveal"} style={{ "--d": "260ms", fontSize: "clamp(44px,14vw,96px)" }}>{EVENT.nombre}</h1>
          <Ornament />
          <p className="kicker gold-shine reveal" style={{ "--d": "420ms", marginTop: 8, marginBottom: 0 }}>{EVENT.evento}</p>
          <div className="scroll-hint">
            Desliza
            <span className="chev"></span>
          </div>
        </section>

        {/* 1.2 · FRASE — después de la foto de bienvenida y el nombre */}
        <section className="section">
          <p className="quote reveal">“{t.quote}”</p>
        </section>

        {/* 1.5 · PADRES */}
        <section className="section">
          <div className="card card--lg reveal">
            <p className="label" style={{ marginBottom: 20 }}>Con la bendición de Dios y de la mano de mis padres</p>
            <div className="parents">
              <div className="parents__group">
                <p className="parents__names" style={{ marginTop: 10 }}>{EVENT.padres[0]}</p>
              </div>
              <div className="parents__div">
                <span className="line"></span><span className="dot">✦</span><span className="line r"></span>
              </div>
              <div className="parents__group">
                <p className="parents__names">{EVENT.padres[1]}</p>
              </div>
            </div>
          </div>
        </section>

        {/* 3 · FECHA + COUNTDOWN */}
        <section className="section stagger">
          <p className="label gold-shine reveal" style={{ "--d": "0ms" }}>Reserva la fecha</p>
          <h2 className="big-date reveal" style={{ "--d": "120ms" }}>{EVENT.fechaTexto}</h2>
          <p className="sub reveal" style={{ "--d": "200ms" }}>{EVENT.recepcion.hora} · ¡Cuenta regresiva!</p>
          <div className="reveal" style={{ "--d": "300ms" }}>
            <Countdown iso={EVENT.fechaISO} />
          </div>
        </section>

        {/* 5 · RECEPCIÓN */}
        <section className="section">
          <VenueCard data={EVENT.recepcion} label="Recepción" />
        </section>

        {/* 5.5 · ITINERARIO — constelación de la noche */}
        <section className="section stagger">
          <p className="label gold-shine reveal" style={{ "--d": "0ms" }}>Una noche estrellada de XV años</p>
          <h2 className="big-date reveal" style={{ "--d": "120ms", fontSize: "clamp(30px,8vw,52px)" }}>Itinerario</h2>
          <p className="sub reveal" style={{ "--d": "180ms", fontStyle: "italic", marginTop: 6 }}>Inspirado en la magia de Vincent Van Gogh</p>
          <div className="timeline reveal" style={{ "--d": "220ms" }}>
            {EVENT.itinerario.map((it, i) => (
              <div className="tl-item" key={i}>
                <p className="tl-time">{it.hora}</p>
                <p className="tl-title">{it.titulo}</p>
                {it.desc && <p className="tl-desc">{it.desc}</p>}
              </div>
            ))}
          </div>
        </section>

        {/* 6 · VESTIMENTA */}
        <section className="section stagger">
          <p className="label gold-shine reveal" style={{ "--d": "0ms" }}>Código de vestimenta</p>
          <h2 className="big-date reveal" style={{ "--d": "120ms", fontSize: "clamp(30px,8vw,52px)" }}>Semiformal</h2>
          <p className="sub reveal" style={{ "--d": "200ms", maxWidth: "22em" }}>
             Te invitamos a vestir con elegancia para celebrar una noche mágica inspirada en La noche estrellada de Van Gogh.
          </p>
          <p className="sub reveal" style={{ "--d": "200ms", maxWidth: "22em" }}>
            Con cariño, les pedimos reservar estos tonos para la
            quinceañera: <strong>azul noche</strong>, <strong>azul claro</strong>,
            <strong> amarillo</strong>, <strong>dorado</strong> y <strong>plateado</strong>.
          </p>
          <div className="dress-colors reveal" style={{ "--d": "300ms" }}>
            <span className="swatch" style={{ background: "#16244d" }} title="Azul noche"></span>
            <span className="swatch" style={{ background: "#7fb0e0" }} title="Azul claro"></span>
            <span className="swatch" style={{ background: "#f2cf4a" }} title="Amarillo"></span>
            <span className="swatch" style={{ background: "#c9a24b" }} title="Dorado"></span>
            <span className="swatch" style={{ background: "#c8ccd4" }} title="Plateado"></span>
          </div>
        </section>

        {/* 6.5 · REGALOS — Lluvia de Sobres */}
        <section className="section">
          <div className="card reveal" style={{ textAlign: "center" }}>
            <div style={{ color: "var(--accent)", marginBottom: 14 }}>
              <svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" strokeWidth="1.4" style={{ filter: "drop-shadow(0 0 12px rgba(232,201,138,0.4))" }}>
                <rect x="3" y="6" width="18" height="13" rx="1.5" />
                <path d="m3.5 7 8.5 7 8.5-7" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </div>
            <p className="label" style={{ marginBottom: 16 }}>Regalos</p>
            <p className="sub" style={{ margin: 0, maxWidth: "22em" }}>
              Mi mayor regalo es contar con <strong>tu presencia</strong> en este día tan especial.
            </p>
            <p className="sub" style={{ marginTop: 14, maxWidth: "22em" }}>
              Si deseas acompañarme con un detalle adicional, puedes hacerlo de la siguiente manera:
            </p>
            <p className="big-date" style={{ fontSize: "clamp(26px,7vw,42px)", margin: "22px 0 6px" }}>Lluvia de Sobres</p>
            <div className="parents__div" style={{ margin: "18px 0" }}>
              <span className="line"></span><span className="dot">✦</span><span className="line r"></span>
            </div>
            <p className="sub" style={{ margin: 0, fontStyle: "italic", maxWidth: "22em" }}>
              Gracias por ser parte de uno de los momentos más importantes de mi vida.
            </p>
          </div>
        </section>

        {/* 6.6 · GALERÍA — después de la lluvia de sobres */}
        <section className="section stagger">
          <p className="label gold-shine reveal" style={{ "--d": "0ms" }}>Recuerdos</p>
          <h2 className="big-date reveal" style={{ "--d": "120ms", fontSize: "clamp(30px,8vw,52px)" }}>Mis momentos</h2>
          <div className="gallery reveal" style={{ "--d": "220ms" }}>
            {GALLERY_BLOCKS.map((block, i) => {
              const gold = (p) => (p.gold ? " photo--gold" : "");
              const focusStyle = (p) => (p.focusTop ? { objectPosition: "50% 20%" } : undefined);
              if (block.type === "feature") {
                return <Photo key={i} src={block.photo.src} className={"photo--feature" + gold(block.photo)} imgStyle={focusStyle(block.photo)} alt="Recuerdo" />;
              }
              if (block.type === "wide") {
                return <Photo key={i} src={block.photo.src} className={"photo--wide" + gold(block.photo)} alt="Recuerdo" />;
              }
              const solo = block.photos.length === 1;
              return (
                <div className={"gallery__row" + (solo ? " gallery__row--solo" : "")} key={i}>
                  {block.photos.map((photo, j) => (
                    <Photo key={j} src={photo.src} className={"photo--portrait" + gold(photo)} alt="Recuerdo" />
                  ))}
                </div>
              );
            })}
          </div>
        </section>

        {/* 7 · RSVP */}
        <section className="section stagger">
          <p className="kicker gold-shine reveal" style={{ "--d": "0ms" }}>Tu presencia es mi mejor regalo</p>
          <h2 className="big-date reveal" style={{ "--d": "120ms" }}>Confirma tu asistencia</h2>
          <p className="sub reveal" style={{ "--d": "200ms", maxWidth: "20em" }}>
            Toca el botón y envíame un mensaje por WhatsApp para reservar tu lugar entre las estrellas.
          </p>
          <a className="btn btn--solid reveal" href={waLink} target="_blank" rel="noopener" style={{ "--d": "300ms" }}>
            <svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor">
              <path d="M12.04 2c-5.5 0-9.96 4.46-9.96 9.96 0 1.76.46 3.45 1.34 4.95L2 22l5.25-1.38a9.9 9.9 0 0 0 4.79 1.22c5.5 0 9.96-4.46 9.96-9.96S17.54 2 12.04 2Zm5.84 14.06c-.25.7-1.45 1.34-2 1.42-.53.08-1.18.11-1.9-.12-.44-.14-1-.33-1.73-.64-3.04-1.31-5.02-4.37-5.18-4.57-.15-.2-1.24-1.65-1.24-3.14s.78-2.23 1.06-2.54c.28-.3.6-.38.8-.38.2 0 .4 0 .58.01.18.01.44-.07.68.52.25.6.85 2.08.92 2.23.07.15.12.32.02.52-.1.2-.15.32-.3.5-.15.17-.31.39-.44.52-.15.15-.3.31-.13.6.17.3.76 1.25 1.63 2.02 1.12.99 2.06 1.3 2.36 1.45.3.15.47.12.64-.07.17-.2.74-.86.94-1.16.2-.3.4-.25.67-.15.27.1 1.72.81 2.01.96.3.15.5.22.57.35.07.12.07.72-.18 1.42Z"/>
            </svg>
            Confirmar por WhatsApp
          </a>
        </section>

        {/* 8 · CIERRE */}
        <section className="section stagger">
          <h1 className={nameClass + " reveal"} style={{ "--d": "0ms", fontSize: "clamp(56px,18vw,128px)" }}>{EVENT.nombre}</h1>
          <Ornament />
          <p className="footer-credit reveal" style={{ "--d": "220ms" }}>{EVENT.fechaTexto}</p>
        </section>
      </main>

      {/* TWEAKS */}
      <TweaksPanel>
        <TweakSection label="Color" />
        <TweakColor label="Acentos" value={t.palette}
          options={[
            ["#e8a317", "#ffd76a", "#05060e"],
            ["#cfd6e6", "#eef2fb", "#05060e"],
            ["#c9a9e6", "#ecdcfb", "#0a0716"],
            ["#e6a9c2", "#fbdcea", "#140710"],
          ]}
          onChange={(v) => setTweak("palette", v)} />
        <TweakSlider label="Oscurecer fondo" value={t.vignette} min={0} max={0.85} step={0.05}
          onChange={(v) => setTweak("vignette", v)} />

        <TweakSection label="Tipografía del nombre" />
        <TweakRadio label="Estilo" value={t.nameFont}
          options={["serif-italic", "script", "tangerine"]}
          onChange={(v) => setTweak("nameFont", v)} />

        <TweakSection label="Cielo" />
        <TweakSlider label="Estrellas" value={t.starDensity} min={0.2} max={2} step={0.1}
          onChange={(v) => setTweak("starDensity", v)} />
        <TweakToggle label="Mostrar luna" value={t.showMoon}
          onChange={(v) => setTweak("showMoon", v)} />
        <TweakSlider label="Suavizado del video" value={t.smoothing} min={0.02} max={0.3} step={0.01}
          onChange={(v) => setTweak("smoothing", v)} />

        <TweakSection label="Texto" />
        <TweakText label="Frase" value={t.quote}
          onChange={(v) => setTweak("quote", v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

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