/* global React */ const { useState, useEffect, useRef } = React; /* ====== Shared atoms ====== */ function Hedgehog({ src, className = '', style, anim = true, rot = 0 }) { return ( ); } function SectionHead({ num, title, meta }) { return (
{num}

{title}

{meta}
); } /* ====== Topbar ====== */ function Topbar({ page, setPage }) { const links = [ ['home', 'Home'], ['groove', 'Groove'], ['about', 'About'], ['media', 'Media'], ['shows', 'Shows'], ['contact', 'Contact'], ]; return (
setPage('home')}> Kevin Weigel
); } /* ====== Footer ====== */ function Footer({ setPage }) { const [tagline, setTagline] = useState('Open-air techno, woodland house, evolution in motion. Booking worldwide.'); useEffect(() => { fetch('/api/settings.php') .then(r => r.json()) .then(d => { if (d.settings && d.settings.footer_tagline) setTagline(d.settings.footer_tagline); }) .catch(() => {}); }, []); return ( ); } /* ====== Intro splash ====== */ function Splash() { const [phase, setPhase] = useState('loading'); // loading → fly → (unmount) const [gone, setGone] = useState(false); const igelRef = useRef(null); const wrapRef = useRef(null); function liftOff() { setPhase(p => (p === 'loading' ? 'fly' : p)); } // While the splash is on screen, hide the real header logo so there isn't a // second igel sitting at the destination before the flying one lands. // Remove the flag as soon as the splash is gone — the component returns null // but stays mounted, so we can't rely on an unmount-cleanup here. useEffect(() => { document.body.classList.add('intro-splashing'); }, []); useEffect(() => { if (gone) document.body.classList.remove('intro-splashing'); }, [gone]); // After 4s the bar is full → lift the igel up to the header logo. useEffect(() => { const t = setTimeout(liftOff, 4000); return () => clearTimeout(t); }, []); // When flying, measure the real header logo and animate the wrap onto it (WAAPI is // deterministic — avoids CSS-transition trigger timing quirks). useEffect(() => { if (phase !== 'fly') return; const logo = document.querySelector('.topbar__brand img'); const igel = igelRef.current; const wrap = wrapRef.current; if (logo && igel && wrap) { const lr = logo.getBoundingClientRect(); const ir = igel.getBoundingClientRect(); const dx = (lr.left + lr.width / 2) - (ir.left + ir.width / 2); const dy = (lr.top + lr.height / 2) - (ir.top + ir.height / 2); const scale = lr.width / ir.width; const target = `translate(${dx}px, ${dy}px) scale(${scale})`; const anim = wrap.animate( [{ transform: 'translate(0px, 0px) scale(1)' }, { transform: target }], { duration: 1050, easing: 'cubic-bezier(0.66, 0, 0.2, 1)', fill: 'forwards' } ); anim.onfinish = () => { wrap.style.transform = target; }; } const t = setTimeout(() => setGone(true), 1150); return () => clearTimeout(t); }, [phase]); if (gone) return null; return (
Loading the groove
); } Object.assign(window, { Hedgehog, SectionHead, Topbar, Footer, Splash });