// ASA Timmerwerken v2 — Components & shared utilities const MEDIA = { // Videos (Mixkit — royalty-free) heroVideo: 'https://assets.mixkit.co/videos/4196/4196-720.mp4', showcaseVideo:'https://assets.mixkit.co/videos/4046/4046-720.mp4', workVideo: 'https://assets.mixkit.co/videos/4648/4648-720.mp4', paintVideo: 'https://assets.mixkit.co/videos/32537/32537-720.mp4', // Photos (Unsplash) heroPoster: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=1800&q=85', overOns: 'https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=900&q=80&fit=crop', aanbouw: 'https://images.unsplash.com/photo-1560518883-ce09059eeffa?w=900&q=80&fit=crop', renovatie: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=900&q=80&fit=crop', badkamer: 'https://images.unsplash.com/photo-1552321554-5fefe8c9ef14?w=900&q=80&fit=crop', maatwerk: 'https://images.unsplash.com/photo-1565538810643-b5bdb714032a?w=900&q=80&fit=crop', gallery1: 'https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=600&q=80', gallery2: 'https://images.unsplash.com/photo-1583847268964-b28dc8f51f92?w=600&q=80', gallery3: 'https://images.unsplash.com/photo-1484154218962-a197022b5858?w=600&q=80', gallery4: 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=600&q=80', gallery5: 'https://images.unsplash.com/photo-1600573472591-ee6b68d14c68?w=600&q=80', gallery6: 'https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=600&q=80', cta: 'https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=1400&q=80', }; const P = { gold: '#C4956A', goldDk: '#A87848', goldLt: '#E8D5B8', ink: '#1C1916', ink2: '#2A2520', ink3: '#38332E', parch: '#FAF6EF', sand: '#EDE5D8', linen: '#F2EDE4', warm: '#3C3830', warmMid: '#8A7F74', warmBdr: '#D5CCC0', white: '#FFFFFF', }; const NAV_LINKS = [ { label: 'Home', page: 'home' }, { label: 'Over ons', page: 'over' }, { label: 'Diensten', page: 'diensten' }, { label: 'Calculator', page: 'calculator' }, { label: 'Contact', page: 'contact' }, ]; // ── useMobile ──────────────────────────────────────────────── function useMobile() { const [m, setM] = React.useState(window.innerWidth < 768); React.useEffect(() => { const h = () => setM(window.innerWidth < 768); window.addEventListener('resize', h); return () => window.removeEventListener('resize', h); }, []); return m; } // ── Nav ───────────────────────────────────────────────────── function Nav({ currentPage, navigate }) { const [scrolled, setScrolled] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); const mob = useMobile(); React.useEffect(() => { const fn = () => setScrolled(window.scrollY > 80); window.addEventListener('scroll', fn, { passive: true }); return () => window.removeEventListener('scroll', fn); }, []); React.useEffect(() => { document.body.style.overflow = menuOpen ? 'hidden' : ''; return () => { document.body.style.overflow = ''; }; }, [menuOpen]); React.useEffect(() => { setMenuOpen(false); }, [currentPage]); const navBg = scrolled || menuOpen ? 'rgba(26,22,18,0.97)' : currentPage === 'home' ? 'transparent' : 'rgba(26,22,18,0.97)'; return ( ); } // ── Footer ─────────────────────────────────────────────────── function Footer({ navigate }) { const mob = useMobile(); return ( ); } // ── Tag ───────────────────────────────────────────────────── function Tag({ children, light }) { return (
{children}
); } // ── GoldBtn ────────────────────────────────────────────────── function GoldBtn({ children, onClick, outline, dark, href, submit }) { const [hov, setHov] = React.useState(false); const base = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, fontFamily: "'DM Sans',sans-serif", fontWeight: 400, cursor: 'pointer', transition: 'all 0.22s', textDecoration: 'none', fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', padding: '13px 28px', border: 'none' }; let style; if (outline && dark) style = { ...base, background: 'transparent', color: P.warm, border: `1px solid ${P.warmBdr}`, ...(hov ? { background: P.parch } : {}) }; else if (outline) style = { ...base, background: 'transparent', color: '#FAF6EF', border: `1px solid rgba(196,149,106,0.4)`, ...(hov ? { background: 'rgba(196,149,106,0.1)' } : {}) }; else style = { ...base, background: hov ? P.goldDk : P.gold, color: '#fff' }; const props = { style, onMouseEnter: () => setHov(true), onMouseLeave: () => setHov(false), onClick }; if (href) return {children}; if (submit) return ; return ; } // ── VideoBackground ───────────────────────────────────────── function VideoBg({ src, poster, overlay = 0.55, children }) { return (
{children}
); } // ── VideoModal ─────────────────────────────────────────────── function VideoModal({ src, onClose }) { React.useEffect(() => { const fn = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', fn); return () => document.removeEventListener('keydown', fn); }, [onClose]); return (
e.stopPropagation()} style={{ width: '100%', maxWidth: 960, aspectRatio: '16/9', background: '#000' }}>
); } // ── ScrollReveal ───────────────────────────────────────────── function Reveal({ children, delay = 0 }) { const ref = React.useRef(null); const [vis, setVis] = React.useState(false); React.useEffect(() => { const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVis(true); obs.disconnect(); } }, { threshold: 0.1 }); if (ref.current) obs.observe(ref.current); return () => obs.disconnect(); }, []); return (
{children}
); } Object.assign(window, { MEDIA, P, NAV_LINKS, useMobile, Nav, Footer, Tag, GoldBtn, VideoBg, VideoModal, Reveal });