// ui.jsx — shared UI primitives for NOVA Résidence // Loaded BEFORE app.jsx so its components and styles are available. // ────────────────────────────────────────────────────────────────────────── // PLACEHOLDER (striped image stand-in) // ────────────────────────────────────────────────────────────────────────── function Placeholder({ label, ratio = "16/9", tone = "warm", radius = 0, style = {}, children, src }) { const theme = window.useTheme ? window.useTheme() : null; const showLabel = theme ? theme.t.showPlaceholderLabels : true; const accent = theme ? theme.palette.accent : "#C5A572"; const ink = theme ? theme.palette.ink : "#1F2A33"; const bg = tone === "dark" ? theme ? theme.palette.deep : "#1F2A33" : tone === "ink" ? ink : theme ? theme.palette.bg2 : "#EBE6DA"; const stripeColor = tone === "dark" || tone === "ink" ? "rgba(255,255,255,0.06)" : "rgba(31,42,51,0.06)"; // If a real image src is provided, render it; placeholder label only shown if no src OR theme.showPlaceholderLabels is true AND we want overlay. return (
{src && {label } {!src && showLabel &&
{label || "image"}
} {children}
); } // ────────────────────────────────────────────────────────────────────────── // LOGO — wordmark + monogram (text-based since no logo file provided yet) // ────────────────────────────────────────────────────────────────────────── function Logo({ size = 32, variant = "full", dark = false, onClick }) { // Use the horizontal brand mark when full, the round monogram when compact const src = variant === "mark" ? dark ? "assets/logo-mono-dore.png" : "assets/logo-rond.png" : dark ? "assets/logo-fond-sombre.png" : "assets/logo-horizontal.png"; const h = variant === "mark" ? size : Math.round(size * 1.05); return ( {if (onClick) {e.preventDefault();onClick();}}} style={{ display: "inline-flex", alignItems: "center", textDecoration: "none" }}> NOVA Résidence ); } // ────────────────────────────────────────────────────────────────────────── // BUTTONS // ────────────────────────────────────────────────────────────────────────── function Button({ children, variant = "primary", onClick, href, size = "md", icon, style = {} }) { const sizing = size === "lg" ? { padding: "16px 28px", fontSize: 14 } : size === "sm" ? { padding: "8px 14px", fontSize: 12 } : { padding: "12px 22px", fontSize: 13 }; const variantStyle = variant === "primary" ? { background: "var(--accent)", color: "#FAF6EC", border: "1px solid var(--accent)" } : variant === "dark" ? { background: "var(--ink)", color: "var(--bg)", border: "1px solid var(--ink)" } : variant === "ghost" ? { background: "transparent", color: "var(--ink)", border: "1px solid var(--line)" } : variant === "link" ? { background: "transparent", color: "var(--ink)", border: 0, padding: 0, textDecoration: "underline", textUnderlineOffset: 4 } : {}; const Tag = href ? "a" : "button"; return ( {if (variant === "primary") e.currentTarget.style.background = "var(--accent-dark)";}} onMouseLeave={(e) => {if (variant === "primary") e.currentTarget.style.background = "var(--accent)";}}> {children} {icon && {icon}} ); } // ────────────────────────────────────────────────────────────────────────── // SECTION SHELL // ────────────────────────────────────────────────────────────────────────── function Section({ children, bg = "bg", id, style = {}, fullBleed = false, label }) { return (
{label && {label}} {children}
); } function Eyebrow({ children, style = {} }) { return (
{children}
); } // ────────────────────────────────────────────────────────────────────────── // TOP BAR // ────────────────────────────────────────────────────────────────────────── function TopBar({ route, navigate }) { const [scrolled, setScrolled] = useState(false); const [menuOpen, setMenuOpen] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 8); onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); // Close the mobile menu whenever the route changes. useEffect(() => {setMenuOpen(false);}, [route]); const links = [ { id: "residence", label: "La Résidence" }, { id: "appartements", label: "Appartements" }, { id: "orange", label: "Orange" }, { id: "habiter", label: "Habiter" }, { id: "investir", label: "Investir" }, { id: "contact", label: "Contact" }]; const go = (id) => {navigate(id);setMenuOpen(false);}; return (
navigate("accueil")} />
07 66 46 18 65 { if (window.plausible) { plausible('Clic WhatsApp'); } }} style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 38, height: 38, borderRadius: "50%", background: "#25D366", textDecoration: "none", flexShrink: 0 }}>
{ if (window.plausible) { plausible('Clic WhatsApp'); } }} style={{ display: "none", alignItems: "center", justifyContent: "center", width: 40, height: 40, borderRadius: "50%", background: "#25D366", textDecoration: "none", flexShrink: 0, marginLeft: "auto", marginRight: 8 }}>
{/* Mobile dropdown menu */}
{links.map((l) => {e.preventDefault();go(l.id);}} style={{ fontFamily: "var(--font-b)", fontSize: 17, fontWeight: 500, color: route === l.id ? "var(--accent)" : "var(--ink)", textDecoration: "none", padding: "14px 4px", borderBottom: "1px solid var(--line)" }}> {l.label} )} 📞 07 66 46 18 65
); } // ────────────────────────────────────────────────────────────────────────── // FOOTER // ────────────────────────────────────────────────────────────────────────── function Footer({ navigate }) { const [plaquetteOpen, setPlaquetteOpen] = React.useState(false); return ( ); } // ────────────────────────────────────────────────────────────────────────── // COOKIE CONSENT — bandeau RGPD + chargement conditionnel du pixel Meta // ────────────────────────────────────────────────────────────────────────── const CONSENT_KEY = "nova_cookie_consent"; function loadMetaPixel() { if (window.__novaMetaPixelLoaded) return; window.__novaMetaPixelLoaded = true; !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n; n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window, document,'script','https://connect.facebook.net/en_US/fbevents.js'); window.fbq('init', '860717183426332'); window.fbq('track', 'PageView'); } function loadGoogleAds() { if (window.__novaGoogleAdsLoaded) return; window.__novaGoogleAdsLoaded = true; var gs = document.createElement('script'); gs.async = true; gs.src = 'https://www.googletagmanager.com/gtag/js?id=AW-18202499337'; document.head.appendChild(gs); window.dataLayer = window.dataLayer || []; function gtag(){ dataLayer.push(arguments); } window.gtag = gtag; gtag('js', new Date()); gtag('config', 'AW-18202499337'); } function loadConsentedTags() { loadMetaPixel(); loadGoogleAds(); } function CookieBanner({ navigate }) { const [visible, setVisible] = React.useState(false); React.useEffect(() => { let choice = null; try { choice = localStorage.getItem(CONSENT_KEY); } catch (e) {} if (choice === "accepted") loadConsentedTags(); else if (choice !== "refused") setVisible(true); const reopen = () => setVisible(true); window.addEventListener("nova:open-cookie-settings", reopen); return () => window.removeEventListener("nova:open-cookie-settings", reopen); }, []); const choose = (value) => { try { localStorage.setItem(CONSENT_KEY, value); } catch (e) {} setVisible(false); if (value === "accepted") loadConsentedTags(); }; if (!visible) return null; return (

Nous utilisons des cookies de mesure publicitaire (Meta et Google) pour améliorer nos campagnes. Vous pouvez les accepter ou les refuser. En savoir plus dans notre{" "} { e.preventDefault(); navigate("politique-confidentialite"); }} style={{ color: "var(--accent)", textDecoration: "underline", textUnderlineOffset: 3 }}>Politique de confidentialité.

); } function cookieBtn(primary) { return { fontFamily: "var(--font-b)", fontWeight: 500, fontSize: 13, letterSpacing: "0.04em", textTransform: "uppercase", padding: "12px 26px", cursor: "pointer", borderRadius: 0, border: primary ? "1px solid var(--accent)" : "1px solid rgba(242,235,221,.4)", background: primary ? "var(--accent)" : "transparent", color: primary ? "#1F2A33" : "var(--bg)", }; } function FooterCol({ title, links, navigate }) { return (

{title}

); } // ────────────────────────────────────────────────────────────────────────── // FORMAT HELPERS // ────────────────────────────────────────────────────────────────────────── const fmtPrice = (n) => new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", maximumFractionDigits: 0 }).format(n); const fmtN = (n) => new Intl.NumberFormat("fr-FR").format(Math.round(n)); // expose const WHATSAPP_URL = "https://wa.me/33766461865?text=Bonjour%2C%20je%20suis%20int%C3%A9ress%C3%A9(e)%20par%20la%20R%C3%A9sidence%20NOVA%20%C3%A0%20Orange."; function WhatsAppFab() { const onClick = () => { if (window.plausible) { plausible('Clic WhatsApp'); } }; return ( ); } Object.assign(window, { Placeholder, Logo, Button, Section, Eyebrow, TopBar, Footer, FooterCol, CookieBanner, WhatsAppFab, WHATSAPP_URL, fmtPrice, fmtN });