// devonNEXT — Main App (React) const {useState,useEffect,useRef,useMemo} = React; const C = window.dnContent; // ──────────────── HOOKS function useReveal(){ useEffect(()=>{ const io = new IntersectionObserver((entries)=>{ entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('v');io.unobserve(e.target)}}); },{threshold:.14,rootMargin:'0px 0px -60px 0px'}); document.querySelectorAll('.rv').forEach(el=>io.observe(el)); return ()=>io.disconnect(); }); } // Baut für jedes [data-swipe]-Karussell einen Wisch-Indikator (Dots + Hinweis). // Nur auf Mobile sichtbar (CSS); Dots folgen dem horizontalen Scroll. function useSwipeRails(){ useEffect(()=>{ document.querySelectorAll('[data-swipe]').forEach(rail=>{ if(rail.dataset.swipeReady) return; const n = rail.children.length; if(n < 2) return; rail.dataset.swipeReady = '1'; const ind = document.createElement('div'); ind.className='swipe-ind'; ind.setAttribute('aria-hidden','true'); const dots = document.createElement('div'); dots.className='swipe-dots'; for(let i=0;i{ const per = rail.scrollWidth / n; const a = Math.max(0, Math.min(n-1, Math.round(rail.scrollLeft/per))); [...dots.children].forEach((d,i)=>d.classList.toggle('on', i===a)); }, {passive:true}); }); },[]); } function useScrollSpy(ids){ const [active,setActive] = useState(ids[0]); useEffect(()=>{ const onScroll = ()=>{ const y = window.scrollY + window.innerHeight*0.4; let cur = ids[0]; for(const id of ids){ const el = document.getElementById(id); if(el && el.offsetTop <= y) cur = id; } setActive(cur); }; onScroll(); window.addEventListener('scroll',onScroll,{passive:true}); return ()=>window.removeEventListener('scroll',onScroll); },[ids.join('|')]); return active; } // ──────────────── THEME TOGGLE (removed — light mode only for go-live) function ThemeToggle(){ return null; } // ──────────────── NAV function TopNav({onBook}){ const [scrolled,setScrolled] = useState(false); const [mobileOpen,setMobileOpen] = useState(false); const variant = 'A'; useEffect(()=>{ const on = ()=>setScrolled(window.scrollY>30); on(); window.addEventListener('scroll',on,{passive:true}); return ()=>window.removeEventListener('scroll',on); },[]); useEffect(()=>{ document.body.style.overflow = mobileOpen ? 'hidden' : ''; return ()=>{document.body.style.overflow='';}; },[mobileOpen]); const go = (id)=>{ setMobileOpen(false); setTimeout(()=>document.getElementById(id)?.scrollIntoView({behavior:'smooth',block:'start'}), 50); }; return ( <>
{C.nav.map(n=>)}
setMobileOpen(false)} aria-hidden="true"/> ); } // ──────────────── SIDE RAIL function SideRail(){ const ids = C.sections.map(s=>s.id); const active = useScrollSpy(ids); const [on,setOn] = useState(false); useEffect(()=>{ const h = ()=>setOn(window.scrollY>window.innerHeight*0.6); h(); window.addEventListener('scroll',h,{passive:true}); return ()=>window.removeEventListener('scroll',h); },[]); return (
{C.sections.map(s=>( ))}
); } // ──────────────── PROGRESS (top bar — visible on mobile, also runs on desktop) function ScrollProgress(){ const [p,setP] = useState(0); useEffect(()=>{ const on = ()=>{ const h = document.documentElement; const max = h.scrollHeight - h.clientHeight; setP(max>0 ? (h.scrollTop/max)*100 : 0); }; on(); window.addEventListener('scroll',on,{passive:true}); return ()=>window.removeEventListener('scroll',on); },[]); return ); // Kein Portal/Remount: dasselbe