// App — main component wiring together all sections. // Scroll-driven reveals, hero parallax, hero bg cycling, nav scroll state. const { useEffect, useRef, useState, useMemo } = React; // Lookup helper: t('hero.title1') -> de.hero.title1, fallback to key. function useT(lang) { return useMemo(() => { const src = (window.STRINGS && window.STRINGS[lang]) || {}; const fallback = (window.STRINGS && window.STRINGS.de) || {}; return (path) => { const parts = path.split('.'); let cur = src, fcur = fallback; for (const p of parts) { cur = cur && cur[p]; fcur = fcur && fcur[p]; } return (cur !== undefined && cur !== null && cur !== '') ? cur : (fcur !== undefined ? fcur : path); }; }, [lang]); } // Read current editmode defaults (written inline in index.html). const TWEAKS_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": "sandstone", "density": "normal", "heroCycle": true, "sendStyle": "broadcast" }/*EDITMODE-END*/; function App() { const [lang, setLang] = useState('de'); const [tweaks, setTweaks] = useState(TWEAKS_DEFAULTS); const [tweaksOpen, setTweaksOpen] = useState(false); const t = useT(lang); // Apply tweaks to so CSS vars kick in useEffect(() => { const html = document.documentElement; html.setAttribute('data-palette', tweaks.palette === 'sandstone' ? '' : tweaks.palette); html.setAttribute('data-density', tweaks.density === 'normal' ? '' : tweaks.density); }, [tweaks.palette, tweaks.density]); // Tweaks host protocol useEffect(() => { const onMsg = (e) => { const d = e.data || {}; if (d.type === '__activate_edit_mode') setTweaksOpen(true); if (d.type === '__deactivate_edit_mode') setTweaksOpen(false); }; window.addEventListener('message', onMsg); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', onMsg); }, []); const updateTweak = (key, value) => { const next = { ...tweaks, [key]: value }; setTweaks(next); window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: value } }, '*'); }; // Reveal on scroll useEffect(() => { 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 -6% 0px' }); document.querySelectorAll('.reveal, .reveal-headline, .reveal-lines, .reveal-card').forEach((el) => io.observe(el)); return () => io.disconnect(); }, [lang]); // Section-level parallax on decorative SVGs + cards useEffect(() => { let ticking = false; const onScroll = () => { if (ticking) return; ticking = true; requestAnimationFrame(() => { const vh = window.innerHeight; document.querySelectorAll('[data-parallax]').forEach((el) => { const r = el.getBoundingClientRect(); const centerDelta = (r.top + r.height / 2 - vh / 2) / vh; // -1..1 roughly const speed = parseFloat(el.dataset.parallax) || 0.2; el.style.transform = `translate3d(0, ${centerDelta * 120 * speed}px, 0)`; }); ticking = false; }); }; onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); return ( <>