// app.jsx — Marsa Cove · Off-Plan Microsite (Template Demo for D&B Properties) // Composes the chrome (Nav/Hero/Footer/FAB/Modal) with the chapter sections. // Manages locale, tweaks, scroll-driven nav, and intersection-observer entry animations. const { useState, useEffect, useRef, useMemo } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#171144", "#F6F3EE", "#B8935C"], "paletteId": "ink", "hero": "cinematic", "density": "regular", "showWA": true, "showDemoStrip": false, "locale": "en" }/*EDITMODE-END*/; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [locale, setLocale] = useState(t.locale || "en"); const [localeOpen, setLocaleOpen] = useState(false); const [registerOpen, setRegisterOpen] = useState(false); const [navDark, setNavDark] = useState(true); const [progress, setProgress] = useState(0); const L = window.LOCALES[locale]; // Expose locale globally for sub-components / form logging window.__LOCALE = locale; // Document dir + lang useEffect(() => { document.documentElement.dir = L.dir; document.documentElement.lang = locale; document.body.style.fontFamily = locale === "ar" ? "var(--ar)" : "var(--sans)"; }, [locale, L.dir]); // Density attr useEffect(() => { document.documentElement.setAttribute("data-density", t.density); }, [t.density]); // Persist locale into tweak state so reload restores useEffect(() => { if (t.locale !== locale) setTweak("locale", locale); }, [locale]); // Apply palette tokens useEffect(() => { const palette = window.TWEAK_PALETTES.find((p) => p.id === t.paletteId) || window.TWEAK_PALETTES[0]; const r = document.documentElement.style; Object.entries(palette.tokens).forEach(([k, v]) => { r.setProperty("--" + (k === "ink2" ? "ink-2" : k === "gold2" ? "gold-2" : k), v); }); }, [t.paletteId]); // Scroll: nav dark <-> light, progress bar, AND fade-up reveal (scroll-based because // IntersectionObserver doesn't fire reliably in the preview sandbox iframe). useEffect(() => { function revealInView() { const vh = window.innerHeight; document.querySelectorAll(".fade-up:not(.is-in)").forEach((el) => { const r = el.getBoundingClientRect(); if (r.top < vh - 40 && r.bottom > 0) el.classList.add("is-in"); }); } function onScroll() { const y = window.scrollY; const vh = window.innerHeight; setNavDark(y < vh * 0.7); const max = document.documentElement.scrollHeight - vh; setProgress(max > 0 ? Math.min(1, y / max) : 0); revealInView(); } onScroll(); setTimeout(revealInView, 50); setTimeout(revealInView, 400); window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", revealInView); return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", revealInView); }; }, []); // (removed separate IntersectionObserver effect — sandbox doesn't fire IO callbacks reliably) // open register from anywhere useEffect(() => { function on() { setRegisterOpen(true); } window.addEventListener("open-register", on); return () => window.removeEventListener("open-register", on); }, []); return (
#171144) from the logo SVG.
Marine was the brief's placeholder; sand & stone are sector-friendly alternatives.