// 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 (
<>
{tweaksOpen && setTweaksOpen(false)} />}
>
);
}
Object.assign(window, { App, useT });