// Reusable components: Navbar, Hero, Sections, Footer, etc. const { useState, useEffect, useRef } = React; // ---- IntersectionObserver hook for scroll reveal ---- function useReveal() { useEffect(() => { const els = document.querySelectorAll(".reveal"); const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -60px 0px" }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }); } // ---- Navbar ---- function Navbar({ t, lang, setLang, onCta }) { const [scrolled, setScrolled] = useState(false); const [open, setOpen] = useState(false); const [active, setActive] = useState("hero"); useEffect(() => { const onScroll = () => { setScrolled(window.scrollY > 20); const ids = ["historie", "carnaval", "lustrum", "over", "contact"]; let cur = "hero"; for (const id of ids) { const el = document.getElementById(id); if (el && el.getBoundingClientRect().top < 120) cur = id; } setActive(cur); }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const goTo = (id) => (e) => { e.preventDefault(); setOpen(false); const el = document.getElementById(id); if (el) { const top = el.getBoundingClientRect().top + window.scrollY - 70; window.scrollTo({ top, behavior: "smooth" }); } }; return ( ); } // ---- Hero ---- function Hero({ t, onPrimary, onSecondary }) { return (
{t.hero.eyebrow}

{t.hero.title1} {t.hero.title2}

{t.hero.lead}

{t.hero.stats.map((s, i) => (
{s.num} {s.label}
))}
Wapenschild De Goeie Orde
); } // ---- Divider ---- function Divider() { return ( ); } // ---- Historie / Timeline ---- function HistorieSection({ t }) { return (
{t.historie.eyebrow}

{t.historie.lead}

{t.historie.timeline.map((it, i) => (
{it.date}

{it.title}

{it.body}

))}

); } // ---- Story (about origin) ---- function StorySection({ t }) { return (
{t.story.eyebrow}

{t.story.title}

{t.story.paras[0]}

{t.story.paras[1]}

{t.story.quote}

{t.story.paras[2]}

); } // ---- Carnaval ---- function CarnavalSection({ t }) { return (
{t.carnaval.eyebrow}

{t.carnaval.lead}

    {t.carnaval.bullets.map((b, i) => (
  • {b.glyph} {b.text}
  • ))}

{t.carnaval.cardTitle}

{t.carnaval.cardLead}

); } function ConfettiBg() { const colors = ["#8b1f1f", "#c89530", "#1e4a8c", "#6b1414", "#d9b04a"]; const pieces = Array.from({length: 18}, (_, i) => ({ left: (i * 5.5 + (i % 3) * 4) % 100, delay: (i * 0.35) % 6, color: colors[i % colors.length], rot: (i * 23) % 360, })); return ( ); } // ---- Over Ons ---- function OverSection({ t }) { return (
{t.over.eyebrow}

{t.over.lead}

{t.over.cards.map((c, i) => (
{c.label}
{c.name}
{c.num}{c.numSuffix}

{c.desc}

))}

); } function StichtingWrap({ t }) { return (
{t.over.stichting.eyebrow}

{t.over.stichting.title}

{t.over.stichting.items.map((item, i) => (
{item.label}
{item.value}
))}
); } function BestuurSection({ t }) { return (
{t.over.bestuur.eyebrow}

{t.over.bestuur.title}

{t.over.bestuur.members.map((m, i) => (
{m.initials}
{m.name}
{m.role}
{m.emailUser} [{t.over.bestuur.bij}] {m.emailDomain}
))}
); } function RoundTable({ t }) { const [seats, setSeats] = React.useState(56); // shoulder width per seat for round-table circumference calc const SHOULDER_CM = 70; const radiusCm = (seats * SHOULDER_CM) / (2 * Math.PI); const radiusM = (radiusCm / 100).toFixed(2); const diameterM = ((radiusCm * 2) / 100).toFixed(2); // dimensions of the SVG viewBox const VB = 600; const cx = VB / 2; const cy = VB / 2; // outer ring radius (visual) const ringR = 260; const innerR = ringR - 38; return (
setSeats(parseInt(e.target.value, 10))} className="rt-slider" aria-label={t.over.tableSeatsLabel} />
{seats}{t.over.seats}
⌀ {diameterM}{t.over.diameter}
↔ {radiusM}{t.over.radius}

{t.over.formula}

{t.over.tableCenter}
); } // ---- Contact ---- function ContactSection({ t }) { return (
{t.contact.eyebrow}

{t.contact.title}

{t.contact.lead}

{t.contact.cardTitle}

{t.contact.cardSub}
{t.contact.labels.staminee}
Café 't Veurletste
{t.contact.labels.plaats}
{t.contact.address}
{t.contact.labels.email}
tafelheer@degoeieorde.nl
{t.contact.labels.sociaal}
Facebook{" · "} Instagram
{t.contact.stamineePre}
't Veurletste
{t.contact.stamineeWhere}

{t.contact.stamineeNote}

); } // ---- Footer ---- function Footer({ t, onNav }) { return ( ); } // ---- Lustrum 2027 ---- function LustrumSection({ t }) { const [now, setNow] = React.useState(() => Date.now()); React.useEffect(() => { const id = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(id); }, []); const target = new Date("2027-05-02T20:00:00").getTime(); const diff = Math.max(0, target - now); const d = Math.floor(diff / (1000 * 60 * 60 * 24)); const h = Math.floor((diff / (1000 * 60 * 60)) % 24); const m = Math.floor((diff / (1000 * 60)) % 60); const s = Math.floor((diff / 1000) % 60); const pad = (n) => String(n).padStart(2, "0"); return (
{t.lustrum.eyebrow}

{t.lustrum.lead}

{t.lustrum.bullets.map((b, i) => (
{b.num}
{b.title}

{b.body}

))}

MMXXVII
{t.lustrum.countdownLabel}
{pad(d)}
{t.lustrum.labels.days}
{pad(h)}
{t.lustrum.labels.hours}
{pad(m)}
{t.lustrum.labels.minutes}
{pad(s)}
{t.lustrum.labels.seconds}
II · V · MMXXVII
); } Object.assign(window, { useReveal, Navbar, Hero, HistorieSection, StorySection, CarnavalSection, OverSection, StichtingWrap, BestuurSection, ContactSection, Footer, Divider, LustrumSection });