'use client' import Image from '@/spa/components/ui/UnoptimizedImage' import React, { useState, useEffect, useRef } from 'react'; import { Link, useLocation } from '@/spa/router'; import { ArrowRight } from 'lucide-react'; import LoadingScreen from '@/spa/components/ui/LoadingScreen'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; import { useApplicationPhase } from '@/spa/cmsRoute'; import { defaultFooterData, defaultHeaderData, type SpaFooterData, type SpaHeaderData, type SpaIconName, withoutLoginCtas, } from '@/spa/cmsNavigation'; interface LayoutProps { children: React.ReactNode; header?: SpaHeaderData; footer?: SpaFooterData; } /* ── SVG icons ─────────────────────────────────────────── */ const IgIcon = () => ; const LiIcon = () => ; const FbIcon = () => ; const ICONS: Record React.ReactElement> = { instagram: IgIcon, linkedin: LiIcon, facebook: FbIcon, }; /* ── Tokens ────────────────────────────────────────────── */ const ANTHRA = '#1A2030'; // nav + footer shared bg const GOLD = '#EFBF04'; const MUTED = '#D0D5DD'; const SEP = 'rgba(239,191,4,0.2)'; const LINE = 'rgba(239,191,4,0.35)'; const FF_DISPLAY = '"IBM Plex Sans", sans-serif'; const FF_BODY = '"Inter", sans-serif'; const HIDE_MEMBERSHIP_FOOTER_ENTRY = true; const APPLICATION_PHASE_OPEN = '0'; const isMembershipFooterLink = (link?: { label?: string; path?: string }) => link?.path === '/mitglied-werden' || link?.label?.trim().toLowerCase() === 'mitglied werden'; /* ── SocialLink helper ─────────────────────────────────── */ const SocialLink: React.FC<{ href:string; icon:SpaIconName; label:string }> = ({ href, icon, label }) => { const Icon = ICONS[icon] || LiIcon; return ( (e.currentTarget.style.color=GOLD)} onMouseLeave={e=>(e.currentTarget.style.color=MUTED)} > ); }; /* ── Component ──────────────────────────────────────────── */ const Layout: React.FC = ({ children, header = defaultHeaderData, footer = defaultFooterData }) => { const NAV = header.navItems; const SECONDARY = header.secondaryLinks; const linkedInHeaderSocial = header.socialLinks.filter((item) => item.icon === 'linkedin'); const linkedInFooterSocial = footer.socialLinks.filter((item) => item.icon === 'linkedin'); const HEADER_SOCIAL = linkedInHeaderSocial.length ? linkedInHeaderSocial : defaultHeaderData.socialLinks.filter((item) => item.icon === 'linkedin'); const FOOTER_SOCIAL = linkedInFooterSocial.length ? linkedInFooterSocial : defaultFooterData.socialLinks.filter((item) => item.icon === 'linkedin'); const logo = header.logo?.url || defaultHeaderData.logo?.url || ''; const footerLogo = footer.logo?.url || defaultFooterData.logo?.url || ''; const headerCtas = withoutLoginCtas(header.ctas); const footerCtas = withoutLoginCtas(footer.ctas); const fallbackHeaderCtas = withoutLoginCtas(defaultHeaderData.ctas); const fallbackFooterCtas = withoutLoginCtas(defaultFooterData.ctas); const applicationPhase = useApplicationPhase(); const showFloatingApplicationCta = applicationPhase?.activePhase === APPLICATION_PHASE_OPEN; const footerMembershipCta = footerCtas[1] || fallbackFooterCtas[1]; const showFooterMembershipCta = !HIDE_MEMBERSHIP_FOOTER_ENTRY || !isMembershipFooterLink(footerMembershipCta); const [open, setOpen] = useState(false); // desktop overlay const [activeKey, setActive] = useState(null); // desktop hover subnav const [mobileOpen, setMobileOpen] = useState(false); // mobile sidebar overlay const [expandKey, setExpand] = useState(null); // mobile accordion const [mounted, setMounted] = useState(false); const [showLoader, setShowLoader] = useState(true); const [formVisible, setFormVisible] = useState(false); const location = useLocation(); const firstLinkRef = useRef(null); const isLegalPage = ['/impressum', '/datenschutz'].includes(location.pathname); const isMobile = useIsMobile(); // Close everything on route change useEffect(() => { setTimeout(() => { setOpen(false); setMobileOpen(false); setExpand(null); }, 0); if (location.hash) { setTimeout(() => { const el = document.querySelector(location.hash); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 120); } else { window.scrollTo(0, 0); } }, [location]); // Reset mobile accordion when its overlay closes useEffect(() => { if (!mobileOpen) setTimeout(() => setExpand(null), 220); }, [mobileOpen]); // Body scroll lock (either overlay) useEffect(() => { const anyOpen = open || mobileOpen; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; if (anyOpen) { document.body.style.overflow = 'hidden'; document.body.style.paddingRight = open ? `${scrollbarWidth}px` : ''; } else { document.body.style.overflow = ''; document.body.style.paddingRight = ''; setTimeout(() => setActive(null), 220); } return () => { document.body.style.overflow = ''; document.body.style.paddingRight = ''; }; }, [open, mobileOpen]); // Focus first desktop nav link on open useEffect(() => { if (open) setTimeout(() => firstLinkRef.current?.focus(), 300); }, [open]); // Escape closes either overlay useEffect(() => { const h = (e:KeyboardEvent) => { if (e.key === 'Escape') { if (open) setOpen(false); if (mobileOpen) setMobileOpen(false); } }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [open, mobileOpen]); useEffect(() => { const t = setTimeout(() => setMounted(true), 60); return () => clearTimeout(t); }, []); // Hide floating CTA when the form section (#bewerben) is in view – homepage only useEffect(() => { setTimeout(() => setFormVisible(false), 0); if (location.pathname !== '/') return; const el = document.getElementById('bewerben'); if (!el) return; const obs = new IntersectionObserver( ([entry]) => setFormVisible(entry.isIntersecting), { threshold: 0.05 } ); obs.observe(el); return () => obs.disconnect(); }, [location.pathname]); return (
{showLoader && ( setShowLoader(false)} /> )} {/* ── GLOBAL BAYERN CSS ────────────────────────── */} {/* ═══════════════════════════════════════════════════════════ */} {/* ══ DESKTOP CHROME (≥768px) – original top bar + overlay ══ */} {/* ═══════════════════════════════════════════════════════════ */} {!isMobile && ( <> {/* ── TOP BAR ──────────────────────────────────── */}
{/* LEFT – logo */}
{header.logo?.alt
{/* CENTER – hamburger */}
◆ ◆
{/* RIGHT – social */}
{HEADER_SOCIAL.map(s => )}
{/* ── DESKTOP OVERLAY ──────────────────────────── */}
{/* TOP – tagline + logo */}
{header.overlayKicker}
{header.overlayHeadline.split('\n').map((line, index) => {index > 0 &&
}{line}
)}
{header.overlayLogoAlt}
{/* BOTTOM – 3 columns */}
{/* LEFT – main nav */}
    {NAV.map((item, idx) => (
  • setOpen(false)} onMouseEnter={() => setActive(item.key)} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'14px 40px 14px 0', textDecoration:'none', color: activeKey===item.key ? GOLD : '#fff', fontSize:'clamp(15px,1.4vw,20px)', fontFamily:FF_DISPLAY, fontWeight:600, textTransform:'uppercase', letterSpacing:'.03em', transition:'color .15s, transform .15s, opacity .3s ease-out', transform: open ? `translateX(${activeKey===item.key ? 6 : 0}px)` : 'translateX(-16px)', opacity: open ? 1 : 0, transitionDelay: open ? `${.04 + idx*.06}s` : '0s', }} > {item.label} ›
  • ))}
{/* MIDDLE – subnav */}
{NAV.map(item => (
{header.overviewLabel}
{item.subLabel}
    {item.sub?.map(sub => (
  • setOpen(false)} style={{ display:'block', padding:'9px 0', textDecoration:'none', color:MUTED, fontSize: 16, fontFamily:FF_BODY, transition:'color .15s, padding-left .15s' }} onMouseEnter={e=>{ e.currentTarget.style.color='#fff'; e.currentTarget.style.paddingLeft='8px'; }} onMouseLeave={e=>{ e.currentTarget.style.color=MUTED; e.currentTarget.style.paddingLeft='0'; }} >{sub.label}
  • ))}
))}
{/* RIGHT – utilities */}
{HEADER_SOCIAL.map(s => )}
{SECONDARY.map(item => ( setOpen(false)} style={{ fontSize: 16, color:MUTED, textDecoration:'none', fontFamily:FF_BODY, transition:'color .15s', width:'fit-content' }} onMouseEnter={e=>(e.currentTarget.style.color=GOLD)} onMouseLeave={e=>(e.currentTarget.style.color=MUTED)} >{item.label} ))}
setOpen(false)} style={{ display:'inline-block', background:'#EFBF04', color:'#101828', fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'11px 24px', textDecoration:'none', transition:'background .2s, box-shadow .2s', borderRadius:0, fontFamily:FF_DISPLAY }} onMouseEnter={e=>{ e.currentTarget.style.background='#FFD130'; e.currentTarget.style.boxShadow='0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }} onMouseLeave={e=>{ e.currentTarget.style.background='#EFBF04'; e.currentTarget.style.boxShadow='none'; }} >{headerCtas[0]?.label || fallbackHeaderCtas[0].label}
)} {/* ═══════════════════════════════════════════════════════════ */} {/* ══ MOBILE CHROME (<768px) – top bar + fullscreen overlay ══ */} {/* ═══════════════════════════════════════════════════════════ */} {isMobile && ( <> {/* ── TOP BAR ──────────────────────────────────── */}
{/* Logo */} {header.logo?.alt {/* Hamburger */}
{/* ── FULLSCREEN OVERLAY ───────────────────────── */}
{/* Brand watermark */}
{header.mobileWatermark.split('\n').map((line, index) => {index > 0 &&
}{line}
)}
{/* Header row: logo + close */}
setMobileOpen(false)} style={{ display:'flex', alignItems:'center', textDecoration:'none' }}> {header.overlayLogoAlt}
{/* Scrollable body */}
{/* Accordion nav */} {/* CTAs */}
setMobileOpen(false)} style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:52, background:GOLD, color:'#101828', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY }} >{headerCtas[0]?.label || fallbackHeaderCtas[0].label}
{/* Footer zone: social + secondary */}
{/* Social row */}
{HEADER_SOCIAL.map(s => )}
{/* Secondary links */}
{SECONDARY.map(item => ( setMobileOpen(false)} style={{ display:'inline-flex', alignItems:'center', minHeight:40, fontSize:13, color:'rgba(255,255,255,.5)', textDecoration:'none', fontFamily:FF_BODY }} >{item.label} ))}
)} {/* ── FLOATING CTA ─────────────────────────────── */} {location.pathname === '/' && showFloatingApplicationCta && !formVisible && !isMobile && ( { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#101828'; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }} > {headerCtas[0]?.label || fallbackHeaderCtas[0].label} )} {/* ── MAIN ─────────────────────────────────────── */}
{children}
{/* ── FOOTER ───────────────────────────────────── */}
); }; export default Layout;