// 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 (
);
}
// ── 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 });