// Main site sections — all copy comes from the Tweaks panel so it // survives refresh and can be edited live. const { useState, useEffect, useRef } = React; // Turn a string with blank-line separated paragraphs into

blocks, // running each through renderRich for *italic* support. function paragraphs(text, className) { if (!text) return null; return String(text) .split(/\n{2,}/) .map((block, i) => (

{renderRich(block.replace(/\n/g, " "))}

)); } function normalizePublicListing(listing) { const status = listing.status || "new"; const statusClass = ["verkocht", "verkocht_ov", "sold"].includes(status) ? "sold" : ["bezichtiging", "open"].includes(status) ? "open" : "new"; const photos = Array.isArray(listing.fotos) ? listing.fotos : Array.isArray(listing.photos) ? listing.photos : []; return { id: listing.id, title: listing.titel || listing.title || "Woning", location: [listing.adres, listing.plaats || listing.location] .filter(Boolean) .join(" - "), price: listing.prijsLabel || listing.price || "", statusClass, statusLabel: listing.statusLabel || { nieuw: "Nieuw in verkoop", bezichtiging: "Bezichtiging", verkocht: "Verkocht", verkocht_ov: "Verkocht o.v.", new: "Nieuw in verkoop", open: "Bezichtiging", sold: "Verkocht", }[status] || "Nieuw in verkoop", beds: listing.slaapkamers ?? listing.beds ?? 0, baths: listing.badkamers ?? listing.baths ?? 0, area: listing.woonoppervlak ?? listing.area ?? 0, tone: listing.kleurToon || listing.tone || "warm", image: photos[0] || listing.image || "", }; } // --- HEADER ------------------------------------------------------------ const Header = ({ onNavigate, t }) => { const [scrolled, setScrolled] = useState(false); const [menuOpen, setMenuOpen] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 80); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); useEffect(() => { document.body.style.overflow = menuOpen ? "hidden" : ""; return () => { document.body.style.overflow = ""; }; }, [menuOpen]); const go = (e, id) => { e.preventDefault(); setMenuOpen(false); onNavigate(id); }; return (
go(e, "top")}> {t.brandTitle} {t.brandSub && {t.brandSub}} go(e, "aanbod")} > {t.navCtaLabel}
); }; // --- HERO -------------------------------------------------------------- const Hero = ({ onNavigate, tweaks = {} }) => { const t = tweaks; return (
Rogier Zwarthoff

{renderRich(t.heroTitle)}

{paragraphs(t.heroLede, "hero-lede")}
{ e.preventDefault(); onNavigate("contact"); }} > Plan een kennismaking { e.preventDefault(); onNavigate("aanbod"); }} > Bekijk aanbod
{t.showSign !== false && (t.heroSignName || t.heroSignMeta) && (
{t.heroSignName && {t.heroSignName}} {t.heroSignMeta && {t.heroSignMeta}}
)}
{t.showScroll !== false && t.heroScrollLbl && ( )}
); }; // --- LISTINGS ---------------------------------------------------------- const Listings = ({ t }) => { const visible = (window.LISTINGS_FROM_SERVER || window.LISTINGS || []) .filter((listing) => listing.zichtbaar !== false) .sort((a, b) => (a.volgorde || 0) - (b.volgorde || 0)) .map(normalizePublicListing); return (
{t.aanbodEyebrow}

{renderRich(t.aanbodTitle)}

{paragraphs(t.aanbodIntro, "intro")} { e.preventDefault(); document .getElementById("contact") .scrollIntoView({ behavior: "smooth" }); }} > {t.aanbodCta}
{visible.map((l) => (
{l.image ? ( {l.title} ) : ( )} {l.statusLabel}

{l.location}

{l.title}

{l.price}

{l.beds} slaapk. {l.baths} badk. {l.area} m²
))}
); }; // --- SERVICES ---------------------------------------------------------- const Services = ({ t }) => (
{t.dienstenEyebrow}

{renderRich(t.dienstenTitle)}

{paragraphs(t.dienstenIntro, "intro")}
{window.SERVICES.map((s) => (
{s.num}

{s.title}

{s.body}

    {s.tags.map((tag) => (
  • {tag}
  • ))}
))}
); // --- PROCESS ----------------------------------------------------------- const Process = ({ t }) => (
{t.werkwijzeEyebrow}

{renderRich(t.werkwijzeTitle)}

{paragraphs(t.werkwijzeIntro, "intro")}
{window.PROCESS.map((s) => (
{s.num}

{s.title}

{s.body}

))}
); // --- DOCUMENTS --------------------------------------------------------- const DocIcon = ({ type }) => { // Minimal file icons — clean line style if (type === "pdf") { return ( ); } if (type === "doc") { return ( ); } return ( ); }; const Documents = ({ t }) => { // Build doc list from tweaks (up to 6 documents) const docs = []; for (let i = 1; i <= 6; i++) { const title = t[`doc${i}Title`]; if (!title) continue; docs.push({ title, desc: t[`doc${i}Desc`] || "", type: t[`doc${i}Type`] || "pdf", meta: t[`doc${i}Meta`] || "", href: t[`doc${i}Href`] || "#", }); } if (docs.length === 0) return null; return (
{t.docsEyebrow}

{renderRich(t.docsTitle)}

{paragraphs(t.docsIntro, "intro")}
{docs.map((d, i) => (

{d.title}

{d.desc &&

{d.desc}

}
{d.type.toUpperCase()} {d.meta && ( <> · {d.meta} )}
))}
); }; // --- CONTACT ----------------------------------------------------------- const Contact = ({ t }) => { const [form, setForm] = useState({ name: "", email: "", phone: "", intent: "Verkoop", message: "", }); const [errors, setErrors] = useState({}); const [submitted, setSubmitted] = useState(false); const intents = ["Verkoop", "Aankoop", "Taxatie", "Hypotheek", "Iets anders"]; const validate = () => { const e = {}; if (!form.name.trim()) e.name = "Vul uw naam in."; if (!form.email.trim()) e.email = "Vul uw e-mailadres in."; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) e.email = "Dit e-mailadres klopt niet."; if (!form.message.trim() || form.message.trim().length < 8) e.message = "Schrijf kort waar u aan denkt."; return e; }; const submit = (ev) => { ev.preventDefault(); const e = validate(); setErrors(e); if (Object.keys(e).length === 0) { setSubmitted(true); fetch("/admin/api.php?action=inquiry", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form), }).catch(() => {}); } }; const set = (k) => (ev) => { setForm((f) => ({ ...f, [k]: ev.target.value })); if (errors[k]) setErrors((er) => ({ ...er, [k]: undefined })); }; return (
{submitted ? (

Bedankt, {form.name.split(" ")[0]}.

Uw bericht is binnen. Ik neem uiterlijk de volgende werkdag persoonlijk contact met u op. Tot snel!

) : (
{intents.map((i) => ( ))}
{errors.name && ( {errors.name} )}
{errors.email && ( {errors.email} )}