// Landing page primitives + sections // ─────────── Supabase : inscriptions à la bêta ─────────── // La clé "anon" est PUBLIQUE par conception : elle peut figurer dans le code. // La sécurité vient de la règle (RLS) configurée dans Supabase, qui n'autorise // QUE l'insertion (aucune lecture des inscriptions par le public). // → Voir INSTALLATION-supabase.md pour créer la table et récupérer ces 2 valeurs. const SUPABASE_URL = 'https://lmhhrccmgebztioesmik.supabase.co'; const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxtaGhyY2NtZ2VienRpb2VzbWlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUzMjIyOTgsImV4cCI6MjA4MDg5ODI5OH0.epfoBIsZJHLqj96dYE7AvImK_EgjMW9PFtvLk4VwlDc'; // Nom EXACT de la table Supabase (respectez la casse / les majuscules). const SUPABASE_TABLE = 'inscription_beta'; async function saveBetaSignup(data) { const res = await fetch(SUPABASE_URL + '/rest/v1/' + SUPABASE_TABLE, { method: 'POST', headers: { 'apikey': SUPABASE_ANON_KEY, 'Authorization': 'Bearer ' + SUPABASE_ANON_KEY, 'Content-Type': 'application/json', 'Prefer': 'return=minimal', }, body: JSON.stringify(data), }); if (!res.ok) { const detail = await res.text().catch(() => ''); throw new Error('Supabase ' + res.status + ', ' + detail); } } // Inscription à la newsletter Brevo (via la fonction Supabase « brevo-subscribe »). // Best-effort : si ça échoue, l'inscription bêta reste valide (on n'interrompt pas l'utilisateur). async function subscribeNewsletter({ email, prenom, nom, ville, role }) { try { await fetch(SUPABASE_URL + '/functions/v1/brevo-subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + SUPABASE_ANON_KEY, 'apikey': SUPABASE_ANON_KEY, }, body: JSON.stringify({ email, prenom: prenom || '', nom: nom || '', ville: ville || '', role: role || null, consent: true }), }); } catch (e) { console.warn('[newsletter] inscription Brevo échouée (non bloquant)', e); } } const ROLES = { pilote: { emoji: '🏡', tint: '#dcefe7', short: 'Porteurs de lieu', name: "Pilote d'impact", verb: 'Coordonnez un lieu durable', persona: "Porteur·se d'un tiers-lieu, écolieu, ferme, association, incubateur…", pillText: "Coordonner un lieu durable et visible", desc: 'Tiers-lieu, écolieu, ferme, association, incubateur. Vous publiez des quêtes, accueillez des Bâtisseurs, certifiez les preuves d\'impact.', perks: ['Pilotez et financez vos projets avec des outils numériques intégrés', 'Rendez vos impacts mesurables et traçables, preuves à l\'appui', 'Transformez vos actions en données probantes pour ancrer votre modèle'], cta: 'Développer mon lieu', accent: '#018262', image: window.__resources.pilote }, batisseur: { emoji: '🌿', tint: '#fdf3e7', short: 'Citoyens', name: "Bâtisseur d'impact", verb: "Passez à l'action", persona: "Membre, particulier, étudiant, digital nomad, entrepreneur…", pillText: "Passer à l'action avec des avantages concrets", desc: 'Membre, particulier, étudiant, digital nomad, entrepreneur. Vous rejoignez des quêtes concrètes, contribuez, gagnez des graines à utiliser dans le réseau.', perks: ['Passez de l\'éco-anxiété à l\'éco-action, un pas à la fois', 'Soyez reconnu et récompensé : chaque action est valorisée', 'Avancez à votre rythme, porté par une communauté qui avance avec vous'], cta: 'Trouver ma quête', accent: '#c8732a', image: window.__resources.batisseur }, semeur: { emoji: '🌾', tint: '#e8f4f9', short: 'Financeurs', name: "Semeur d'impact", verb: 'Soutenez des projets durables', persona: "Financeur public/privé, investisseur, fondation, collectivité…", pillText: "Soutenir des projets durables certifiés", desc: 'Financeur public/privé, fondation, investisseur, collectivité. Vous financez des projets contre des preuves d\'impact certifiées.', perks: ['Identifiez les initiatives véritablement transformatrices', 'Assurez-vous d\'impacts mesurables, durables et transparents', 'Chaque acte investi est tracé, mesuré, et devient une graine'], cta: 'Soutenir des projets', accent: '#3a6e8c', image: window.__resources.semeur }, }; const Section = ({ id, eyebrow, title, sub, children, dark, narrow, padded = true }) => (
{eyebrow && (
{eyebrow}
)} {title && (

{title}

)} {sub && (

{sub}

)} {children}
); // ─────────────────── Hero ─────────────────── const Hero = ({ role, setRole, palette, persona, onChoose }) => { const r = ROLES[role]; const [mode, setMode] = React.useState('signup'); // 'signup' | 'login' const accent = palette === 'terracotta' ? '#c8732a' : palette === 'sky' ? '#3a6e8c' : '#018262'; React.useEffect(() => { if (mode === 'login') { document.getElementById('hero')?.scrollIntoView({ behavior: 'smooth' }); } }, [mode]); return (
setMode('login')} persona={persona} onChoose={onChoose}/>
Écosystème Vivant Autonome & Décentralisé

Imaginons un avenir durable et réalisons-le ensemble.

Du pixel à la terre, du rêve au lieu, de l'action à l'impact. Plongez dans un avenir solarpunk et rejoignez le mouvement qui transforme la transition écologique en économie régénérative.

); }; const NAV_LINKS = [['#ecosystem', 'Solutions'], ['#roles', 'Profils'], ['#foundations', 'Piliers'], ['#cycle', 'Boucle'], ['#deva', 'Deva'], ['#association', "L'asso"], ['#agir', 'Nous soutenir'], ['#cta', 'Nous suivre']]; const NavBar = ({ accent, onLogin, persona, onChoose }) => { const [scrolled, setScrolled] = React.useState(false); const [overDark, setOverDark] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); const pr = persona && ROLES[persona]; React.useEffect(() => { const onScroll = () => { setScrolled(window.scrollY > 24); setOverDark(document.documentElement.classList.contains('over-prologue')); }; onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); // Also watch the html class for changes the prologue sets after mount const mo = new MutationObserver(() => setOverDark(document.documentElement.classList.contains('over-prologue'))); mo.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); return () => { window.removeEventListener('scroll', onScroll); mo.disconnect(); }; }, []); // Close menu on scroll or escape React.useEffect(() => { if (!menuOpen) return; const onKey = (e) => { if (e.key === 'Escape') setMenuOpen(false); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [menuOpen]); const linkColor = overDark ? '#f5fbf8' : '#0d2b22'; const logoFill = overDark ? '#f5fbf8' : '#018262'; const burgerColor = overDark ? '#f5fbf8' : '#0d2b22'; return ( ); }; const Logo = ({ width = 100, fill = '#018262' }) => ( ); const HeroVisual = ({ role, setRole, mode, setMode }) => { const r = ROLES[role]; return (
Bienvenue dans l'écosystème
{mode === 'login' ? ( setMode('signup')}/> ) : ( setMode('login')}/> )}
); }; const SignupContent = ({ role, setRole, r, onLogin }) => { const [chosen, setChosen] = React.useState(false); const [sent, setSent] = React.useState(false); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const inputStyle = { width: '100%', padding: '14px 16px', background: '#fff', border: '1px solid rgba(46,102,66,.16)', borderRadius: 12, fontFamily: "'Satoshi',sans-serif", fontSize: 14, color: '#0d2b22', outline: 'none', transition: 'border-color .15s, box-shadow .15s', boxSizing: 'border-box', }; const focusIn = e => { e.currentTarget.style.borderColor = r.accent; e.currentTarget.style.boxShadow = '0 0 0 3px ' + r.accent + '22'; }; const focusOut = e => { e.currentTarget.style.borderColor = 'rgba(46,102,66,.16)'; e.currentTarget.style.boxShadow = 'none'; }; // ─── FORM VIEW (a profile has been chosen) ─── if (chosen) { return ( <>

Rejoignez la bêta :

{/* Chosen profile recap + changer */}
{r.emoji}
{r.name}
{r.short}
{!sent && ( )}
{sent ? (
🌱
Merci pour votre inscription !
Votre demande d'accès {r.name} est bien enregistrée. Nous vous contacterons dès l'ouverture de la bêta, en octobre 2026, et vous serez convié·e à notre événement de lancement.
) : (
{ e.preventDefault(); if (busy) return; setError(null); setBusy(true); const fd = new FormData(e.currentTarget); const data = { role, role_label: r.name, prenom: String(fd.get('prenom') || '').trim(), nom: String(fd.get('nom') || '').trim(), structure: String(fd.get('structure') || '').trim() || null, ville: String(fd.get('ville') || '').trim(), email: String(fd.get('email') || '').trim(), consent: fd.get('consent') === 'on', }; const wantsNewsletter = fd.get('newsletter') === 'on'; try { const configured = !SUPABASE_URL.includes('VOTRE-PROJET') && !SUPABASE_ANON_KEY.includes('COLLEZ'); if (configured) { await saveBetaSignup(data); if (wantsNewsletter && data.email) { await subscribeNewsletter({ email: data.email, prenom: data.prenom, nom: data.nom, ville: data.ville, role }); } } else { // Mode démo (clés Supabase pas encore renseignées) : on simule. await new Promise(res => setTimeout(res, 500)); } setSent(true); } catch (err) { console.error(err); setError("Impossible d'enregistrer votre inscription pour le moment. Merci de réessayer dans un instant."); } finally { setBusy(false); } }} style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> {(role === 'pilote' || role === 'semeur') && ( )} {error && (
{error}
)}
)}
Déjà membre ?
); } // ─── SELECTION VIEW (pick a profile) ─── return ( <>
S'inscrire à la bêta

Notre prototype ouvre en octobre 2026. Inscrivez-vous pour le tester en avant-première, être convié·e à notre événement de lancement et nous aider à le faire grandir avec vos retours.

{Object.entries(ROLES).map(([id, x]) => { const isSel = role === id; return (
{ setRole(id); setChosen(true); setSent(false); }} style={{ display: 'flex', alignItems: 'flex-start', gap: 14, padding: '16px 18px', border: '1px solid ' + (isSel ? x.accent : 'rgba(46,102,66,.14)'), background: isSel ? 'rgba(1,130,98,.04)' : '#fff', borderRadius: 14, marginBottom: 10, cursor: 'pointer', boxShadow: isSel ? '0 8px 24px ' + x.accent + '30' : 'none', transition: 'all .25s', }}>
{x.emoji}
{x.short}
{x.name}
{x.persona}
{x.pillText}
); })}
Déjà membre ?
); }; const LoginForm = ({ onBack }) => { const inputStyle = { width: '100%', padding: '14px 16px', background: '#fff', border: '1px solid rgba(46,102,66,.16)', borderRadius: 12, fontFamily: "'Satoshi',sans-serif", fontSize: 14, color: '#0d2b22', outline: 'none', transition: 'border-color .15s, box-shadow .15s', boxSizing: 'border-box', }; return ( <>
Se connecter

Bon retour parmi nous.

e.preventDefault()} style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> { e.currentTarget.style.borderColor = '#018262'; e.currentTarget.style.boxShadow = '0 0 0 3px rgba(1,130,98,.12)'; }} onBlur={e => { e.currentTarget.style.borderColor = 'rgba(46,102,66,.16)'; e.currentTarget.style.boxShadow = 'none'; }}/> { e.currentTarget.style.borderColor = '#018262'; e.currentTarget.style.boxShadow = '0 0 0 3px rgba(1,130,98,.12)'; }} onBlur={e => { e.currentTarget.style.borderColor = 'rgba(46,102,66,.16)'; e.currentTarget.style.boxShadow = 'none'; }}/>
Pas encore de compte ?
); }; window.Hero = Hero; window.Section = Section; window.ROLES = ROLES; window.Logo = Logo;