// Landing, CTA + Footer + App const BackToTop = () => { const [show, setShow] = React.useState(false); React.useEffect(() => { const onScroll = () => setShow(window.scrollY > 600); onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); return ( ); }; const SocialBtn = ({ href, label, children }) => ( { e.currentTarget.style.background = 'rgba(232,247,243,.2)'; e.currentTarget.style.transform = 'translateY(-2px)'; e.currentTarget.style.borderColor = 'rgba(207,238,231,.4)'; }} onMouseLeave={e => { e.currentTarget.style.background = 'rgba(232,247,243,.1)'; e.currentTarget.style.transform = ''; e.currentTarget.style.borderColor = 'rgba(207,238,231,.22)'; }}> {children} ); const NEWSLETTER_BY_ROLE = { default: { eyebrow: 'Quatre fois par an', titleA: 'Quatre fois par an,', titleB: 'au rythme des saisons.', pitch: "Une lettre de saison qui raconte ce qui pousse dans l'écosystème : les nouveaux lieux, les quêtes en cours, ce qu'on a appris, ce qui se sème. Lent, soigné, sans tracker.", btn: "🌱 S'inscrire à la newsletter", socialTitle: 'Suivez-nous,', socialItalic: 'écrivez-nous.', }, pilote: { eyebrow: 'Pour votre lieu', titleA: 'Pour votre lieu,', titleB: 'un rendez-vous saisonnier.', pitch: "Recevez chaque saison les nouveaux modules, fiches techniques et retours d'expérience d'autres lieux. De quoi nourrir vos chantiers et faire grandir votre projet sans bruit.", btn: "🏡 Recevoir la lettre des lieux", socialTitle: 'Rejoignez les autres pilotes,', socialItalic: 'partagez vos réussites.', }, batisseur: { eyebrow: 'Pour passer à l\'action', titleA: 'Vos quêtes de saison,', titleB: 'directement dans votre boîte.', pitch: "Recevez les quêtes ouvertes près de chez vous, les nouveaux lieux à découvrir et les bons plans graines. Un email par saison, sans tracker.", btn: "🌿 Recevoir mes quêtes", socialTitle: 'Vibrez avec la communauté,', socialItalic: 'suivez les chantiers du réseau.', }, semeur: { eyebrow: 'Pour suivre l\'impact', titleA: 'Le rapport saisonnier,', titleB: "pour mesurer l'impact.", pitch: "Recevez chaque saison les chiffres du réseau : graines en circulation, quêtes accomplies, preuves validées, scores REGEN. De quoi orienter vos financements en toute connaissance.", btn: "🌾 Recevoir le rapport d'impact", socialTitle: 'Suivez les projets que vous soutenez,', socialItalic: 'partagez avec votre réseau.', }, }; // ─────────── Newsletter : inscription Brevo via fonction Supabase ─────────── // La clé API Brevo reste SECRÈTE côté serveur (variable BREVO_API_KEY dans la // fonction Edge « brevo-subscribe »). Le site n'appelle que cette fonction. // → Voir INSTALLATION-newsletter-brevo.md. const NL_SUPABASE_URL = 'https://lmhhrccmgebztioesmik.supabase.co'; const NL_SUPABASE_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxtaGhyY2NtZ2VienRpb2VzbWlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUzMjIyOTgsImV4cCI6MjA4MDg5ODI5OH0.epfoBIsZJHLqj96dYE7AvImK_EgjMW9PFtvLk4VwlDc'; const NL_ENDPOINT = NL_SUPABASE_URL + '/functions/v1/brevo-subscribe'; const CTASection = ({ role }) => { const copy = (role && NEWSLETTER_BY_ROLE[role]) || NEWSLETTER_BY_ROLE.default; const [prenom, setPrenom] = React.useState(''); const [email, setEmail] = React.useState(''); const [consent, setConsent] = React.useState(false); const [status, setStatus] = React.useState('idle'); // idle | loading | ok | error const [errMsg, setErrMsg] = React.useState(''); const submit = async (e) => { if (e) e.preventDefault(); if (status === 'loading') return; const mail = email.trim(); if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(mail)) { setStatus('error'); setErrMsg('Merci d’indiquer un email valide.'); return; } if (!consent) { setStatus('error'); setErrMsg('Merci de cocher la case pour accepter de recevoir la newsletter.'); return; } setStatus('loading'); setErrMsg(''); try { const res = await fetch(NL_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + NL_SUPABASE_ANON, 'apikey': NL_SUPABASE_ANON, }, body: JSON.stringify({ email: mail, prenom: prenom.trim(), role: role || null, consent: true }), }); if (!res.ok) { let msg = 'Une erreur est survenue. Réessayez dans un instant.'; let raw = ''; try { raw = await res.text(); const j = JSON.parse(raw); if (j && j.error) msg = j.error; } catch {} console.error('[newsletter] HTTP', res.status, 'depuis', NL_ENDPOINT, '→', raw); setStatus('error'); setErrMsg(msg); return; } setStatus('ok'); setPrenom(''); setEmail(''); setConsent(false); } catch { setStatus('error'); setErrMsg('Connexion impossible. Vérifiez votre réseau.'); } }; return (
💭 ➡️ 🏡

{copy.titleA}
{copy.titleB}

{copy.pitch}

{status === 'ok' ? (
Merci, c’est noté !
Vous êtes bien inscrit·e à la newsletter. À très vite dans votre boîte mail.
) : (
setPrenom(e.target.value)} style={{ flex: '1 1 140px', minWidth: 120, padding: '14px 16px', background: 'transparent', border: 'none', outline: 'none', color: '#e8f7f3', fontFamily: "'Satoshi',sans-serif", fontSize: 15, fontWeight: 500, }}/> setEmail(e.target.value)} style={{ flex: '1 1 200px', minWidth: 180, padding: '14px 16px', background: 'transparent', border: 'none', outline: 'none', color: '#e8f7f3', fontFamily: "'Satoshi',sans-serif", fontSize: 15, fontWeight: 500, }}/> )} {status !== 'ok' && ( )} {status === 'error' && (
{errMsg}
)}
Pas de spam, pas de tracker, désinscription en un clic.
{/* ─── Réseaux sociaux + contact ─── */}
✓ Association d'intérêt général ✓ Licence Creative Commons ✓ Données souveraines
); }; const Footer = () => ( ); // ─────────────────── App ─────────────────── const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "heroRole": "pilote", "density": "comfortable", "palette": "forest", "teamLayout": "directory" }/*EDITMODE-END*/; const App = () => { const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); const [role, setRole] = React.useState(tweaks.heroRole); // Persona = explicitly chosen via Deva chat onboarding. Null when reset. const [persona, setPersona] = React.useState(() => { try { const v = localStorage.getItem('evad.deva.persona'); return (v && v !== 'null') ? v : null; } catch { return null; } }); React.useEffect(() => { setRole(tweaks.heroRole); }, [tweaks.heroRole]); // Personnalisation du site depuis le prologue (choix de profil) const choosePersona = (id) => { if (id) { try { localStorage.setItem('evad.deva.persona', id); } catch {} setPersona(id); setRole(id); setTweak('heroRole', id); } else { try { localStorage.removeItem('evad.deva.persona'); } catch {} setPersona(null); } }; // density CSS var const secPad = tweaks.density === 'compact' ? '64px' : tweaks.density === 'airy' ? '128px' : '96px'; // observe-in animation React.useEffect(() => { const els = document.querySelectorAll('[data-fade]'); const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('in'); }); }, { threshold: 0.12 }); els.forEach(el => io.observe(el)); return () => io.disconnect(); }, []); return (
{ setRole(r); setTweak('heroRole', r); }} palette={tweaks.palette} persona={persona} onChoose={choosePersona}/>
); }; ReactDOM.createRoot(document.getElementById('root')).render();