// app.jsx — Carlos & Laura, Save the Date

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

function PaperTextureProvider({ children }) {return children;}
function PaperTextureFX() {return null;}

// ─────────────────────────────────────────────────────────────
// Tweakable defaults — host edits this block in place.
// ─────────────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "aesthetic": "editorial",
  "particleDensity": 35,
  "showPhoto": true,
  "petalStyle": "petals"
} /*EDITMODE-END*/;

// ─────────────────────────────────────────────────────────────
// Event constants
// ─────────────────────────────────────────────────────────────
const EVENT = {
  names: "Carlos & Laura",
  monogram: "C·L",
  // 29 May 2027, 12:00 local
  startISO: "2027-05-29T12:00:00",
  endISO: "2027-05-30T00:00:00",
  dateLabel: "Sábado, 29 de mayo de 2027",
  dateShort: "29·05·2027",
  timeLabel: "12:00 h",
  mapsUrl: "https://www.google.com/maps/place/Finca+El+Tribunal/@28.0232751,-15.5150067,825m/data=!3m2!1e3!4b1!4m6!3m5!1s0xc4091a9395300ed:0xb9e3a9b2b0633e87!8m2!3d28.0232751!4d-15.5150067!16s%2Fg%2F11s957hqdz?entry=ttu&g_ep=EgoyMDI2MDUxMy4wIKXMDSoASAFQAw%3D%3D",
  locationLine: "Finca el Tribunal, Gran Canaria",
  locationSub: "Ceremonia y celebración",
  monthYear: "Mayo · 2027"
};

// ─────────────────────────────────────────────────────────────
// Particles — drifting golden dust in background
// ─────────────────────────────────────────────────────────────
function Particles({ count }) {
  const items = useMemo(() => {
    return Array.from({ length: count }, (_, i) => {
      const left = Math.random() * 100;
      const dx = (Math.random() - 0.5) * 180;
      const dur = 14 + Math.random() * 18;
      const delay = -Math.random() * dur;
      const size = 1 + Math.random() * 3;
      const op = 0.25 + Math.random() * 0.5;
      return { i, left, dx, dur, delay, size, op };
    });
  }, [count]);
  return (
    <div className="particles" aria-hidden="true">
      {items.map((p) =>
      <span
        key={p.i}
        className="particle"
        style={{
          left: `${p.left}%`,
          "--dx": `${p.dx}px`,
          animationDuration: `${p.dur}s`,
          animationDelay: `${p.delay}s`,
          width: `${p.size}px`,
          height: `${p.size}px`,
          opacity: p.op
        }} />

      )}
    </div>);

}

// ─────────────────────────────────────────────────────────────
// Petals — burst when envelope opens
// ─────────────────────────────────────────────────────────────
function Petals({ active, style }) {
  const colors = style === "gold" ?
  ["#cdd9e6", "#a8bdd2", "#7e9ab8", "#3f5a80"] :
  style === "hearts" ?
  ["#3f5a80", "#2e4a72", "#7e9ab8", "#b8cce0"] :
  ["#eef2f8", "#cdd9e6", "#a8bdd2", "#7e9ab8", "#3f5a80", "#b8cce0"];
  const items = useMemo(() => {
    return Array.from({ length: 28 }, (_, i) => {
      const angle = Math.random() * Math.PI * 2;
      const dist = 200 + Math.random() * 380;
      const dx = Math.cos(angle) * dist;
      const dy = Math.sin(angle) * dist - 80;
      const rot = (Math.random() - 0.5) * 720;
      const dur = 1.4 + Math.random() * 1.2;
      const delay = Math.random() * 0.25;
      const c = colors[i % colors.length];
      const sx = 0.7 + Math.random() * 0.8;
      const sy = sx * (0.8 + Math.random() * 0.6);
      return { i, dx, dy, rot, dur, delay, c, sx, sy };
    });
  }, [style]);
  if (!active) return null;
  return (
    <div className="petals" aria-hidden="true">
      {items.map((p) =>
      <span
        key={p.i}
        className="petal"
        style={{
          "--c": p.c,
          transform: `translate(-50%, -50%)`,
          animation: `petalFly-${p.i} ${p.dur}s var(--ease-out) ${p.delay}s forwards`,
          width: `${10 * p.sx}px`,
          height: `${16 * p.sy}px`
        }} />

      )}
      <style>{items.map((p) => `
        @keyframes petalFly-${p.i} {
          0% { transform: translate(-50%, -50%) rotate(0deg) scale(0.6); opacity: 0; }
          15% { opacity: 1; }
          100% { transform: translate(calc(-50% + ${p.dx}px), calc(-50% + ${p.dy}px)) rotate(${p.rot}deg) scale(1); opacity: 0; }
        }
      `).join("")}</style>
    </div>);

}

// ─────────────────────────────────────────────────────────────
// Countdown to the day
// ─────────────────────────────────────────────────────────────
function useCountdown(targetISO) {
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);
  const target = new Date(targetISO).getTime();
  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);
  return { d, h, m, s };
}

function pad(n) {return String(n).padStart(2, "0");}

// ─────────────────────────────────────────────────────────────
// Calendar helpers
// ─────────────────────────────────────────────────────────────
function downloadIcs() {
  const fmt = (iso) => iso.replace(/[-:]/g, "");
  const stamp = new Date().toISOString().replace(/[-:.]/g, "").slice(0, 15) + "Z";
  const lines = [
    "BEGIN:VCALENDAR",
    "VERSION:2.0",
    "CALSCALE:GREGORIAN",
    "PRODID:-//Carlos & Laura//Boda 2027//ES",
    "BEGIN:VEVENT",
    "UID:boda-carlos-laura-20270529@wedding",
    `DTSTAMP:${stamp}`,
    `DTSTART:${fmt(EVENT.startISO)}`,
    `DTEND:${fmt(EVENT.endISO)}`,
    "SUMMARY:Boda de Carlos & Laura",
    "DESCRIPTION:¡Reserva la fecha! Invitación formal a seguir.",
    `LOCATION:${EVENT.locationLine}\\n${EVENT.mapsUrl}`,
    "BEGIN:VALARM",
    "ACTION:DISPLAY",
    "DESCRIPTION:Boda mañana",
    "TRIGGER:-P1D",
    "END:VALARM",
    "END:VEVENT",
    "END:VCALENDAR",
  ].join("\r\n");
  const blob = new Blob([lines], { type: "text/calendar;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "boda-carlos-laura.ics";
  a.click();
  URL.revokeObjectURL(url);
}

function CalendarButton() {
  return (
    <button className="btn btn-primary" onClick={downloadIcs}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
        <rect x="3" y="5" width="18" height="16" rx="2" />
        <path d="M3 9h18M8 3v4M16 3v4" />
      </svg>
      Añadir al calendario
    </button>
  );
}

// ─────────────────────────────────────────────────────────────
// Inside card — the revealed content
// ─────────────────────────────────────────────────────────────

const PHOTOS = [
  { src: "assets/photo-hands.jpeg",   objectPosition: "50% 35%" },
  { src: "assets/photo-alhambra.jpeg", objectPosition: "50% 40%" },
  { src: "assets/photo-dog.jpeg",      objectPosition: "55% 20%" },
];

function useMobile() {
  const [mobile, setMobile] = useState(
    () => typeof window !== "undefined" && window.matchMedia("(max-width: 520px)").matches
  );
  useEffect(() => {
    const mq = window.matchMedia("(max-width: 520px)");
    const h = (e) => setMobile(e.matches);
    mq.addEventListener("change", h);
    return () => mq.removeEventListener("change", h);
  }, []);
  return mobile;
}

function Polaroid({ caption, src, objectPosition }) {
  return (
    <div className="polaroid">
      <div className="polaroid-img">
        <img src={src} alt={caption} style={objectPosition ? { objectPosition } : undefined} />
      </div>
      <div className="polaroid-cap">{caption}</div>
    </div>);
}

// Stacked swipeable polaroid carousel — used on mobile
function PolaroidStack() {
  const n = PHOTOS.length;
  const [active, setActive] = useState(0);
  const [dragX, setDragX] = useState(0);
  const [dragging, setDragging] = useState(false);
  const startX = useRef(null);
  const startY = useRef(null);
  const wrapRef = useRef(null);

  const go = (dir) => setActive((i) => (i + dir + n) % n);

  // Non-passive listener so preventDefault works; only block horizontal swipes.
  useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const onMove = (e) => {
      if (startX.current === null) return;
      const dx = e.touches[0].clientX - startX.current;
      const dy = e.touches[0].clientY - startY.current;
      if (Math.abs(dx) > Math.abs(dy)) {
        e.preventDefault();
        setDragX(dx);
      }
    };
    el.addEventListener("touchmove", onMove, { passive: false });
    return () => el.removeEventListener("touchmove", onMove);
  }, []);

  const onTouchStart = (e) => {
    startX.current = e.touches[0].clientX;
    startY.current = e.touches[0].clientY;
    setDragging(true);
  };
  const onTouchEnd = () => {
    if (Math.abs(dragX) > 50) go(dragX < 0 ? 1 : -1);
    setDragX(0);
    setDragging(false);
    startX.current = null;
    startY.current = null;
  };

  const TILTS  = [-3, 5, -6];
  const LAYERS = [
    { scale: 1,    y: 0,  opacity: 1   },
    { scale: 0.93, y: 14, opacity: 1   },
    { scale: 0.86, y: 28, opacity: 0.6 },
  ];

  return (
    <div ref={wrapRef} className="polaroid-stack-wrap"
         onTouchStart={onTouchStart}
         onTouchEnd={onTouchEnd}>
      <div className="polaroid-stack">
        {PHOTOS.map(({ src, objectPosition }, i) => {
          const pos    = (i - active + n) % n;
          const isTop  = pos === 0;
          const { scale, y, opacity } = LAYERS[pos];
          const tx     = isTop ? dragX : 0;
          const rotate = TILTS[i] + (isTop ? dragX / 22 : 0);
          return (
            <div key={i} className="polaroid"
                 style={{
                   position: "absolute",
                   top: 0, left: 0, right: 0,
                   transform: `rotate(${rotate}deg) translateX(${tx}px) translateY(${y}px) scale(${scale})`,
                   zIndex: n - pos,
                   opacity,
                   transition: isTop && dragging
                     ? "none"
                     : "transform 0.45s cubic-bezier(0.22,0.61,0.36,1), opacity 0.35s",
                 }}>
              <div className="polaroid-img">
                <img src={src} alt="" style={objectPosition ? { objectPosition } : undefined} />
              </div>
              <div className="polaroid-cap" />
            </div>
          );
        })}
      </div>
      <div className="stack-dots" role="tablist" aria-label="Seleccionar foto">
        {PHOTOS.map((_, i) => (
          <button key={i} role="tab" aria-selected={i === active}
                  className={`stack-dot${i === active ? " active" : ""}`}
                  onClick={() => { setActive(i); resetAuto(); }}
                  aria-label={`Foto ${i + 1}`} />
        ))}
      </div>
    </div>
  );
}

function Card({ shown, onClose, showPhoto }) {
  const cd = useCountdown(EVENT.startISO);
  const isMobile = useMobile();
  return (
    <div className={`card-stage ${shown ? "is-shown" : ""}`} aria-hidden={!shown}>
      <div className="card" role="dialog" aria-label="Detalles de la boda" data-comment-anchor="dd6323fb08-div-178-7">
        <button className="close-btn" onClick={onClose} aria-label="Cerrar">×</button>
        <div className="card-inner">

          <div className="eyebrow">
            <span className="eyebrow-line">Save the Date</span>
          </div>

          <div className="name-block">
            <span className="sparkle s-1" aria-hidden="true"><svg viewBox="0 0 32 32" fill="currentColor"><path d="M16 2 L18 14 L30 16 L18 18 L16 30 L14 18 L2 16 L14 14 Z" /></svg></span>
            <span className="sparkle s-2" aria-hidden="true"><svg viewBox="0 0 32 32" fill="currentColor"><path d="M16 2 L18 14 L30 16 L18 18 L16 30 L14 18 L2 16 L14 14 Z" /></svg></span>
            <span className="sparkle s-3" aria-hidden="true"><svg viewBox="0 0 32 32" fill="currentColor"><path d="M16 2 L18 14 L30 16 L18 18 L16 30 L14 18 L2 16 L14 14 Z" /></svg></span>
            <span className="sparkle s-4" aria-hidden="true"><svg viewBox="0 0 32 32" fill="currentColor"><path d="M16 2 L18 14 L30 16 L18 18 L16 30 L14 18 L2 16 L14 14 Z" /></svg></span>
            <h1 className="names" style={{ fontFamily: "\"Segoe Script\", \"Lucida Handwriting\", \"Allura\", cursive", fontStyle: "normal" }} data-comment-anchor="30d0999150-h1-191-13">
              Carlos &amp; Laura
            </h1>
          </div>

          {showPhoto && (
            isMobile
              ? <PolaroidStack />
              : <div className="polaroids" aria-label="Fotos de los novios">
                  <Polaroid src="assets/photo-hands.jpeg" caption="" objectPosition="50% 35%" />
                  <Polaroid src="assets/photo-alhambra.jpeg" caption="" objectPosition="50% 40%" />
                  <Polaroid src="assets/photo-dog.jpeg" caption="" objectPosition="55% 45%" />
                </div>
          )}

          <div className="divider" aria-hidden="true">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2">
              <path d="M12 4v16M4 12h16M7 7l10 10M17 7L7 17" />
            </svg>
          </div>

          <div className="details">
            <div className="detail-row">
              <div className="detail-label">Cuándo</div>
              <div className="detail-value">
                {EVENT.dateLabel}
                <em>boda de día</em>
              </div>
            </div>
            <div className="detail-row">
              <div className="detail-label">Dónde</div>
              <div className="detail-value">
                {EVENT.locationLine}
                <em>{EVENT.locationSub}</em>
              </div>
            </div>
          </div>

          <div className="countdown" aria-label="Cuenta atrás hasta la boda">
            <div className="cd-cell"><div className="cd-num">{cd.d}</div><div className="cd-lbl">Días</div></div>
            <div className="cd-cell"><div className="cd-num">{pad(cd.h)}</div><div className="cd-lbl">Horas</div></div>
            <div className="cd-cell"><div className="cd-num">{pad(cd.m)}</div><div className="cd-lbl">Minutos</div></div>
            <div className="cd-cell"><div className="cd-num">{pad(cd.s)}</div><div className="cd-lbl">Segundos</div></div>
          </div>

          <div className="actions">
            <CalendarButton />
            <a className="btn" href={EVENT.mapsUrl} target="_blank" rel="noopener noreferrer">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
                <path d="M12 22s7-7.5 7-13a7 7 0 1 0-14 0c0 5.5 7 13 7 13z" />
                <circle cx="12" cy="9" r="2.5" />
              </svg>
              Ver ubicación
            </a>
          </div>

          <div className="faq">
            <div className="faq-title">Preguntas frecuentes</div>
            <div className="faq-list">
              <div className="faq-item">
                <div className="faq-q">¿Hay aparcamiento en la finca?</div>
                <div className="faq-a">Sí</div>
              </div>
              <div className="faq-item">
                <div className="faq-q">¿Habrá guagua desde el centro?</div>
                <div className="faq-a">Sí</div>
              </div>
              <div className="faq-item">
                <div className="faq-q">¿Puedo llevar acompañante?</div>
                <div className="faq-a">Sí</div>
              </div>
            </div>
          </div>

          <div className="footnote">Invitación formal - próximamente</div>

        </div>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────
// Envelope — the closed scene
// ─────────────────────────────────────────────────────────────
function Envelope({ state, onOpen }) {
  const handleKey = (e) => {
    if (e.key === "Enter" || e.key === " ") {e.preventDefault();onOpen();}
  };
  const cls = state === "opening" || state === "open" ? "is-opening" : "";
  const fadeCls = state === "open" ? "is-open" : "";

  return (
    <div className={`scene ${cls} ${fadeCls}`}>
      <div className="envelope">
        <div className="card-slot">
          <div className="card-mini">
            <div className="card-mini-inner">
              Save the Date
              <em>Carlos &amp; Laura</em>
              <span className="card-mini-date">{EVENT.dateShort}</span>
            </div>
          </div>
        </div>
        <div className="env-body">
          <div className="env-paper-fx" aria-hidden="true">
            <PaperTextureFX
              width="100%" height="100%"
              colorBack="#ffffff" colorFront="#9fadbc"
              contrast={0.27} roughness={0.85}
              fiber={0.3} fiberSize={0.2}
              crumples={0.3} crumpleSize={0.35}
              folds={0.65} foldCount={5}
              drops={0.2} fade={0}
              seed={5.8} scale={0.6} fit="cover" />
          </div>
        </div>
        <div className="save-the-date env-stamp" aria-hidden="true">
          <span className="std-text">SAVE&nbsp;&nbsp;THE&nbsp;&nbsp;DATE</span>
          <svg className="std-sprig" viewBox="0 0 60 12" aria-hidden="true">
            <line x1="6" y1="6" x2="54" y2="6" stroke="currentColor" strokeWidth="0.6" />
            <circle cx="30" cy="6" r="1.4" fill="currentColor" />
            <path d="M22 6 q4 -4 8 0 q-4 4 -8 0 z M38 6 q-4 -4 -8 0 q4 4 8 0 z" fill="currentColor" opacity="0.85" />
          </svg>
        </div>
        <div className="tap-hint" aria-hidden="true">toca para abrir</div>
        <div className="env-flap">
          <div className="env-paper-fx env-paper-fx--flap" aria-hidden="true">
            <PaperTextureFX
              width="100%" height="100%"
              colorBack="#ffffff" colorFront="#7e8fa3"
              contrast={0.27} roughness={0.85}
              fiber={0.3} fiberSize={0.2}
              crumples={0.3} crumpleSize={0.35}
              folds={0.55} foldCount={4}
              drops={0.2} fade={0}
              seed={3.2} scale={0.55} fit="cover" />
          </div>
          <div className="env-flap-face" />
          <div className="env-flap-back" />
        </div>
        <button
          className="seal-wrap"
          onClick={onOpen}
          onKeyDown={handleKey}
          aria-label="Abrir invitación"
          disabled={state !== "closed"}>
          <img className="seal-img" src="sello.png" alt="Sello de cera con el monograma C&L" draggable="false" />
          <svg className="seal-svg" viewBox="0 0 200 200" aria-hidden="true" style={{ display: 'none' }}>
            <defs>
              {/* Organic edge displacement */}
              <filter id="waxEdge" x="-15%" y="-15%" width="130%" height="130%">
                <feTurbulence type="fractalNoise" baseFrequency="0.018" numOctaves="2" seed="7" result="noise" />
                <feDisplacementMap in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="G" />
              </filter>
              {/* Surface micro-grain */}
              <filter id="waxGrain" x="0" y="0" width="100%" height="100%">
                <feTurbulence type="fractalNoise" baseFrequency="1.4" numOctaves="2" seed="3" />
                <feColorMatrix values="0 0 0 0 0.32  0 0 0 0 0.22  0 0 0 0 0.06  0 0 0 0.55 0" />
                <feComposite in2="SourceGraphic" operator="in" />
              </filter>
              {/* Inner relief — for the embossed C&L stamp */}
              <filter id="waxDeboss" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur in="SourceAlpha" stdDeviation="0.8" result="blur" />
                <feSpecularLighting in="blur" surfaceScale="2" specularConstant="0.9" specularExponent="22" lightingColor="#fff5d8" result="spec">
                  <feDistantLight azimuth="135" elevation="55" />
                </feSpecularLighting>
                <feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip" />
                <feMerge>
                  <feMergeNode in="SourceGraphic" />
                  <feMergeNode in="specClip" />
                </feMerge>
              </filter>
              {/* Molten gold gradient */}
              <radialGradient id="waxFill" cx="38%" cy="30%" r="78%">
                <stop offset="0%" stopColor="#fbe9b3" />
                <stop offset="18%" stopColor="#ecc678" />
                <stop offset="48%" stopColor="#c79a4b" />
                <stop offset="78%" stopColor="#8b6730" />
                <stop offset="100%" stopColor="#3e2c12" />
              </radialGradient>
              {/* Specular highlight */}
              <radialGradient id="waxShine" cx="32%" cy="24%" r="22%">
                <stop offset="0%" stopColor="#fff8d6" stopOpacity="0.85" />
                <stop offset="60%" stopColor="#ffe9b0" stopOpacity="0.25" />
                <stop offset="100%" stopColor="#ffe9b0" stopOpacity="0" />
              </radialGradient>
              {/* Inner well shadow */}
              <radialGradient id="waxWell" cx="50%" cy="50%" r="50%">
                <stop offset="55%" stopColor="#000" stopOpacity="0" />
                <stop offset="100%" stopColor="#2a1a06" stopOpacity="0.55" />
              </radialGradient>
            </defs>

            {/* Organic wax disc */}
            <g filter="url(#waxEdge)">
              <circle cx="100" cy="100" r="78" fill="url(#waxFill)" />
            </g>
            {/* Inner darkening to suggest depression */}
            <circle cx="100" cy="100" r="74" fill="url(#waxWell)" style={{ mixBlendMode: "multiply" }} />
            {/* Pressed border ring */}
            <circle cx="100" cy="100" r="62" fill="none" stroke="rgba(48,30,8,0.45)" strokeWidth="1.2" />
            <circle cx="100" cy="100" r="62" fill="none" stroke="rgba(255,232,170,0.32)" strokeWidth="0.6" strokeDasharray="2 4" />
            {/* Surface grain overlay */}
            <circle cx="100" cy="100" r="74" fill="#e0bd72" filter="url(#waxGrain)" opacity="0.55" style={{ mixBlendMode: "multiply" }} />
            {/* Specular shine */}
            <ellipse cx="78" cy="62" rx="34" ry="22" fill="url(#waxShine)" transform="rotate(-22 78 62)" />

            {/* Hand-stamped monogram */}
            <g className="seal-mono-svg" filter="url(#waxDeboss)">
              <text x="68" y="120" className="seal-glyph seal-glyph--c" textAnchor="middle">C</text>
              <text x="100" y="116" className="seal-glyph seal-glyph--amp" textAnchor="middle">&amp;</text>
              <text x="132" y="124" className="seal-glyph seal-glyph--l" textAnchor="middle">L</text>
            </g>
          </svg>
        </button>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────
// Tweaks
// ─────────────────────────────────────────────────────────────
function Tweaks({ t, setTweak }) {
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Estética" />
      <TweakRadio
        label="Tipografía"
        value={t.aesthetic}
        options={[
        { value: "classical", label: "Clásica" },
        { value: "editorial", label: "Editorial" },
        { value: "romantic", label: "Romántica" },
        { value: "modern", label: "Moderna" }]
        }
        onChange={(v) => setTweak("aesthetic", v)} />
      
      <TweakSection label="Ambiente" />
      <TweakSlider
        label="Partículas"
        value={t.particleDensity}
        min={0} max={120} step={5}
        onChange={(v) => setTweak("particleDensity", v)} />
      
      <TweakRadio
        label="Confeti"
        value={t.petalStyle}
        options={[
        { value: "petals", label: "Pétalos" },
        { value: "gold", label: "Dorado" },
        { value: "hearts", label: "Corazones" }]
        }
        onChange={(v) => setTweak("petalStyle", v)} />
      
      <TweakSection label="Tarjeta" />
      <TweakToggle
        label="Mostrar foto"
        value={t.showPhoto}
        onChange={(v) => setTweak("showPhoto", v)} />
      
    </TweaksPanel>);

}

// ─────────────────────────────────────────────────────────────
// App
// ─────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  // state: closed → opening → open → (close) → closed
  const [state, setState] = useState("closed");
  const [petalsActive, setPetalsActive] = useState(false);

  useEffect(() => {
    document.body.dataset.aesthetic = t.aesthetic;
  }, [t.aesthetic]);

  const handleOpen = useCallback(() => {
    if (state !== "closed") return;
    setState("opening");
    setPetalsActive(true);
    // sequence: flap+seal animate ~1s, card slides ~1s, then reveal full card
    setTimeout(() => setState("open"), 1500);
    setTimeout(() => setPetalsActive(false), 3000);
  }, [state]);

  const handleClose = useCallback(() => {
    setState("closed");
  }, []);

  return (
    <div className="stage" data-comment-anchor="6ceaad5547-div-394-5">
      <Particles count={t.particleDensity} />
      <Envelope state={state} onOpen={handleOpen} />
      {false &&
      <div className="hint">Toca el sello para abrir</div>
      }
      <Petals active={petalsActive} style={t.petalStyle} />
      <Card shown={state === "open"} onClose={handleClose} showPhoto={t.showPhoto} />
      <Tweaks t={t} setTweak={setTweak} />
    </div>);

}

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