diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 65ebb0d..d56e0b4 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -7,9 +7,9 @@ import { useIsMobile } from '@/hooks/useIsMobile'; interface LayoutProps { children: React.ReactNode; } /* ── SVG icons ─────────────────────────────────────────── */ -const IgIcon = () => ; -const LiIcon = () => ; -const FbIcon = () => ; +const IgIcon = () => ; +const LiIcon = () => ; +const FbIcon = () => ; const SOCIAL = [ { href:'https://instagram.com', Icon:IgIcon, label:'Instagram' }, { href:'https://linkedin.com', Icon:LiIcon, label:'LinkedIn' }, @@ -90,11 +90,11 @@ const SECONDARY = [ /* ── Tokens ────────────────────────────────────────────── */ const NAVY = '#111D55'; -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 ANTHRA = '#1A2030'; +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'; @@ -107,12 +107,11 @@ const SocialLink: React.FC<{ href:string; Icon:()=>JSX.Element; label:string }> > ); - -/* ── Component ──────────────────────────────────────────── */ +/* ── Component ─────────────────────────────────────────── */ const Layout: React.FC = ({ children }) => { const [open, setOpen] = useState(false); - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [activeKey, setActive] = useState(null); + const [activeKey, setActive] = useState(null); // desktop hover + const [expandKey, setExpand] = useState(null); // mobile accordion const [lang, setLang] = useState<'DE'|'EN'>('DE'); const [mounted, setMounted] = useState(false); const [showLoader, setShowLoader] = useState(() => { @@ -126,9 +125,10 @@ const Layout: React.FC = ({ children }) => { const isLegalPage = ['/impressum', '/datenschutz'].includes(location.pathname); const isMobile = useIsMobile(); + // Close overlay on route change useEffect(() => { setOpen(false); - setMobileMenuOpen(false); + setExpand(null); if (location.hash) { setTimeout(() => { const el = document.querySelector(location.hash); @@ -139,15 +139,14 @@ const Layout: React.FC = ({ children }) => { } }, [location]); + // Reset mobile accordion when overlay closes useEffect(() => { - if (mobileMenuOpen) { - document.body.style.overflow = 'hidden'; - } else if (!open) { - document.body.style.overflow = ''; + if (!open) { + setTimeout(() => { setExpand(null); setActive(null); }, 220); } - return () => { if (!open) document.body.style.overflow = ''; }; - }, [mobileMenuOpen, open]); + }, [open]); + // Body scroll lock useEffect(() => { const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; if (open) { @@ -156,7 +155,6 @@ const Layout: React.FC = ({ children }) => { } else { document.body.style.overflow = ''; document.body.style.paddingRight = ''; - setTimeout(() => setActive(null), 220); } return () => { document.body.style.overflow = ''; @@ -164,22 +162,25 @@ const Layout: React.FC = ({ children }) => { }; }, [open]); + // Focus first nav link on open useEffect(() => { - if (open) setTimeout(() => firstLinkRef.current?.focus(), 300); - }, [open]); + if (open && !isMobile) setTimeout(() => firstLinkRef.current?.focus(), 300); + }, [open, isMobile]); + // Escape key useEffect(() => { - const h = (e:KeyboardEvent) => { if (e.key==='Escape' && open) setOpen(false); }; + const h = (e: KeyboardEvent) => { if (e.key === 'Escape' && open) setOpen(false); }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [open]); + // Mounted animation useEffect(() => { const t = setTimeout(() => setMounted(true), 60); return () => clearTimeout(t); }, []); - // Hide floating CTA when the form section (#bewerben) is in view — homepage only + // Hide floating CTA when form section in view useEffect(() => { setFormVisible(false); if (location.pathname !== '/') return; @@ -194,10 +195,10 @@ const Layout: React.FC = ({ children }) => { }, [location.pathname]); return ( -
+
{showLoader && setShowLoader(false)} />} - {/* ── GLOBAL BAYERN CSS ────────────────────────── */} + {/* ── GLOBAL CSS ───────────────────────────────── */} - - {/* ── TOP BAR ──────────────────────────────────── */} -
- {/* LEFT — logo */} -
- - Der Bayerische Mittelstandspreis 2026 - + {/* Logo */} + + BMP + + BAY.
MITTEL
STANDS
PREIS +
+ + + {/* Gold vertical line */} +
+ + {/* Hamburger */} + + + {/* Social icons */} +
+ {SOCIAL.map(s => )}
- {/* CENTER — hamburger (desktop only) */} - {!isMobile && ( -
- - - -
- )} - - {/* RIGHT — social + lang (desktop) | hamburger (mobile) */} - {isMobile ? ( -
- {/* Mobile CTA */} - BMP - {/* Mobile hamburger */} - -
- ) : ( -
-
- {SOCIAL.map(s => )} -
-
-
- {(['DE','EN'] as const).map((l, i) => ( - - {i===1 && |} - - - ))} -
-
- )} - -
+ {/* Lang switcher */} +
+ {(['DE','EN'] as const).map((l, i) => ( + + {i === 1 &&
} + + + ))} +
+ {/* ── OVERLAY ──────────────────────────────────── */}
-
+ {/* Background watermark */} +
EXZELLENZ HAT EINEN NAMEN.
+ {isMobile ? ( + /* ── MOBILE: single-column accordion ──────── */ +
- {/* TOP — tagline + logo */} -
- {/* Tagline */} -
-
- Bayerischer Mittelstandspreis -
-
- EXZELLENZ HAT
EINEN NAMEN. -
-
- {/* Logo top-right */} -
- BMP Logo + {/* Top bar */} +
+ setOpen(false)} style={{ display:'flex', alignItems:'center', textDecoration:'none' }}> + BMP Logo + +
+ + {/* Scrollable nav */} +
+ ) : ( + /* ── DESKTOP: 3-column grid ────────────────── */ +
- {/* BOTTOM — 3 columns */} -
- - {/* LEFT — main nav */} -
+ {/* 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', + padding:'16px 32px 16px 0', textDecoration:'none', - color: activeKey===item.key ? GOLD : '#fff', + 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)', + transition:'color .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', + transitionDelay: open ? `${.04 + idx * .06}s` : '0s', }} > {item.label} @@ -369,32 +426,30 @@ const Layout: React.FC = ({ children }) => {
- {/* MIDDLE — subnav */} -
+ {/* MIDDLE: subnav panels */} +
{NAV.map(item => (
-
- Überblick -
-
- {item.subLabel} -
+
Überblick
+
{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'; }} + style={{ display:'block', padding:'9px 0', textDecoration:'none', color:MUTED, fontSize:14, 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}
  • ))} @@ -404,529 +459,193 @@ const Layout: React.FC = ({ children }) => { ))}
- {/* RIGHT — utilities */} -
+ {/* RIGHT: utilities */} +
- {/* Lang + Social on one row */} -
-
- {(['DE','EN'] as const).map((l,i) => ( - - {i===1 && |} - - + {/* Top row: close + logo */} +
+ +
+ BMP Logo +
+
+ + {/* Bottom section */} +
+ + {/* Lang + Social */} +
+
+ {(['DE','EN'] as const).map((l, i) => ( + + {i === 1 && |} + + + ))} +
+
+ {SOCIAL.map(s => )} +
+
+ + {/* Secondary links */} +
+ {SECONDARY.map(item => ( + setOpen(false)} + style={{ fontSize:13, 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} ))}
-
- {SOCIAL.map(s => )} + + {/* CTAs */} +
+ setOpen(false)} + style={{ display:'inline-block', background:GOLD, color:'#101828', fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'13px 28px', textDecoration:'none', fontFamily:FF_DISPLAY, transition:'background .2s, box-shadow .2s' }} + 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 = GOLD; e.currentTarget.style.boxShadow = 'none'; }} + >Jetzt bewerben + setOpen(false)} + style={{ display:'inline-block', background:'transparent', color:MUTED, fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'11px 26px', textDecoration:'none', fontFamily:FF_DISPLAY, border:`1.5px solid rgba(239,191,4,0.35)`, transition:'border-color .2s, color .2s' }} + onMouseEnter={e => { e.currentTarget.style.borderColor = GOLD; e.currentTarget.style.color = GOLD; }} + onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(239,191,4,0.35)'; e.currentTarget.style.color = MUTED; }} + >Mitglied werden +
+ setOpen(false)} + style={{ display:'flex', alignItems:'center', gap:10, background:'rgba(255,255,255,0.04)', color:'rgba(255,255,255,0.45)', fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.14em', padding:'10px 18px', textDecoration:'none', fontFamily:FF_DISPLAY, border:'1px dashed rgba(255,255,255,0.15)', transition:'color .2s, border-color .2s, background .2s' }} + onMouseEnter={e => { e.currentTarget.style.color='#fff'; e.currentTarget.style.borderColor='rgba(255,255,255,0.4)'; e.currentTarget.style.background='rgba(255,255,255,0.07)'; }} + onMouseLeave={e => { e.currentTarget.style.color='rgba(255,255,255,0.45)'; e.currentTarget.style.borderColor='rgba(255,255,255,0.15)'; e.currentTarget.style.background='rgba(255,255,255,0.04)'; }} + > Formular hochladen
+
- - {/* Secondary links */} -
- {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} - ))} -
- - {/* CTAs */} -
- 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'; }} - >Jetzt bewerben - setOpen(false)} - style={{ display:'inline-block', background:'transparent', color:MUTED, fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'10px 24px', textDecoration:'none', transition:'border-color .2s, color .2s', borderRadius:0, fontFamily:FF_DISPLAY, border:'1.5px solid rgba(239,191,4,0.35)' }} - onMouseEnter={e=>{ e.currentTarget.style.borderColor='#EFBF04'; e.currentTarget.style.color='#EFBF04'; }} - onMouseLeave={e=>{ e.currentTarget.style.borderColor='rgba(239,191,4,0.35)'; e.currentTarget.style.color=MUTED; }} - >Mitglied werden - - {/* ── Upload separator ── */} -
- - setOpen(false)} - style={{ - display:'flex', alignItems:'center', gap:10, - background:'rgba(255,255,255,0.04)', - color:'rgba(255,255,255,0.45)', - fontSize:11, fontWeight:700, - textTransform:'uppercase', letterSpacing:'.14em', - padding:'10px 18px', - textDecoration:'none', - fontFamily:FF_DISPLAY, - border:'1px dashed rgba(255,255,255,0.15)', - transition:'color .2s, border-color .2s, background .2s', - }} - onMouseEnter={e=>{ - e.currentTarget.style.color='#fff'; - e.currentTarget.style.borderColor='rgba(255,255,255,0.4)'; - e.currentTarget.style.background='rgba(255,255,255,0.07)'; - }} - onMouseLeave={e=>{ - e.currentTarget.style.color='rgba(255,255,255,0.45)'; - e.currentTarget.style.borderColor='rgba(255,255,255,0.15)'; - e.currentTarget.style.background='rgba(255,255,255,0.04)'; - }} - > - Formular hochladen - -
-
-
+ )}
- {/* ── MOBILE MENU OVERLAY ──────────────────────── */} - {isMobile && ( -
- {/* Mobile menu top bar */} -
- setMobileMenuOpen(false)} style={{ display:'flex', alignItems:'center', textDecoration:'none' }}> - BMP Logo - - -
- - {/* Mobile nav links — top-level only, vertical list */} - -
- )} - {/* ── FLOATING CTA ─────────────────────────────── */} {location.pathname === '/' && !formVisible && ( { (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'; }} + onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.color = '#101828'; }} + onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = NAVY; (e.currentTarget as HTMLElement).style.color = '#fff'; }} > Jetzt bewerben )} {/* ── MAIN ─────────────────────────────────────── */} -
{children}
+
{children}
{/* ── FOOTER ───────────────────────────────────── */} -
); };