/* 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 (
);
}
/* ====== Topbar ====== */
function Topbar({ page, setPage }) {
const links = [
['home', 'Home'],
['groove', 'Groove'],
['about', 'About'],
['media', 'Media'],
['shows', 'Shows'],
['contact', 'Contact'],
];
return (
setPage('home')}>
);
}
/* ====== 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 (
);
}
Object.assign(window, { Hedgehog, SectionHead, Topbar, Footer, Splash });