/* global React, ReactDOM, gsap, ScrollTrigger, MotionPathPlugin */
/* eslint-disable */
const { useState, useEffect, useMemo, useRef, useCallback } = React;
/* ============================================================
GSAP plugin registration
============================================================ */
if (typeof gsap !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
if (typeof MotionPathPlugin !== "undefined") gsap.registerPlugin(MotionPathPlugin);
}
/* ============================================================
Storage helpers
============================================================ */
// ─── Cole aqui a URL do seu Google Apps Script após criar o webhook ───────────
const SHEETS_WEBHOOK_URL = "https://script.google.com/macros/s/AKfycbzIAjhNhyIZ2Cm17wcraZkfHMbhC-j1CCbpAE-RjW32G0CjRaJDBrpGzUxUml4gyhQ4EQ/exec";
// ─────────────────────────────────────────────────────────────────────────────
const STORAGE_KEY = "mariany_rsvps_v1";
function loadRsvps() {
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); }
catch { return []; }
}
function saveRsvp(r) {
const entry = { ...r, id: Date.now(), createdAt: new Date().toISOString() };
// Salva localmente
const all = loadRsvps();
all.unshift(entry);
localStorage.setItem(STORAGE_KEY, JSON.stringify(all));
// Envia para o Google Sheets (se configurado)
if (SHEETS_WEBHOOK_URL) {
fetch(SHEETS_WEBHOOK_URL, {
method: "POST",
mode: "no-cors",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(entry),
}).catch(() => {});
}
}
async function fetchRsvpsFromSheets() {
if (!SHEETS_WEBHOOK_URL) return null;
try {
const res = await fetch(SHEETS_WEBHOOK_URL + "?action=list");
if (!res.ok) return null;
return await res.json();
} catch { return null; }
}
/* ============================================================
Tweaks defaults
============================================================ */
const TWEAK_DEFAULTS = {
palette: "blush",
showFlyingButterflies: true,
showParticles: true,
particleDensity: 28,
videoLabel: "Mariany",
};
const PALETTES = {
blush: {
name: "Blush Garden",
"--blush-50": "oklch(0.985 0.008 10)",
"--blush-100": "oklch(0.965 0.018 12)",
"--blush-200": "oklch(0.93 0.04 10)",
"--blush-300": "oklch(0.88 0.07 10)",
"--blush-400": "oklch(0.82 0.11 10)",
"--candy-500": "oklch(0.74 0.16 12)",
"--rose-600": "oklch(0.62 0.18 14)",
"--rose-700": "oklch(0.48 0.16 16)",
"--rose-900": "oklch(0.28 0.10 18)",
"--lilac-200": "oklch(0.92 0.035 320)",
},
candy: {
name: "Candy Bloom",
"--blush-50": "oklch(0.98 0.012 350)",
"--blush-100": "oklch(0.96 0.026 350)",
"--blush-200": "oklch(0.92 0.05 350)",
"--blush-300": "oklch(0.86 0.09 350)",
"--blush-400": "oklch(0.78 0.14 350)",
"--candy-500": "oklch(0.7 0.2 350)",
"--rose-600": "oklch(0.58 0.22 0)",
"--rose-700": "oklch(0.44 0.18 5)",
"--rose-900": "oklch(0.26 0.12 8)",
"--lilac-200": "oklch(0.9 0.05 310)",
},
pearl: {
name: "Pearl Petal",
"--blush-50": "oklch(0.99 0.004 20)",
"--blush-100": "oklch(0.975 0.012 20)",
"--blush-200": "oklch(0.95 0.025 20)",
"--blush-300": "oklch(0.91 0.05 20)",
"--blush-400": "oklch(0.86 0.08 18)",
"--candy-500": "oklch(0.78 0.12 15)",
"--rose-600": "oklch(0.66 0.14 18)",
"--rose-700": "oklch(0.52 0.12 20)",
"--rose-900": "oklch(0.32 0.08 22)",
"--lilac-200": "oklch(0.94 0.025 320)",
},
};
function applyPalette(key) {
const p = PALETTES[key] || PALETTES.blush;
const root = document.documentElement;
Object.entries(p).forEach(([k, v]) => {
if (k.startsWith("--")) root.style.setProperty(k, v);
});
}
/* ============================================================
GSAP reveal hook — replaces IntersectionObserver
============================================================ */
function useGsapReveal(active) {
useEffect(() => {
if (!active || typeof gsap === "undefined") return;
// Immediately reveal elements in view
const revealEls = document.querySelectorAll(".reveal:not(.in)");
revealEls.forEach((el) => {
ScrollTrigger.create({
trigger: el,
start: "top 88%",
onEnter: () => {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 1.1,
ease: "power3.out",
overwrite: "auto",
});
el.classList.add("in");
},
once: true,
});
// Reveal immediately if already in viewport
const rect = el.getBoundingClientRect();
if (rect.top < window.innerHeight * 0.95) {
gsap.to(el, { opacity: 1, y: 0, duration: 1.1, ease: "power3.out", overwrite: "auto" });
el.classList.add("in");
}
});
}, [active]);
}
/* ============================================================
GSAP scene entrance hook
============================================================ */
function useGsapScenes(active) {
const [activeScene, setActiveScene] = useState("hero");
useEffect(() => {
if (!active || typeof gsap === "undefined") return;
const scenes = document.querySelectorAll("section[data-scene]");
if (!scenes.length) return;
const triggers = [];
scenes.forEach((scene) => {
const inner = scene.querySelector(".scene-inner");
if (!inner) return;
// Entrance
const enterTrigger = ScrollTrigger.create({
trigger: scene,
start: "top 65%",
onEnter: () => {
gsap.to(inner, {
opacity: 1,
y: 0,
scale: 1,
filter: "blur(0px)",
duration: 1.4,
ease: "power3.out",
});
scene.classList.add("in");
scene.classList.remove("past");
setActiveScene(scene.dataset.scene);
// Stagger child reveals
const children = scene.querySelectorAll(".reveal:not(.in)");
gsap.to(children, {
opacity: 1,
y: 0,
duration: 1.1,
ease: "power3.out",
stagger: 0.12,
delay: 0.2,
onStart() { children.forEach((c) => c.classList.add("in")); },
});
},
once: false,
});
// Exit upward
const exitTrigger = ScrollTrigger.create({
trigger: scene,
start: "bottom 20%",
onLeave: () => {
gsap.to(inner, {
opacity: 0.15,
y: -30,
scale: 0.97,
filter: "blur(6px)",
duration: 0.8,
ease: "power2.in",
});
scene.classList.add("past");
},
onEnterBack: () => {
gsap.to(inner, {
opacity: 1,
y: 0,
scale: 1,
filter: "blur(0px)",
duration: 1.0,
ease: "power3.out",
});
scene.classList.remove("past");
setActiveScene(scene.dataset.scene);
},
});
triggers.push(enterTrigger, exitTrigger);
});
// Always show first scene
const first = scenes[0];
if (first) {
const fi = first.querySelector(".scene-inner");
if (fi) {
gsap.set(fi, { opacity: 1, y: 0, scale: 1, filter: "blur(0px)" });
first.classList.add("in");
first.querySelectorAll(".reveal").forEach((el) => {
gsap.to(el, { opacity: 1, y: 0, duration: 1.1, ease: "power3.out", stagger: 0.1 });
el.classList.add("in");
});
}
}
return () => {
triggers.forEach((t) => t.kill());
};
}, [active]);
return activeScene;
}
/* ============================================================
Topbar
============================================================ */
function Topbar({ route, go }) {
const ref = useRef(null);
useEffect(() => {
if (!ref.current || typeof gsap === "undefined") return;
gsap.fromTo(ref.current,
{ y: -60, opacity: 0 },
{ y: 0, opacity: 1, duration: 1.2, ease: "power3.out", delay: 0.3 }
);
}, []);
return (
);
}
/* ============================================================
Audio toggle
============================================================ */
function AudioToggle() {
const [on, setOn] = useState(false);
const audioRef = useRef(null);
useEffect(() => {
const audio = new Audio("/musica.mp3");
audio.loop = true;
audio.volume = 0.45;
audioRef.current = audio;
// Attempt autoplay immediately; browsers may block it until user gesture
audio.play().then(() => setOn(true)).catch(() => {
// Fallback: start on first user interaction
const unlock = () => {
audio.play().then(() => setOn(true)).catch(() => {});
window.removeEventListener("click", unlock);
window.removeEventListener("touchstart", unlock);
window.removeEventListener("keydown", unlock);
};
window.addEventListener("click", unlock, { once: true });
window.addEventListener("touchstart", unlock, { once: true });
window.addEventListener("keydown", unlock, { once: true });
});
return () => { audio.pause(); audio.src = ""; };
}, []);
const toggle = () => {
const audio = audioRef.current;
if (!audio) return;
if (!on) {
audio.play().then(() => setOn(true)).catch(() => {});
} else {
audio.pause();
setOn(false);
}
};
return (
);
}
/* ============================================================
Hero — foto + GSAP cinematic entrance (sem timer)
============================================================ */
function Hero({ tweaks }) {
const stageRef = useRef(null);
// Cinematic entrance timeline
useEffect(() => {
if (!stageRef.current || typeof gsap === "undefined") return;
const stage = stageRef.current;
const tl = gsap.timeline({ delay: 0.4 });
// Photo wrap scales in from center
tl.fromTo(stage.querySelector(".video-wrap"),
{ scale: 0.75, opacity: 0, y: 40 },
{ scale: 1, opacity: 1, y: 0, duration: 1.6, ease: "back.out(1.3)" }
);
// Left titles stagger in
tl.fromTo(
stage.querySelectorAll(".hero-side.left .hero-title, .hero-side.left .hero-lede, .hero-side.left .hero-meta"),
{ x: -40, opacity: 0 },
{ x: 0, opacity: 1, duration: 1.2, ease: "power3.out", stagger: 0.15 },
"-=1.2"
);
// Right titles stagger in
tl.fromTo(
stage.querySelectorAll(".hero-side.right .hero-title, .hero-side.right .hero-lede, .hero-side.right .hero-meta"),
{ x: 40, opacity: 0 },
{ x: 0, opacity: 1, duration: 1.2, ease: "power3.out", stagger: 0.15 },
"-=1.2"
);
}, []);
// Subtle continuous parallax on photo-wrap
useEffect(() => {
if (!stageRef.current || typeof gsap === "undefined") return;
const vw = stageRef.current.querySelector(".video-wrap");
if (!vw) return;
const onMove = (e) => {
const mx = (e.clientX / window.innerWidth - 0.5) * 14;
const my = (e.clientY / window.innerHeight - 0.5) * 10;
gsap.to(vw, { x: mx, y: my, duration: 1.4, ease: "power1.out", overwrite: "auto" });
};
window.addEventListener("mousemove", onMove);
return () => window.removeEventListener("mousemove", onMove);
}, []);
return (
Da forma mais linda que poderíamos imaginar.
Hoje, entre flores e borboletas, celebramos o primeiro
capítulo do nosso conto encantado.
Está completando seu primeiro aninho e
queremos viver esse momento mágico ao seu lado.
Há 1 ano…
o nosso mundo
floresceu ✿
Mariany
nossa princesa
Cada sorriso da nossa princesa transformou nossos dias em um verdadeiro conto encantado.
Agora queremos celebrar esse capítulo especial ♡ ao lado das pessoas que amamos.
{/* Carrossel de fotos */}Seu carinho faz parte da nossa história…
Esperamos por você ✿
Para viver esse dia mágico ao nosso lado.
Sua presença é o nosso maior presente.
Te esperamos no jardim encantado em 13 · 06 · 2026.
Mas, para quem desejar presentear nossa princesa, deixamos algumas sugestões com muito carinho — escolha o que tocar o seu coração.
Vestidos, conjuntos e peças tamanho 2 a 3 anos para acompanhar nossa borboleta.
Para pisar leve no jardim — tamanho 19 ou 20.
Que estimulem o desenvolvimento infantil — descoberta, cores e sons.
Para quem preferir, também disponibilizamos esta opção delicada.
QR Code indisponível — copie a chave acima
`; } }, [pixPayload]); return ( ); } /* ============================================================ Admin confirmações ============================================================ */ function ConfirmacoesPage() { const [auth, setAuth] = useState(() => sessionStorage.getItem("admin_ok") === "1"); const [pw, setPw] = useState(""); const [err, setErr] = useState(""); const [filter, setFilter] = useState("all"); const [rows, setRows] = useState(() => loadRsvps()); const [syncing, setSyncing] = useState(false); const [syncMsg, setSyncMsg] = useState(""); // Auto-atualiza localStorage a cada 2s useEffect(() => { const id = setInterval(() => setRows(loadRsvps()), 2000); return () => clearInterval(id); }, []); // Ao entrar autenticado, busca do Sheets automaticamente se configurado useEffect(() => { if (auth && SHEETS_WEBHOOK_URL) handleSyncSheets(); }, [auth]); const handleSyncSheets = async () => { if (!SHEETS_WEBHOOK_URL) { setSyncMsg("⚠️ URL do Sheets não configurada."); setTimeout(() => setSyncMsg(""), 3000); return; } setSyncing(true); setSyncMsg(""); const data = await fetchRsvpsFromSheets(); setSyncing(false); if (!data) { setSyncMsg("❌ Não foi possível conectar ao Sheets."); } else { // Mescla com localStorage evitando duplicatas por id const local = loadRsvps(); const localIds = new Set(local.map(r => String(r.id))); // Normaliza linhas vindas do Sheets const remote = data.map(r => ({ id: r["ID"] || r.id || Date.now(), createdAt: r["Data"] || r.createdAt || new Date().toISOString(), name: r["Nome"] || r.name || "", phone: r["WhatsApp"] || r.phone || "", count: Number(r["Acompanhantes"] || r.count || 0), companions: r["Nomes Acompanhantes"] ? String(r["Nomes Acompanhantes"]).split(",").map(s => s.trim()).filter(Boolean) : (r.companions || []), pix: Number(r["PIX"] || r.pix || 0), })).filter(r => !localIds.has(String(r.id))); const merged = [...remote, ...local].sort((a, b) => b.id - a.id); localStorage.setItem(STORAGE_KEY, JSON.stringify(merged)); setRows(merged); setSyncMsg(`✿ Sincronizado — ${merged.length} confirmação(ões)`); } setTimeout(() => setSyncMsg(""), 4000); }; if (!auth) { const tryLogin = (e) => { e.preventDefault(); if (pw === "mariany1106") { sessionStorage.setItem("admin_ok", "1"); setAuth(true); setErr(""); } else { setErr("Senha incorreta — tente novamente."); } }; return (Apenas a família tem acesso a esta área ✿
Painel privado · convidados da Mariany ✿
Nenhuma confirmação ainda
As respostas aparecerão aqui assim que chegarem ✿
| Nome | Qtd. | Acompanhantes | PIX | Data | |
|---|---|---|---|---|---|
| {r.name} | {r.phone} | {r.count || 0} | {(r.companions || []).length === 0 ? — : (r.companions || []).map((c, i) => {c}) } | {r.pix ? `R$${r.pix}` : "—"} | {new Date(r.createdAt).toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" })} |
Dados armazenados localmente neste navegador