chore: migrate to payload / nextjs
This commit is contained in:
78
src/spa/NextApp.tsx
Normal file
78
src/spa/NextApp.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import Layout from '@/spa/components/layout/Layout'
|
||||
import ScrollToTop from '@/spa/components/layout/ScrollToTop'
|
||||
import About from '@/spa/pages/About'
|
||||
import BlogDetail from '@/spa/pages/BlogDetail'
|
||||
import Contact from '@/spa/pages/Contact'
|
||||
import Datenschutz from '@/spa/pages/Datenschutz'
|
||||
import EventDetail from '@/spa/pages/EventDetail'
|
||||
import FormularUpload from '@/spa/pages/FormularUpload'
|
||||
import Home from '@/spa/pages/Home'
|
||||
import Impressum from '@/spa/pages/Impressum'
|
||||
import MitgliedWerden from '@/spa/pages/MitgliedWerden'
|
||||
import Netzwerk from '@/spa/pages/Netzwerk'
|
||||
import Participation from '@/spa/pages/Participation'
|
||||
import Preistraeger from '@/spa/pages/Preistraeger'
|
||||
import PreistraegerDetail from '@/spa/pages/PreistraegerDetail'
|
||||
import Press from '@/spa/pages/Press'
|
||||
|
||||
const routes: Record<string, React.ComponentType> = {
|
||||
'/': Home,
|
||||
'/teilnahme': Participation,
|
||||
'/preistraeger': Preistraeger,
|
||||
'/der-bmp': About,
|
||||
'/netzwerk': Netzwerk,
|
||||
'/presse': Press,
|
||||
'/kontakt': Contact,
|
||||
'/formular-hochladen': FormularUpload,
|
||||
'/mitglied-werden': MitgliedWerden,
|
||||
'/datenschutz': Datenschutz,
|
||||
'/impressum': Impressum,
|
||||
}
|
||||
|
||||
function resolvePage(pathname: string) {
|
||||
if (routes[pathname]) return routes[pathname]
|
||||
if (pathname.startsWith('/preistraeger/')) return PreistraegerDetail
|
||||
if (pathname.startsWith('/presse/events/')) return EventDetail
|
||||
if (pathname.startsWith('/presse/blog/')) return BlogDetail
|
||||
return Home
|
||||
}
|
||||
|
||||
export default function NextApp({ pathname }: { pathname: string }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setTimeout(() => setLoading(false), 1500)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[1000] bg-primary flex flex-col items-center justify-center">
|
||||
<div className="relative">
|
||||
<div className="w-24 h-24 border-2 border-accent/20 border-t-accent rounded-full animate-spin" />
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-accent font-display font-bold text-2xl">BMP</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 text-white/40 uppercase tracking-[0.4em] text-[10px] font-bold animate-pulse">
|
||||
Exzellenz wird geladen...
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Page = resolvePage(pathname)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollToTop />
|
||||
<Layout>
|
||||
<Page />
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
158
src/spa/components/LoginGate.tsx
Normal file
158
src/spa/components/LoginGate.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const SESSION_KEY = 'bmp_auth';
|
||||
const VALID_USER = 'newedgebrand';
|
||||
const VALID_PASS = 'n3wedg3br4nd2o26';
|
||||
|
||||
export function isAuthenticated(): boolean {
|
||||
return sessionStorage.getItem(SESSION_KEY) === '1';
|
||||
}
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
export default function LoginGate({ children }: { children: React.ReactNode }) {
|
||||
const [authed, setAuthed] = useState(isAuthenticated);
|
||||
const [user, setUser] = useState('');
|
||||
const [pass, setPass] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [shake, setShake] = useState(false);
|
||||
|
||||
if (authed) return <>{children}</>;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (user === VALID_USER && pass === VALID_PASS) {
|
||||
sessionStorage.setItem(SESSION_KEY, '1');
|
||||
setAuthed(true);
|
||||
} else {
|
||||
setError('Ungültige Zugangsdaten.');
|
||||
setShake(true);
|
||||
setTimeout(() => setShake(false), 600);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed', inset: 0, zIndex: 9999,
|
||||
background: '#111D55',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
||||
fontFamily: FF,
|
||||
}}>
|
||||
{/* Subtle grid overlay */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, opacity: 0.04,
|
||||
backgroundImage: 'linear-gradient(#EFBF04 1px, transparent 1px), linear-gradient(90deg, #EFBF04 1px, transparent 1px)',
|
||||
backgroundSize: '60px 60px',
|
||||
pointerEvents: 'none',
|
||||
}} />
|
||||
|
||||
{/* Logo mark */}
|
||||
<div style={{ position: 'relative', zIndex: 1, textAlign: 'center', marginBottom: 48 }}>
|
||||
<div style={{
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
width: 64, height: 64,
|
||||
border: '2px solid #EFBF04',
|
||||
marginBottom: 20,
|
||||
}}>
|
||||
<span style={{ fontFamily: FF, fontWeight: 700, fontSize: 22, color: '#EFBF04', letterSpacing: '0.05em' }}>BMP</span>
|
||||
</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.35)', fontSize: 10, letterSpacing: '0.35em', textTransform: 'uppercase', fontWeight: 700 }}>
|
||||
Bayerischer Mittelstandspreis
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form card */}
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
style={{
|
||||
position: 'relative', zIndex: 1,
|
||||
width: '100%', maxWidth: 380,
|
||||
padding: '40px 40px 36px',
|
||||
background: 'rgba(255,255,255,0.04)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
animation: shake ? 'bmp-shake 0.5s ease' : undefined,
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 28 }}>
|
||||
<div style={{ color: 'white', fontSize: 18, fontWeight: 300, marginBottom: 6, letterSpacing: '0.01em' }}>
|
||||
Zugang
|
||||
</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12 }}>
|
||||
Bitte melden Sie sich an, um fortzufahren.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 10, fontWeight: 700, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 8 }}>
|
||||
Benutzername
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={user}
|
||||
onChange={e => { setUser(e.target.value); setError(''); }}
|
||||
autoComplete="username"
|
||||
style={{
|
||||
width: '100%', padding: '12px 14px',
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
border: `1px solid ${error ? '#e53e3e' : 'rgba(255,255,255,0.15)'}`,
|
||||
color: 'white', fontSize: 14, outline: 'none',
|
||||
fontFamily: FF, boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 28 }}>
|
||||
<label style={{ display: 'block', fontSize: 10, fontWeight: 700, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 8 }}>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={pass}
|
||||
onChange={e => { setPass(e.target.value); setError(''); }}
|
||||
autoComplete="current-password"
|
||||
style={{
|
||||
width: '100%', padding: '12px 14px',
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
border: `1px solid ${error ? '#e53e3e' : 'rgba(255,255,255,0.15)'}`,
|
||||
color: 'white', fontSize: 14, outline: 'none',
|
||||
fontFamily: FF, boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{ marginBottom: 16, color: '#fc8181', fontSize: 12 }}>{error}</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
style={{
|
||||
width: '100%', padding: '14px',
|
||||
background: '#EFBF04', border: 'none', cursor: 'pointer',
|
||||
color: '#111D55', fontFamily: FF, fontSize: 12, fontWeight: 700,
|
||||
letterSpacing: '0.2em', textTransform: 'uppercase',
|
||||
transition: 'background 0.15s, box-shadow 0.2s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1, marginTop: 32, color: 'rgba(255,255,255,0.2)', fontSize: 10, letterSpacing: '0.2em' }}>
|
||||
© {new Date().getFullYear()} New Edge Brand
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes bmp-shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20% { transform: translateX(-8px); }
|
||||
40% { transform: translateX(8px); }
|
||||
60% { transform: translateX(-6px); }
|
||||
80% { transform: translateX(6px); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
129
src/spa/components/TestimonialsSection.tsx
Normal file
129
src/spa/components/TestimonialsSection.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
export const TESTIMONIALS = [
|
||||
{ img: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=600", name: "Dr. Maria Huber", role: "Geschäftsführerin", company: "TechVision GmbH", year: "2025", quote: "Der BMP hat uns neue Türen geöffnet. Die Auszeichnung ist ein wichtiges Qualitätssiegel für unser gesamtes Team." },
|
||||
{ img: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=600", name: "Hannes Weber", role: "Inhaber & CEO", company: "Weber Präzision GmbH", year: "2023", quote: "Der Gewinn war ein Moment des Stolzes für die gesamte Belegschaft. Die höchste Anerkennung für unsere tägliche Arbeit." },
|
||||
{ img: "https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&q=80&w=600", name: "Lisa Berger", role: "Gründerin & CEO", company: "EcoTech Bavaria", year: "2024", quote: "Das Netzwerk, das wir durch den BMP gewonnen haben, ist unbezahlbar. Wir fanden Partner, die unsere Werte teilen." },
|
||||
{ img: "https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=600", name: "Dr. Michael Schuh", role: "Vorstand", company: "Bayerischer Maschinenbau", year: "2024", quote: "Der Preis hat unsere Sichtbarkeit weit über die bayerischen Grenzen hinaus gestärkt. Ein echtes Premium-Siegel." },
|
||||
{ img: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=600", name: "Anna Kellner", role: "Mitgründerin", company: "AlpinTech Solutions", year: "2024", quote: "Der BMP hat unsere Employer Brand transformiert. Wir gewinnen heute die besten Talente, weil wir Preisträger sind." },
|
||||
];
|
||||
|
||||
export default function TestimonialsSection() {
|
||||
const [active, setActive] = useState(0);
|
||||
const t = TESTIMONIALS[active];
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const prev = () => setActive(i => (i - 1 + TESTIMONIALS.length) % TESTIMONIALS.length);
|
||||
const next = () => setActive(i => (i + 1) % TESTIMONIALS.length);
|
||||
|
||||
return (
|
||||
<section style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
{/* ── MOBILE: headed section + premium testimonial card ── */}
|
||||
{isMobile && (
|
||||
<div style={{ padding: '48px 24px 52px', position: 'relative', zIndex: 1 }}>
|
||||
{/* Section header */}
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>Stimmen der Preisträger</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 8vw, 2.3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 16px' }}>
|
||||
DAS SAGEN UNSERE<br />PREISTRÄGER.
|
||||
</h2>
|
||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
||||
|
||||
{/* Card */}
|
||||
<div style={{ background: '#fff', borderRadius: 20, border: '1px solid rgba(17,29,85,0.10)', boxShadow: '0 16px 38px rgba(3,9,58,0.10)', overflow: 'hidden' }}>
|
||||
{/* Photo banner */}
|
||||
<div style={{ position: 'relative', height: 240, overflow: 'hidden' }}>
|
||||
<img key={t.img} src={t.img} alt={t.name} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top', display: 'block' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(3,9,58,0.28) 0%, transparent 45%)' }} />
|
||||
<span style={{ position: 'absolute', top: 14, left: 14, fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#101828', background: '#EFBF04', padding: '5px 10px', borderRadius: 100 }}>Preisträger {t.year}</span>
|
||||
</div>
|
||||
{/* Body */}
|
||||
<div style={{ padding: '22px 22px 24px' }}>
|
||||
<span aria-hidden style={{ fontFamily: 'Georgia, serif', fontSize: 52, lineHeight: 0.4, color: '#EFBF04', display: 'block', height: 26, marginBottom: 6 }}>“</span>
|
||||
<p style={{ fontFamily: FF, fontSize: '1.2rem', fontWeight: 300, color: '#101828', lineHeight: 1.5, margin: '0 0 22px' }}>{t.quote}</p>
|
||||
<div style={{ width: 36, height: 2, background: '#EFBF04', marginBottom: 16 }} />
|
||||
<div style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#101828', marginBottom: 4 }}>{t.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 14, color: 'rgba(16,24,40,0.5)' }}>{t.role} · {t.company}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 24 }}>
|
||||
<button onClick={prev} aria-label="Vorheriges" style={{ width: 46, height: 46, borderRadius: 12, border: '1.5px solid #D0D5DD', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<ChevronRight size={16} style={{ transform: 'rotate(180deg)' }} />
|
||||
</button>
|
||||
<button onClick={next} aria-label="Nächstes" style={{ width: 46, height: 46, borderRadius: 12, border: '1.5px solid #111D55', background: '#111D55', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', flexShrink: 0 }}>
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
<div style={{ display: 'flex', gap: 6, marginLeft: 6 }}>
|
||||
{TESTIMONIALS.map((_, i) => (
|
||||
<button key={i} onClick={() => setActive(i)} aria-label={`Stimme ${i + 1}`} style={{ width: i === active ? 22 : 7, height: 7, borderRadius: 100, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── DESKTOP ── */}
|
||||
{!isMobile && (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '42% 58%', height: 560, position: 'relative', zIndex: 1 }}>
|
||||
{/* Left – portrait photo */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', height: isMobile ? 280 : 'auto' }}>
|
||||
<img
|
||||
key={t.img}
|
||||
src={t.img}
|
||||
alt={t.name}
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top', display: 'block', transition: 'opacity 0.4s' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to bottom, transparent 70%, #fff 100%)' : 'linear-gradient(to right, transparent 60%, #fff 100%)' }} />
|
||||
</div>
|
||||
|
||||
{/* Right – quote */}
|
||||
<div style={{ padding: isMobile ? '40px 22px 56px' : '80px 72px 80px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>Stimmen der Preisträger</span>
|
||||
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 2.6vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 36px' }}>
|
||||
DAS SAGEN UNSERE PREISTRÄGER.
|
||||
</h2>
|
||||
|
||||
<p style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 300, color: '#101828', lineHeight: 1.55, margin: '0 0 40px' }}>
|
||||
{t.quote}
|
||||
</p>
|
||||
|
||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 20 }} />
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: '#101828', marginBottom: 4 }}>{t.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 12, color: 'rgba(16,24,40,0.5)' }}>{t.role} · {t.company}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: '#4A8FC9', letterSpacing: '0.1em', textTransform: 'uppercase', marginTop: 4 }}>Preisträger {t.year}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 40 }}>
|
||||
<button onClick={prev} style={{ width: isMobile ? 44 : 40, height: isMobile ? 44 : 40, border: '1.5px solid #D0D5DD', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = '#D0D5DD'; (e.currentTarget as HTMLElement).style.background = 'transparent'; (e.currentTarget as HTMLElement).style.color = 'inherit'; }}
|
||||
>
|
||||
<ChevronRight size={16} style={{ transform: 'rotate(180deg)' }} />
|
||||
</button>
|
||||
<button onClick={next} style={{ width: isMobile ? 44 : 40, height: isMobile ? 44 : 40, border: '1.5px solid #111D55', background: '#111D55', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s', color: '#fff' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.borderColor = '#EFBF04'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; }}
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
<div style={{ display: 'flex', gap: 6, marginLeft: 8 }}>
|
||||
{TESTIMONIALS.map((_, i) => (
|
||||
<button key={i} onClick={() => setActive(i)} style={{ width: i === active ? 20 : 6, height: 6, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
182
src/spa/components/WegZurAuszeichnungSection.tsx
Normal file
182
src/spa/components/WegZurAuszeichnungSection.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import React from 'react';
|
||||
import { FileText, Search, UserPlus, Trophy } from 'lucide-react';
|
||||
import {
|
||||
ContainerScroll,
|
||||
ContainerSticky,
|
||||
ProcessCard,
|
||||
ProcessCardBody,
|
||||
ProcessCardTitle,
|
||||
} from '@/spa/components/ui/process-timeline';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
|
||||
const STEPS = [
|
||||
{ step: '01', title: 'Online-Einreichung', desc: 'Bewerbungsformular vollständig ausfüllen. Die Teilnahme ist kostenfrei und ohne bürokratischen Aufwand möglich.', Icon: FileText, date: 'Bis 30. Juni 2026' },
|
||||
{ step: '02', title: 'Formale Vorprüfung', desc: 'Das Gremium sichtet alle Unterlagen auf Vollständigkeit, KMU-Konformität und regionale Zugehörigkeit im Freistaat.', Icon: Search, date: 'Juli 2026' },
|
||||
{ step: '03', title: 'Audit & Jury-Sitzung',desc: 'Die besten Unternehmen werden durch unabhängige Experten-Audits vor Ort evaluiert und in der Jury-Sitzung bewertet.', Icon: UserPlus, date: 'Aug – Sep 2026' },
|
||||
{ step: '04', title: 'Gala-Preisverleihung',desc: 'Die Gewinner werden im Rahmen der feierlichen Gala in München vor geladenen Gästen aus Wirtschaft und Politik geehrt.', Icon: Trophy, date: 'Oktober 2026' },
|
||||
];
|
||||
|
||||
export default function WegZurAuszeichnungSection() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// ── MOBILE: stacked vertical list (no scroll-jack, no horizontal overflow) ──
|
||||
if (isMobile) {
|
||||
return (
|
||||
<section id="schritte" style={{ background: NAVY, padding: '56px 22px' }}>
|
||||
{/* Section header */}
|
||||
<div style={{ marginBottom: 36 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Schritt für Schritt</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.05, margin: '0 0 16px' }}>
|
||||
IHR WEG ZUM<br />PREIS.
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, margin: 0 }}>
|
||||
Von der Einreichung bis zur Gala – vier klar definierte Schritte auf dem Weg zur höchsten Auszeichnung des bayerischen Mittelstands.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stacked step cards */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{STEPS.map((item, index) => {
|
||||
const Icon = item.Icon;
|
||||
return (
|
||||
<div key={item.step} style={{ border: '1px solid rgba(239,191,4,0.25)', background: '#111D55', padding: '24px 22px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
||||
<div style={{ width: 44, height: 44, border: '1px solid rgba(239,191,4,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<Icon size={18} style={{ color: GOLD }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.18em', textTransform: 'uppercase' }}>Schritt {item.step}</span>
|
||||
<span style={{ width: 1, height: 11, background: 'rgba(255,255,255,0.2)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.06em' }}>{item.date}</span>
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: '1.35rem', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: 0 }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.68)', lineHeight: 1.7, margin: 0 }}>
|
||||
{item.desc}
|
||||
</p>
|
||||
{/* Step progress indicator */}
|
||||
<div style={{ display: 'flex', gap: 4, marginTop: 20 }}>
|
||||
{[0, 1, 2, 3].map(i => (
|
||||
<div key={i} style={{ height: 2, flex: i === index ? 2 : 1, background: i === index ? GOLD : 'rgba(255,255,255,0.1)' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContainerScroll id="schritte" className="h-[380vh]" style={{ background: NAVY }}>
|
||||
<ContainerSticky className="top-0 h-screen flex flex-col">
|
||||
|
||||
{/* Section header */}
|
||||
<div style={{ padding: '56px 80px 40px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40, alignItems: 'flex-end', borderBottom: '1px solid rgba(255,255,255,0.07)', flexShrink: 0 }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 14 }}>Schritt für Schritt</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 3vw, 2.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.03, margin: 0 }}>
|
||||
IHR WEG ZUM<br />PREIS.
|
||||
</h2>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 14, color: 'rgba(255,255,255,0.65)', lineHeight: 1.8, margin: 0 }}>
|
||||
Von der Einreichung bis zur Gala – vier klar definierte Schritte auf dem Weg zur höchsten Auszeichnung des bayerischen Mittelstands.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Scroll hint – prominent */}
|
||||
<div style={{ padding: '20px 80px', display: 'flex', alignItems: 'center', gap: 14, flexShrink: 0 }}>
|
||||
{/* Animated chevron stack */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{[0, 1, 2].map(i => (
|
||||
<svg
|
||||
key={i}
|
||||
width="12" height="7" viewBox="0 0 12 7" fill="none"
|
||||
style={{ opacity: 1 - i * 0.3, animation: `scrollBounce 1.6s ease-in-out ${i * 0.18}s infinite` }}
|
||||
>
|
||||
<polyline points="1,1 6,6 11,1" stroke={GOLD} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.55)' }}>
|
||||
Scrollen zum Erkunden
|
||||
</span>
|
||||
{/* Progress bar */}
|
||||
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,0.07)', position: 'relative', overflow: 'hidden', maxWidth: 200 }}>
|
||||
<div style={{ position: 'absolute', left: 0, top: 0, height: '100%', width: '25%', background: GOLD, opacity: 0.6 }} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.1em' }}>01 / 04</span>
|
||||
</div>
|
||||
|
||||
{/* Cards strip */}
|
||||
<div className="flex flex-nowrap flex-1 items-stretch px-20 pb-16 gap-0">
|
||||
{STEPS.map((item, index) => {
|
||||
const Icon = item.Icon;
|
||||
return (
|
||||
<ProcessCard
|
||||
key={item.step}
|
||||
itemsLength={4}
|
||||
index={index}
|
||||
variant="bmp"
|
||||
className="min-w-[70%] max-w-[70%] flex-shrink-0"
|
||||
>
|
||||
{/* Left accent strip with step number */}
|
||||
<ProcessCardTitle className="border-r border-[rgba(239,191,4,0.2)] flex flex-col items-center justify-between py-8 px-5 min-w-[80px]">
|
||||
<div style={{ width: 40, height: 40, border: '1px solid rgba(239,191,4,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Icon size={16} style={{ color: GOLD }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, fontWeight: 900, color: 'rgba(239,191,4,0.4)', letterSpacing: '0.1em', writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}>
|
||||
{item.step}
|
||||
</span>
|
||||
</ProcessCardTitle>
|
||||
|
||||
{/* Card body */}
|
||||
<ProcessCardBody className="flex flex-col justify-between py-10 px-10 gap-6">
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.22em', textTransform: 'uppercase' }}>
|
||||
Schritt {item.step}
|
||||
</span>
|
||||
<span style={{ width: 1, height: 12, background: 'rgba(255,255,255,0.2)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.08em' }}>{item.date}</span>
|
||||
</div>
|
||||
<div style={{ width: 32, height: 1, background: GOLD, marginBottom: 20, opacity: 0.5 }} />
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.5rem, 2.5vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: 0 }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.68)', lineHeight: 1.75, margin: 0 }}>
|
||||
{item.desc}
|
||||
</p>
|
||||
{/* Step progress indicator */}
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{[0,1,2,3].map(i => (
|
||||
<div key={i} style={{ height: 2, flex: i === index ? 2 : 1, background: i === index ? GOLD : 'rgba(255,255,255,0.1)', transition: 'flex 0.3s' }} />
|
||||
))}
|
||||
</div>
|
||||
</ProcessCardBody>
|
||||
</ProcessCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ContainerSticky>
|
||||
|
||||
<style>{`
|
||||
@keyframes scrollBounce {
|
||||
0%, 100% { transform: translateY(0); opacity: inherit; }
|
||||
50% { transform: translateY(3px); }
|
||||
}
|
||||
`}</style>
|
||||
</ContainerScroll>
|
||||
);
|
||||
}
|
||||
1155
src/spa/components/editor/LayoutEditor.tsx
Normal file
1155
src/spa/components/editor/LayoutEditor.tsx
Normal file
File diff suppressed because it is too large
Load Diff
610
src/spa/components/forms/BewerbungsForm.tsx
Normal file
610
src/spa/components/forms/BewerbungsForm.tsx
Normal file
@@ -0,0 +1,610 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ArrowRight, ArrowLeft, Check, Trophy, User, Send, Building2, AlertCircle } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
/* ── Theme definitions ─────────────────────────────────────────────────── */
|
||||
|
||||
const DARK = {
|
||||
outerBg: 'transparent',
|
||||
outerBgImage: 'none',
|
||||
border: 'rgba(255,255,255,0.12)',
|
||||
borderActive: 'rgba(239,191,4,0.8)',
|
||||
progressBg: 'rgba(255,255,255,0.1)',
|
||||
progressFill: 'linear-gradient(to right,#EFBF04,rgba(239,191,4,0.75))',
|
||||
progressGlow: '0 0 8px rgba(239,191,4,0.6)',
|
||||
stepActive: '#fff',
|
||||
stepDone: '#EFBF04',
|
||||
stepInactive: 'rgba(255,255,255,0.25)',
|
||||
stepActiveBg: 'rgba(239,191,4,0.07)',
|
||||
stepUnderline: '#EFBF04',
|
||||
accentLabel: 'rgba(239,191,4,0.8)',
|
||||
heading: '#fff',
|
||||
inputBg: 'rgba(255,255,255,0.05)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.08)',
|
||||
inputBorder: 'rgba(255,255,255,0.18)',
|
||||
inputText: '#fff',
|
||||
inputPlaceholder: 'rgba(255,255,255,0.35)',
|
||||
fieldLabel: 'rgba(255,255,255,0.45)',
|
||||
fieldError: '#f87171',
|
||||
cardBg: 'rgba(255,255,255,0.06)',
|
||||
cardBgSelected: 'rgba(239,191,4,0.1)',
|
||||
cardBorder: 'rgba(255,255,255,0.22)',
|
||||
cardBorderSel: '#EFBF04',
|
||||
cardText: '#fff',
|
||||
cardTextSel: '#EFBF04',
|
||||
cardDesc: 'rgba(255,255,255,0.65)',
|
||||
optBg: 'rgba(255,255,255,0.05)',
|
||||
optBgSel: 'rgba(239,191,4,0.12)',
|
||||
optBorder: 'rgba(255,255,255,0.22)',
|
||||
optBorderSel: '#EFBF04',
|
||||
optText: 'rgba(255,255,255,0.7)',
|
||||
optTextSel: '#EFBF04',
|
||||
bodyText: 'rgba(255,255,255,0.6)',
|
||||
mutedText: 'rgba(255,255,255,0.35)',
|
||||
rowLabel: 'rgba(255,255,255,0.35)',
|
||||
rowValue: '#fff',
|
||||
alertIcon: '#EFBF04',
|
||||
alertTitle: '#EFBF04',
|
||||
alertEmail: '#EFBF04',
|
||||
btnBg: '#EFBF04',
|
||||
btnBgHover: '#FFD130',
|
||||
btnText: '#101828',
|
||||
backText: 'rgba(255,255,255,0.35)',
|
||||
backTextHover: '#fff',
|
||||
successIcon: '#EFBF04',
|
||||
successIconInner: '#111D55',
|
||||
successHeading: '#fff',
|
||||
successBody: 'rgba(255,255,255,0.55)',
|
||||
successName: '#fff',
|
||||
successAccent: '#EFBF04',
|
||||
};
|
||||
|
||||
const GOLD = {
|
||||
outerBg: 'transparent',
|
||||
outerBgImage: 'none',
|
||||
border: 'rgba(17,29,85,0.15)',
|
||||
borderActive: '#111D55',
|
||||
progressBg: 'rgba(17,29,85,0.12)',
|
||||
progressFill: 'linear-gradient(to right,#111D55,#051A87)',
|
||||
progressGlow: 'none',
|
||||
stepActive: '#111D55',
|
||||
stepDone: 'rgba(17,29,85,0.55)',
|
||||
stepInactive: 'rgba(17,29,85,0.28)',
|
||||
stepActiveBg: 'rgba(17,29,85,0.07)',
|
||||
stepUnderline: '#111D55',
|
||||
accentLabel: 'rgba(17,29,85,0.5)',
|
||||
heading: '#111D55',
|
||||
inputBg: 'rgba(255,255,255,0.5)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.9)',
|
||||
inputBorder: 'rgba(17,29,85,0.18)',
|
||||
inputText: '#111D55',
|
||||
inputPlaceholder: 'rgba(17,29,85,0.35)',
|
||||
fieldLabel: 'rgba(17,29,85,0.5)',
|
||||
fieldError: '#b91c1c',
|
||||
cardBg: 'rgba(255,255,255,0.38)',
|
||||
cardBgSelected: 'rgba(17,29,85,0.1)',
|
||||
cardBorder: 'rgba(17,29,85,0.2)',
|
||||
cardBorderSel: '#111D55',
|
||||
cardText: '#111D55',
|
||||
cardTextSel: '#111D55',
|
||||
cardDesc: 'rgba(17,29,85,0.6)',
|
||||
optBg: 'rgba(255,255,255,0.4)',
|
||||
optBgSel: 'rgba(17,29,85,0.1)',
|
||||
optBorder: 'rgba(17,29,85,0.2)',
|
||||
optBorderSel: '#111D55',
|
||||
optText: 'rgba(17,29,85,0.65)',
|
||||
optTextSel: '#111D55',
|
||||
bodyText: 'rgba(17,29,85,0.65)',
|
||||
mutedText: 'rgba(17,29,85,0.4)',
|
||||
rowLabel: 'rgba(17,29,85,0.4)',
|
||||
rowValue: '#111D55',
|
||||
alertIcon: '#111D55',
|
||||
alertTitle: '#111D55',
|
||||
alertEmail: '#111D55',
|
||||
btnBg: '#111D55',
|
||||
btnBgHover: '#051A87',
|
||||
btnText: '#fff',
|
||||
backText: 'rgba(17,29,85,0.4)',
|
||||
backTextHover: '#111D55',
|
||||
successIcon: '#111D55',
|
||||
successIconInner: '#EFBF04',
|
||||
successHeading: '#111D55',
|
||||
successBody: 'rgba(17,29,85,0.6)',
|
||||
successName: '#111D55',
|
||||
successAccent: '#111D55',
|
||||
};
|
||||
|
||||
type C = typeof DARK;
|
||||
|
||||
/* ── Data / Steps ─────────────────────────────────────────────────────── */
|
||||
|
||||
type FormData = {
|
||||
type: 'selbst' | 'vorschlag' | '';
|
||||
mitarbeiter: string;
|
||||
standortBayern: 'ja' | 'nein' | '';
|
||||
familiengeführt: 'ja' | 'nein' | '';
|
||||
firmenname: string;
|
||||
kontaktName: string;
|
||||
email: string;
|
||||
telefon: string;
|
||||
nomFirma: string;
|
||||
nomBranche: string;
|
||||
nomStandort: string;
|
||||
nomName: string;
|
||||
nomEmail: string;
|
||||
nomBeziehung: string;
|
||||
};
|
||||
|
||||
const STEPS_SELBST = [
|
||||
{ label: 'Einreichung', icon: Trophy },
|
||||
{ label: 'Schnell-Check',icon: Check },
|
||||
{ label: 'Kontakt', icon: User },
|
||||
{ label: 'Absenden', icon: Send },
|
||||
];
|
||||
|
||||
const STEPS_VORSCHLAG = [
|
||||
{ label: 'Einreichung', icon: Trophy },
|
||||
{ label: 'Unternehmen', icon: Building2},
|
||||
{ label: 'Nominierender',icon: User },
|
||||
{ label: 'Absenden', icon: Send },
|
||||
];
|
||||
|
||||
const MITARBEITER_OPTIONS = [
|
||||
{ value: 'unter10', label: 'Unter 10' },
|
||||
{ value: '10-50', label: '10 – 50' },
|
||||
{ value: '51-200', label: '51 – 200' },
|
||||
{ value: '201-500', label: '201 – 500'},
|
||||
{ value: 'über500', label: 'Über 500' },
|
||||
];
|
||||
|
||||
const variants = {
|
||||
enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }),
|
||||
center: { x: 0, opacity: 1 },
|
||||
exit: (dir: number) => ({ x: dir > 0 ? -40 : 40, opacity: 0 }),
|
||||
};
|
||||
|
||||
/* ── Sub-components ───────────────────────────────────────────────────── */
|
||||
|
||||
function Field({ label, error, children, optional, c }: {
|
||||
label: string; error?: string; children: React.ReactNode; optional?: boolean; c: C;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: c.fieldLabel }}>
|
||||
{label}{!optional && ' *'}
|
||||
</label>
|
||||
{children}
|
||||
{error && <span style={{ fontFamily: FF, fontSize: 11, color: c.fieldError }}>{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: isMobile ? 44 : 38, padding: isMobile ? '0 14px' : '0 12px',
|
||||
fontFamily: FF, fontSize: isMobile ? 16 : 13, color: c.inputText,
|
||||
border: `1px solid ${focused ? c.borderActive : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
transition: 'border-color 0.15s,background 0.15s',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TypeCard({ selected, onClick, title, desc, c }: {
|
||||
selected: boolean; onClick: () => void; title: string; desc: string; c: C;
|
||||
}) {
|
||||
return (
|
||||
<button type="button" onClick={onClick} style={{
|
||||
flex: 1, padding: '13px 12px', textAlign: 'left', cursor: 'pointer',
|
||||
border: `1.5px solid ${selected ? c.cardBorderSel : c.cardBorder}`,
|
||||
background: selected ? c.cardBgSelected : c.cardBg,
|
||||
fontFamily: FF, transition: 'all 0.15s',
|
||||
display: 'flex', flexDirection: 'column', gap: 6,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: selected ? c.cardTextSel : c.cardText }}>{title}</span>
|
||||
<div style={{ width: 14, height: 14, borderRadius: '50%', border: `2px solid ${selected ? c.cardBorderSel : c.border}`, background: selected ? c.cardBorderSel : 'transparent', transition: 'all 0.15s' }} />
|
||||
</div>
|
||||
<span style={{ fontSize: 13, color: c.cardDesc, lineHeight: 1.5 }}>{desc}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function OptionButton({ selected, onClick, label, c, style }: {
|
||||
selected: boolean; onClick: () => void; label: string; c: C; style?: React.CSSProperties;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<button type="button" onClick={onClick} style={{
|
||||
padding: isMobile ? '12px 18px' : '8px 16px', minHeight: isMobile ? 44 : undefined,
|
||||
fontFamily: FF, fontSize: isMobile ? 14 : 12, fontWeight: 600, cursor: 'pointer',
|
||||
border: `1.5px solid ${selected ? c.optBorderSel : c.optBorder}`,
|
||||
background: selected ? c.optBgSel : c.optBg,
|
||||
color: selected ? c.optTextSel : c.optText,
|
||||
transition: 'all 0.15s',
|
||||
...style,
|
||||
}}>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function YesNo({ value, onChange, c }: {
|
||||
value: 'ja' | 'nein' | ''; onChange: (v: 'ja' | 'nein') => void; c: C;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const flexStyle = isMobile ? { flex: 1 } : undefined;
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<OptionButton selected={value === 'ja'} onClick={() => onChange('ja')} label="Ja" c={c} style={flexStyle} />
|
||||
<OptionButton selected={value === 'nein'} onClick={() => onChange('nein')} label="Nein" c={c} style={flexStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isEligible(data: FormData) {
|
||||
return ['10-50', '51-200', '201-500'].includes(data.mitarbeiter) && data.standortBayern === 'ja';
|
||||
}
|
||||
|
||||
/* ── Main component ───────────────────────────────────────────────────── */
|
||||
|
||||
export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) {
|
||||
const c = theme === 'gold' ? GOLD : DARK;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [step, setStep] = useState(0);
|
||||
const [dir, setDir] = useState(1);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [data, setData] = useState<FormData>({
|
||||
type: '', mitarbeiter: '', standortBayern: '', familiengeführt: '',
|
||||
firmenname: '', kontaktName: '', email: '', telefon: '',
|
||||
nomFirma: '', nomBranche: '', nomStandort: '',
|
||||
nomName: '', nomEmail: '', nomBeziehung: '',
|
||||
});
|
||||
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
|
||||
|
||||
const STEPS = data.type === 'vorschlag' ? STEPS_VORSCHLAG : STEPS_SELBST;
|
||||
const progress = step === 0 ? 0 : (step / (STEPS.length - 1)) * 100;
|
||||
|
||||
const set = (k: keyof FormData, v: string) => {
|
||||
setData(d => ({ ...d, [k]: v }));
|
||||
setErrors(e => { const n = { ...e }; delete n[k]; return n; });
|
||||
};
|
||||
|
||||
const validate = (): boolean => {
|
||||
const e: typeof errors = {};
|
||||
if (data.type === 'selbst') {
|
||||
if (step === 1) {
|
||||
if (!data.mitarbeiter) e.mitarbeiter = 'Bitte wählen';
|
||||
if (!data.standortBayern) e.standortBayern = 'Bitte wählen';
|
||||
if (!data.familiengeführt)e.familiengeführt= 'Bitte wählen';
|
||||
}
|
||||
if (step === 2) {
|
||||
if (!data.firmenname) e.firmenname = 'Pflichtfeld';
|
||||
if (!data.kontaktName) e.kontaktName = 'Pflichtfeld';
|
||||
if (!data.email) e.email = 'Pflichtfeld';
|
||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = 'Ungültige E-Mail';
|
||||
}
|
||||
} else {
|
||||
if (step === 1) {
|
||||
if (!data.nomFirma) e.nomFirma = 'Pflichtfeld';
|
||||
if (!data.nomBranche) e.nomBranche = 'Pflichtfeld';
|
||||
}
|
||||
if (step === 2) {
|
||||
if (!data.nomName) e.nomName = 'Pflichtfeld';
|
||||
if (!data.nomEmail) e.nomEmail = 'Pflichtfeld';
|
||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.nomEmail)) e.nomEmail = 'Ungültige E-Mail';
|
||||
}
|
||||
}
|
||||
setErrors(e);
|
||||
return Object.keys(e).length === 0;
|
||||
};
|
||||
|
||||
const next = () => { if (validate()) { setDir(1); setStep(s => s + 1); } };
|
||||
const prev = () => { setDir(-1); setStep(s => s - 1); };
|
||||
const submit = () => { if (validate()) setSubmitted(true); };
|
||||
|
||||
if (submitted) {
|
||||
return (
|
||||
<div style={{ fontFamily: FF, display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, borderTop: `1px solid ${c.border}`, background: c.outerBg, backgroundImage: c.outerBgImage }}>
|
||||
<div style={{ padding: '48px 28px', textAlign: 'center' }}>
|
||||
<div style={{ width: 52, height: 52, background: c.successIcon, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}>
|
||||
<Check size={22} color={c.successIconInner} strokeWidth={3} />
|
||||
</div>
|
||||
<h3 style={{ fontSize: 18, fontWeight: 900, color: c.successHeading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 10 }}>
|
||||
{data.type === 'selbst' ? 'Anfrage eingegangen' : 'Nominierung eingegangen'}
|
||||
</h3>
|
||||
<p style={{ fontSize: 13, color: c.successBody, maxWidth: 340, margin: '0 auto', lineHeight: 1.7 }}>
|
||||
{data.type === 'selbst'
|
||||
? <><span>Vielen Dank, </span><strong style={{ color: c.successName }}>{data.firmenname}</strong><span>. Wir senden Ihnen den vollständigen Fragebogen an </span><strong style={{ color: c.successAccent }}>{data.email}</strong>.</>
|
||||
: <><span>Vielen Dank, </span><strong style={{ color: c.successName }}>{data.nomName}</strong><span>. Wir nehmen Kontakt mit </span><strong style={{ color: c.successAccent }}>{data.nomFirma}</strong><span> auf und informieren sie über Ihre Nominierung.</span></>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, borderTop: `1px solid ${c.border}`, background: c.outerBg, backgroundImage: c.outerBgImage }}>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div style={{ height: 3, background: c.progressBg, flexShrink: 0 }}>
|
||||
<div style={{ height: '100%', backgroundImage: c.progressFill, width: `${progress}%`, transition: 'width 0.4s ease', boxShadow: c.progressGlow }} />
|
||||
</div>
|
||||
|
||||
{/* Step indicators */}
|
||||
<div style={{ display: 'flex', borderBottom: `1px solid ${c.border}`, flexShrink: 0 }}>
|
||||
{STEPS.map((s, i) => {
|
||||
const Icon = s.icon;
|
||||
const done = i < step;
|
||||
const active = i === step;
|
||||
return (
|
||||
<div key={i} style={{
|
||||
flex: 1, padding: '8px 4px', textAlign: 'center', fontFamily: FF, fontSize: 9,
|
||||
fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||
color: active ? c.stepActive : done ? c.stepDone : c.stepInactive,
|
||||
borderBottom: active ? `2px solid ${c.stepUnderline}` : '2px solid transparent',
|
||||
background: active ? c.stepActiveBg : 'transparent',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
|
||||
marginBottom: -1, transition: 'color 0.2s',
|
||||
}}>
|
||||
<Icon size={11} strokeWidth={active ? 2 : 1.5} />
|
||||
<span>{done ? '✓' : s.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: isMobile ? '18px 20px' : '20px 28px', flex: 1, minHeight: 0, overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<AnimatePresence mode="wait" custom={dir}>
|
||||
<motion.div key={`${step}-${data.type}`} custom={dir} variants={variants}
|
||||
initial="enter" animate="center" exit="exit"
|
||||
transition={{ duration: 0.22, ease: 'easeInOut' }}>
|
||||
|
||||
{/* ── Step 0: Typ-Wahl ── */}
|
||||
{step === 0 && (
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 1</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 14 }}>Art der Einreichung</h3>
|
||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 8 }}>
|
||||
<TypeCard c={c} selected={data.type === 'selbst'} title="Eigenbewerbung"
|
||||
desc="Ich bewerbe mein eigenes Unternehmen für den Bayerischen Mittelstandspreis."
|
||||
onClick={() => { set('type', 'selbst'); setDir(1); setStep(1); }} />
|
||||
<TypeCard c={c} selected={data.type === 'vorschlag'} title="Vorschlag"
|
||||
desc="Ich schlage ein anderes Unternehmen vor, das den Preis verdient."
|
||||
onClick={() => { set('type', 'vorschlag'); setDir(1); setStep(1); }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Eigenbewerbung Step 1: Schnell-Check ── */}
|
||||
{step === 1 && data.type === 'selbst' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Schnell-Check</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Grundlegende Eignung</h3>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<label style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: c.fieldLabel }}>Wie viele Mitarbeiter hat Ihr Unternehmen? *</label>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{MITARBEITER_OPTIONS.map(o => (
|
||||
<OptionButton c={c} key={o.value} selected={data.mitarbeiter === o.value} onClick={() => set('mitarbeiter', o.value)} label={o.label} />
|
||||
))}
|
||||
</div>
|
||||
{errors.mitarbeiter && <span style={{ fontFamily: FF, fontSize: 11, color: c.fieldError }}>{errors.mitarbeiter}</span>}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<label style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: c.fieldLabel }}>Hat Ihr Unternehmen seinen Sitz in Bayern? *</label>
|
||||
<YesNo c={c} value={data.standortBayern} onChange={v => set('standortBayern', v)} />
|
||||
{errors.standortBayern && <span style={{ fontFamily: FF, fontSize: 11, color: c.fieldError }}>{errors.standortBayern}</span>}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<label style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: c.fieldLabel }}>Ist Ihr Unternehmen inhabergeführt oder familiengeführt? *</label>
|
||||
<YesNo c={c} value={data.familiengeführt} onChange={v => set('familiengeführt', v)} />
|
||||
{errors.familiengeführt && <span style={{ fontFamily: FF, fontSize: 11, color: c.fieldError }}>{errors.familiengeführt}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Eigenbewerbung Step 2: Kontakt / Ineligible ── */}
|
||||
{step === 2 && data.type === 'selbst' && (() => {
|
||||
if (!isEligible(data)) return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, alignItems: 'flex-start' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<AlertCircle size={18} color={c.alertIcon} />
|
||||
<h3 style={{ fontFamily: FF, fontSize: 15, fontWeight: 800, color: c.alertTitle, textTransform: 'uppercase' }}>Leider nicht förderfähig</h3>
|
||||
</div>
|
||||
<p style={{ fontFamily: FF, fontSize: 13, color: c.bodyText, lineHeight: 1.7 }}>
|
||||
Der Bayerische Mittelstandspreis richtet sich an inhabergeführte Unternehmen mit 10–500 Mitarbeitern und Sitz in Bayern. Ihr Unternehmen erfüllt diese Voraussetzungen aktuell nicht.
|
||||
</p>
|
||||
<p style={{ fontFamily: FF, fontSize: 13, color: c.mutedText, lineHeight: 1.7 }}>
|
||||
Fragen? Schreiben Sie uns: <span style={{ color: c.alertEmail }}>info@bmp-bayern.de</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Kontakt</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Kontaktdaten</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Firmenname" error={errors.firmenname}>
|
||||
<StyledInput c={c} value={data.firmenname} onChange={e => set('firmenname', e.target.value)} placeholder="Muster GmbH" />
|
||||
</Field>
|
||||
<Field c={c} label="Ihr Name" error={errors.kontaktName}>
|
||||
<StyledInput c={c} value={data.kontaktName} onChange={e => set('kontaktName', e.target.value)} placeholder="Vor- und Nachname" />
|
||||
</Field>
|
||||
<Field c={c} label="E-Mail" error={errors.email}>
|
||||
<StyledInput c={c} type="email" value={data.email} onChange={e => set('email', e.target.value)} placeholder="name@unternehmen.de" />
|
||||
</Field>
|
||||
<Field c={c} label="Telefon" optional>
|
||||
<StyledInput c={c} type="tel" value={data.telefon} onChange={e => set('telefon', e.target.value)} placeholder="+49 89 ..." />
|
||||
</Field>
|
||||
</div>
|
||||
<p style={{ fontFamily: FF, fontSize: 12, color: c.mutedText, lineHeight: 1.6 }}>
|
||||
Wir senden Ihnen den vollständigen Fragebogen automatisch per E-Mail zu.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* ── Eigenbewerbung Step 3: Zusammenfassung ── */}
|
||||
{step === 3 && data.type === 'selbst' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Zusammenfassung</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Bewerbung</h3>
|
||||
</div>
|
||||
<div style={{ borderTop: `1px solid ${c.border}` }}>
|
||||
{[
|
||||
{ label: 'Unternehmen', value: data.firmenname },
|
||||
{ label: 'Mitarbeiter', value: MITARBEITER_OPTIONS.find(o => o.value === data.mitarbeiter)?.label || '' },
|
||||
{ label: 'Standort', value: `Bayern: ${data.standortBayern === 'ja' ? 'Ja' : 'Nein'}` },
|
||||
{ label: 'Kontakt', value: `${data.kontaktName} · ${data.email}` },
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'grid', gridTemplateColumns: '90px 1fr', gap: 12, padding: '10px 0', borderBottom: `1px solid ${c.border}`, alignItems: 'baseline' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: c.rowLabel, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue, overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Vorschlag Step 1: Nominiertes Unternehmen ── */}
|
||||
{step === 1 && data.type === 'vorschlag' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Nominierung</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Nominiertes Unternehmen</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Firmenname" error={errors.nomFirma}>
|
||||
<StyledInput c={c} value={data.nomFirma} onChange={e => set('nomFirma', e.target.value)} placeholder="Muster GmbH" />
|
||||
</Field>
|
||||
<Field c={c} label="Branche" error={errors.nomBranche}>
|
||||
<StyledInput c={c} value={data.nomBranche} onChange={e => set('nomBranche', e.target.value)} placeholder="z. B. Maschinenbau" />
|
||||
</Field>
|
||||
<Field c={c} label="Standort Bayern" optional>
|
||||
<StyledInput c={c} value={data.nomStandort} onChange={e => set('nomStandort', e.target.value)} placeholder="z. B. München" />
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Vorschlag Step 2: Nominierender ── */}
|
||||
{step === 2 && data.type === 'vorschlag' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Nominierender</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Angaben</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Ihr Name" error={errors.nomName}>
|
||||
<StyledInput c={c} value={data.nomName} onChange={e => set('nomName', e.target.value)} placeholder="Vor- und Nachname" />
|
||||
</Field>
|
||||
<Field c={c} label="Ihre E-Mail" error={errors.nomEmail}>
|
||||
<StyledInput c={c} type="email" value={data.nomEmail} onChange={e => set('nomEmail', e.target.value)} placeholder="ihre@email.de" />
|
||||
</Field>
|
||||
<Field c={c} label="Ihre Beziehung zum Unternehmen" optional>
|
||||
<StyledInput c={c} value={data.nomBeziehung} onChange={e => set('nomBeziehung', e.target.value)} placeholder="z. B. Kunde, Partner, Bekannter" />
|
||||
</Field>
|
||||
</div>
|
||||
<p style={{ fontFamily: FF, fontSize: 12, color: c.mutedText, lineHeight: 1.6 }}>
|
||||
Wir nehmen Kontakt mit dem nominierten Unternehmen auf und informieren es über Ihre Nominierung.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Vorschlag Step 3: Zusammenfassung ── */}
|
||||
{step === 3 && data.type === 'vorschlag' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Zusammenfassung</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Nominierung</h3>
|
||||
</div>
|
||||
<div style={{ borderTop: `1px solid ${c.border}` }}>
|
||||
{[
|
||||
{ label: 'Unternehmen', value: data.nomFirma },
|
||||
{ label: 'Branche', value: data.nomBranche },
|
||||
{ label: 'Standort', value: data.nomStandort || '–' },
|
||||
{ label: 'Nominiert von',value: `${data.nomName} · ${data.nomEmail}` },
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'grid', gridTemplateColumns: '100px 1fr', gap: 12, padding: '10px 0', borderBottom: `1px solid ${c.border}`, alignItems: 'baseline' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: c.rowLabel, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue, overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
{step > 0 && (
|
||||
<div style={{ padding: isMobile ? '12px 20px' : '12px 28px', borderTop: `1px solid ${c.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.backText, background: 'none',
|
||||
border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 5,
|
||||
padding: isMobile ? '12px 4px' : 0, minHeight: isMobile ? 44 : undefined,
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
>
|
||||
<ArrowLeft size={12} /> Zurück
|
||||
</button>
|
||||
|
||||
{step < STEPS.length - 1 ? (
|
||||
step === 2 && data.type === 'selbst' && !isEligible(data) ? <div /> :
|
||||
<button type="button" onClick={next} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.btnText, background: c.btnBg,
|
||||
border: 'none', cursor: 'pointer', padding: isMobile ? '13px 24px' : '10px 22px',
|
||||
display: 'flex', alignItems: 'center', gap: 6, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
>
|
||||
Weiter <ArrowRight size={12} />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={submit} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.btnText, background: c.btnBg,
|
||||
border: 'none', cursor: 'pointer', padding: isMobile ? '13px 24px' : '10px 22px',
|
||||
display: 'flex', alignItems: 'center', gap: 6, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
>
|
||||
Absenden <Send size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
355
src/spa/components/forms/KontaktForm.tsx
Normal file
355
src/spa/components/forms/KontaktForm.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ArrowRight, ArrowLeft, Check, MessageSquare, User, Send } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
const DARK = {
|
||||
border: 'rgba(255,255,255,0.12)',
|
||||
borderActive: 'rgba(239,191,4,0.8)',
|
||||
progressBg: 'rgba(255,255,255,0.1)',
|
||||
progressFill: '#EFBF04',
|
||||
stepActive: '#fff',
|
||||
stepDone: '#EFBF04',
|
||||
stepInactive: 'rgba(255,255,255,0.25)',
|
||||
stepActiveBg: 'rgba(239,191,4,0.07)',
|
||||
stepUnderline: '#EFBF04',
|
||||
subtext: 'rgba(255,255,255,0.5)',
|
||||
inputBg: 'rgba(255,255,255,0.05)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.09)',
|
||||
inputBorder: 'rgba(255,255,255,0.18)',
|
||||
inputBorderFoc: 'rgba(239,191,4,0.8)',
|
||||
inputText: '#fff',
|
||||
labelText: 'rgba(255,255,255,0.45)',
|
||||
errorText: '#f87171',
|
||||
cardBg: 'rgba(255,255,255,0.06)',
|
||||
cardBgSel: 'rgba(239,191,4,0.1)',
|
||||
cardBorder: 'rgba(255,255,255,0.22)',
|
||||
cardBorderSel: '#EFBF04',
|
||||
cardTitle: '#fff',
|
||||
cardTitleSel: '#EFBF04',
|
||||
cardDesc: 'rgba(255,255,255,0.5)',
|
||||
radioBorder: 'rgba(255,255,255,0.25)',
|
||||
radioBorderSel: '#EFBF04',
|
||||
radioFill: '#EFBF04',
|
||||
radioInner: '#111D55',
|
||||
btnBg: '#EFBF04',
|
||||
btnBgHover: '#FFD130',
|
||||
btnText: '#101828',
|
||||
backText: 'rgba(255,255,255,0.35)',
|
||||
backTextHover: '#fff',
|
||||
successIcon: '#EFBF04',
|
||||
successIconIn: '#111D55',
|
||||
successHeading: '#fff',
|
||||
successBody: 'rgba(255,255,255,0.6)',
|
||||
};
|
||||
|
||||
const GOLD = {
|
||||
border: 'rgba(17,29,85,0.15)',
|
||||
borderActive: '#111D55',
|
||||
progressBg: 'rgba(17,29,85,0.12)',
|
||||
progressFill: '#111D55',
|
||||
stepActive: '#111D55',
|
||||
stepDone: 'rgba(17,29,85,0.55)',
|
||||
stepInactive: 'rgba(17,29,85,0.28)',
|
||||
stepActiveBg: 'rgba(17,29,85,0.07)',
|
||||
stepUnderline: '#111D55',
|
||||
subtext: 'rgba(17,29,85,0.6)',
|
||||
inputBg: 'rgba(255,255,255,0.5)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.9)',
|
||||
inputBorder: 'rgba(17,29,85,0.18)',
|
||||
inputBorderFoc: '#111D55',
|
||||
inputText: '#111D55',
|
||||
labelText: 'rgba(17,29,85,0.5)',
|
||||
errorText: '#b91c1c',
|
||||
cardBg: 'rgba(255,255,255,0.38)',
|
||||
cardBgSel: 'rgba(17,29,85,0.1)',
|
||||
cardBorder: 'rgba(17,29,85,0.2)',
|
||||
cardBorderSel: '#111D55',
|
||||
cardTitle: '#111D55',
|
||||
cardTitleSel: '#111D55',
|
||||
cardDesc: 'rgba(17,29,85,0.55)',
|
||||
radioBorder: 'rgba(17,29,85,0.25)',
|
||||
radioBorderSel: '#111D55',
|
||||
radioFill: '#111D55',
|
||||
radioInner: '#fff',
|
||||
btnBg: '#111D55',
|
||||
btnBgHover: '#051A87',
|
||||
btnText: '#fff',
|
||||
backText: 'rgba(17,29,85,0.4)',
|
||||
backTextHover: '#111D55',
|
||||
successIcon: '#111D55',
|
||||
successIconIn: '#EFBF04',
|
||||
successHeading: '#111D55',
|
||||
successBody: 'rgba(17,29,85,0.65)',
|
||||
};
|
||||
|
||||
type C = typeof DARK;
|
||||
|
||||
type FormData = {
|
||||
betreff: string;
|
||||
name: string;
|
||||
email: string;
|
||||
nachricht: string;
|
||||
};
|
||||
|
||||
const STEPS = [
|
||||
{ label: 'Betreff', icon: MessageSquare },
|
||||
{ label: 'Kontakt', icon: User },
|
||||
{ label: 'Nachricht', icon: Send },
|
||||
];
|
||||
|
||||
const BETREFFS = [
|
||||
{ value: 'allgemein', label: 'Allgemeine Anfrage', desc: 'Ich habe eine allgemeine Frage.' },
|
||||
{ value: 'bewerbung', label: 'Frage zur Bewerbung', desc: 'Ich möchte mich bewerben oder habe Rückfragen.' },
|
||||
{ value: 'presse', label: 'Presseanfrage', desc: 'Ich bin Journalist:in und recherchiere zum BMP.' },
|
||||
{ value: 'sponsoring', label: 'Sponsoring', desc: 'Ich interessiere mich für eine Partnerschaft.' },
|
||||
];
|
||||
|
||||
const variants = {
|
||||
enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }),
|
||||
center: { x: 0, opacity: 1 },
|
||||
exit: (dir: number) => ({ x: dir > 0 ? -40 : 40, opacity: 0 }),
|
||||
};
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: isMobile ? 46 : 42, padding: '0 12px',
|
||||
fontSize: isMobile ? 16 : 14, color: c.inputText, fontFamily: FF,
|
||||
border: `1.5px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StyledTextarea({ c, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', padding: '10px 12px',
|
||||
fontSize: isMobile ? 16 : 14, color: c.inputText, fontFamily: FF,
|
||||
border: `1.5px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
resize: 'vertical', minHeight: 100,
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function BetreffCard({ c, item, selected, onClick }: { c: C; item: typeof BETREFFS[0]; selected: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
type="button" onClick={onClick}
|
||||
style={{
|
||||
width: '100%', padding: '11px 14px', textAlign: 'left', cursor: 'pointer',
|
||||
border: `1.5px solid ${selected ? c.cardBorderSel : c.cardBorder}`,
|
||||
background: selected ? c.cardBgSel : c.cardBg,
|
||||
fontFamily: FF, transition: 'all 0.15s',
|
||||
display: 'flex', alignItems: 'center', gap: 12,
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
width: 16, height: 16, borderRadius: '50%', flexShrink: 0,
|
||||
border: `2px solid ${selected ? c.radioBorderSel : c.radioBorder}`,
|
||||
background: selected ? c.radioFill : 'transparent',
|
||||
transition: 'all 0.15s', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
{selected && <div style={{ width: 5, height: 5, borderRadius: '50%', background: c.radioInner }} />}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, color: selected ? c.cardTitleSel : c.cardTitle, marginBottom: 1 }}>{item.label}</div>
|
||||
<div style={{ fontSize: 11, color: c.cardDesc }}>{item.desc}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function KontaktForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) {
|
||||
const c = theme === 'gold' ? GOLD : DARK;
|
||||
const isMobile = useIsMobile();
|
||||
const [step, setStep] = useState(0);
|
||||
const [dir, setDir] = useState(1);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [data, setData] = useState<FormData>({ betreff: 'allgemein', name: '', email: '', nachricht: '' });
|
||||
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
|
||||
|
||||
const set = (k: keyof FormData, v: string) => {
|
||||
setData(d => ({ ...d, [k]: v }));
|
||||
setErrors(e => { const n = { ...e }; delete n[k]; return n; });
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const e: typeof errors = {};
|
||||
if (step === 1) {
|
||||
if (!data.name) e.name = 'Bitte Namen angeben';
|
||||
if (!data.email) e.email = 'Bitte E-Mail angeben';
|
||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = 'Ungültige E-Mail';
|
||||
}
|
||||
if (step === 2 && !data.nachricht) e.nachricht = 'Bitte Nachricht verfassen.';
|
||||
setErrors(e);
|
||||
return Object.keys(e).length === 0;
|
||||
};
|
||||
|
||||
const next = () => { if (validate()) { setDir(1); setStep(s => s + 1); } };
|
||||
const prev = () => { setDir(-1); setStep(s => s - 1); };
|
||||
const submit = () => { if (validate()) setSubmitted(true); };
|
||||
|
||||
if (submitted) {
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }}
|
||||
style={{ padding: '48px 24px', textAlign: 'center' }}
|
||||
>
|
||||
<div style={{ width: 56, height: 56, background: c.successIcon, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}>
|
||||
<Check size={24} color={c.successIconIn} strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 style={{ fontSize: 20, fontWeight: 800, color: c.successHeading, marginBottom: 10, letterSpacing: '-0.02em', fontFamily: FF }}>
|
||||
Nachricht gesendet
|
||||
</h3>
|
||||
<p style={{ fontSize: 15, color: c.successBody, lineHeight: 1.7, maxWidth: 280, margin: '0 auto', fontFamily: FF, overflowWrap: 'anywhere' }}>
|
||||
Danke, <strong style={{ color: c.successHeading }}>{data.name}</strong>. Wir melden uns zeitnah bei{' '}
|
||||
<strong style={{ color: c.successIcon }}>{data.email}</strong>.
|
||||
</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
|
||||
{/* Progress */}
|
||||
<div style={{ height: 3, background: c.progressBg, flexShrink: 0 }}>
|
||||
<div style={{ height: '100%', background: c.progressFill, width: step === 0 ? '0%' : `${(step / (STEPS.length - 1)) * 100}%`, transition: 'width 0.4s ease' }} />
|
||||
</div>
|
||||
|
||||
{/* Step tabs */}
|
||||
<div style={{ display: 'flex', borderBottom: `1px solid ${c.border}`, flexShrink: 0 }}>
|
||||
{STEPS.map((s, i) => {
|
||||
const Icon = s.icon;
|
||||
const done = i < step, active = i === step;
|
||||
return (
|
||||
<div key={i} style={{
|
||||
flex: 1, padding: '8px 4px', textAlign: 'center', fontFamily: FF, fontSize: 9,
|
||||
fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||
color: active ? c.stepActive : done ? c.stepDone : c.stepInactive,
|
||||
borderBottom: active ? `2px solid ${c.stepUnderline}` : '2px solid transparent',
|
||||
background: active ? c.stepActiveBg : 'transparent',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
|
||||
marginBottom: -1, transition: 'color 0.2s',
|
||||
}}>
|
||||
<Icon size={11} strokeWidth={active ? 2 : 1.5} />
|
||||
<span>{done ? '✓' : s.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: '20px 0', flex: 1, minHeight: 0, overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<AnimatePresence mode="wait" custom={dir}>
|
||||
<motion.div key={step} custom={dir} variants={variants}
|
||||
initial="enter" animate="center" exit="exit"
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}>
|
||||
|
||||
{step === 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<p style={{ fontSize: 14, color: c.subtext, marginBottom: 12, fontFamily: FF }}>Womit können wir Ihnen helfen?</p>
|
||||
{BETREFFS.map(item => (
|
||||
<BetreffCard key={item.value} c={c} item={item} selected={data.betreff === item.value}
|
||||
onClick={() => { set('betreff', item.value); setDir(1); setStep(1); }} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 1 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<p style={{ fontSize: 14, color: c.subtext, marginBottom: 4, fontFamily: FF }}>Wie dürfen wir Sie kontaktieren?</p>
|
||||
{([
|
||||
{ label: 'Name', key: 'name' as const, type: 'text', placeholder: 'Vor- und Nachname', error: errors.name },
|
||||
{ label: 'E-Mail', key: 'email' as const, type: 'email', placeholder: 'ihre@email.de', error: errors.email },
|
||||
] as const).map(f => (
|
||||
<div key={f.key} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: c.labelText, fontFamily: FF }}>{f.label}</label>
|
||||
<StyledInput c={c} type={f.type} value={data[f.key]} onChange={e => set(f.key, e.target.value)} placeholder={f.placeholder} />
|
||||
{f.error && <span style={{ fontSize: 11, color: c.errorText, fontFamily: FF }}>{f.error}</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<p style={{ fontSize: 14, color: c.subtext, marginBottom: 4, fontFamily: FF }}>Was möchten Sie uns mitteilen?</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: c.labelText, fontFamily: FF }}>Nachricht</label>
|
||||
<StyledTextarea c={c} value={data.nachricht} onChange={e => set('nachricht', e.target.value)} rows={5} placeholder="Schreiben Sie Ihre Nachricht…" />
|
||||
{errors.nachricht && <span style={{ fontSize: 11, color: c.errorText, fontFamily: FF }}>{errors.nachricht}</span>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
{step > 0 && (
|
||||
<div style={{ paddingTop: 16, borderTop: `1px solid ${c.border}`, display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'stretch' : 'center', gap: 12, flexShrink: 0 }}>
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontSize: 12, fontWeight: 600, color: c.backText, background: 'none', border: 'none', cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', gap: 5,
|
||||
padding: isMobile ? '12px 0' : 0, minHeight: isMobile ? 44 : undefined, fontFamily: FF, transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
>
|
||||
<ArrowLeft size={13} /> Zurück
|
||||
</button>
|
||||
|
||||
{step < STEPS.length - 1 ? (
|
||||
<button type="button" onClick={next} style={{
|
||||
fontSize: 14, fontWeight: 700, color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: '12px 28px', fontFamily: FF, display: 'flex', alignItems: 'center', gap: 8,
|
||||
transition: 'background 0.15s',
|
||||
...(isMobile ? { width: '100%', justifyContent: 'center' } : {}),
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = c.btnBgHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = c.btnBg)}
|
||||
>
|
||||
Weiter <ArrowRight size={14} />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={submit} style={{
|
||||
fontSize: 14, fontWeight: 700, color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: '12px 28px', fontFamily: FF, display: 'flex', alignItems: 'center', gap: 8,
|
||||
transition: 'background 0.15s',
|
||||
...(isMobile ? { width: '100%', justifyContent: 'center' } : {}),
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = c.btnBgHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = c.btnBg)}
|
||||
>
|
||||
Anfrage senden <Send size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
350
src/spa/components/forms/SponsoringForm.tsx
Normal file
350
src/spa/components/forms/SponsoringForm.tsx
Normal file
@@ -0,0 +1,350 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
const DARK = {
|
||||
border: 'rgba(255,255,255,0.12)',
|
||||
borderActive: 'rgba(239,191,4,0.8)',
|
||||
progressBg: 'rgba(255,255,255,0.06)',
|
||||
progressFill: '#EFBF04',
|
||||
stepActive: '#fff',
|
||||
stepDone: '#EFBF04',
|
||||
stepInactive: 'rgba(255,255,255,0.2)',
|
||||
stepActiveBg: 'rgba(239,191,4,0.07)',
|
||||
stepUnderline: '#EFBF04',
|
||||
accentLabel: 'rgba(239,191,4,0.8)',
|
||||
heading: '#fff',
|
||||
inputBg: 'rgba(255,255,255,0.05)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.08)',
|
||||
inputBorder: 'rgba(255,255,255,0.18)',
|
||||
inputBorderFoc: 'rgba(239,191,4,0.8)',
|
||||
inputText: '#fff',
|
||||
labelText: 'rgba(255,255,255,0.5)',
|
||||
errorText: '#f87171',
|
||||
cardBg: 'rgba(255,255,255,0.05)',
|
||||
cardBgSel: 'rgba(239,191,4,0.08)',
|
||||
cardBorder: 'rgba(255,255,255,0.12)',
|
||||
cardBorderSel: '#EFBF04',
|
||||
cardTitle: '#fff',
|
||||
cardTitleSel: '#EFBF04',
|
||||
cardDesc: 'rgba(255,255,255,0.65)',
|
||||
radioFill: '#EFBF04',
|
||||
btnBg: '#EFBF04',
|
||||
btnBgHover: '#FFD130',
|
||||
btnText: '#101828',
|
||||
backText: 'rgba(255,255,255,0.35)',
|
||||
backTextHover: '#fff',
|
||||
successIcon: '#EFBF04',
|
||||
successIconIn: '#111D55',
|
||||
successHeading: '#fff',
|
||||
successBody: 'rgba(255,255,255,0.45)',
|
||||
footerText: 'rgba(255,255,255,0.3)',
|
||||
};
|
||||
|
||||
const GOLD = {
|
||||
border: 'rgba(17,29,85,0.15)',
|
||||
borderActive: '#111D55',
|
||||
progressBg: 'rgba(17,29,85,0.1)',
|
||||
progressFill: '#111D55',
|
||||
stepActive: '#111D55',
|
||||
stepDone: 'rgba(17,29,85,0.55)',
|
||||
stepInactive: 'rgba(17,29,85,0.28)',
|
||||
stepActiveBg: 'rgba(17,29,85,0.07)',
|
||||
stepUnderline: '#111D55',
|
||||
accentLabel: 'rgba(17,29,85,0.5)',
|
||||
heading: '#111D55',
|
||||
inputBg: 'rgba(255,255,255,0.5)',
|
||||
inputBgFocus: 'rgba(255,255,255,0.9)',
|
||||
inputBorder: 'rgba(17,29,85,0.18)',
|
||||
inputBorderFoc: '#111D55',
|
||||
inputText: '#111D55',
|
||||
labelText: 'rgba(17,29,85,0.5)',
|
||||
errorText: '#b91c1c',
|
||||
cardBg: 'rgba(255,255,255,0.38)',
|
||||
cardBgSel: 'rgba(17,29,85,0.1)',
|
||||
cardBorder: 'rgba(17,29,85,0.2)',
|
||||
cardBorderSel: '#111D55',
|
||||
cardTitle: '#111D55',
|
||||
cardTitleSel: '#111D55',
|
||||
cardDesc: 'rgba(17,29,85,0.6)',
|
||||
radioFill: '#111D55',
|
||||
btnBg: '#111D55',
|
||||
btnBgHover: '#051A87',
|
||||
btnText: '#fff',
|
||||
backText: 'rgba(17,29,85,0.4)',
|
||||
backTextHover: '#111D55',
|
||||
successIcon: '#111D55',
|
||||
successIconIn: '#EFBF04',
|
||||
successHeading: '#111D55',
|
||||
successBody: 'rgba(17,29,85,0.6)',
|
||||
footerText: 'rgba(17,29,85,0.4)',
|
||||
};
|
||||
|
||||
type C = typeof DARK;
|
||||
|
||||
type FormData = {
|
||||
paket: string;
|
||||
unternehmen: string;
|
||||
branche: string;
|
||||
kontakt: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
const STEPS = [
|
||||
{ label: 'Paket', icon: Layers },
|
||||
{ label: 'Unternehmen', icon: Building2 },
|
||||
{ label: 'Kontakt', icon: Mail },
|
||||
];
|
||||
|
||||
const PAKETE = [
|
||||
{ value: 'main', title: 'Haupt-Sponsoring', desc: 'Maximale Sichtbarkeit auf allen Kanälen – Bühne, Digital, Print.' },
|
||||
{ value: 'category', title: 'Kategorie-Partner', desc: 'Namensgebung einer Auszeichnungskategorie – fokussiertes Branding.' },
|
||||
{ value: 'event', title: 'Event-Partner', desc: 'Präsenz beim exklusiven Gala-Abend in München.' },
|
||||
];
|
||||
|
||||
const variants = {
|
||||
enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }),
|
||||
center: { x: 0, opacity: 1 },
|
||||
exit: (dir: number) => ({ x: dir > 0 ? -40 : 40, opacity: 0 }),
|
||||
};
|
||||
|
||||
function Field({ c, label, error, children }: { c: C; label: string; error?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: c.labelText }}>{label}</label>
|
||||
{children}
|
||||
{error && <span style={{ fontFamily: FF, fontSize: 11, color: c.errorText }}>{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: isMobile ? 46 : 44, padding: '0 14px',
|
||||
fontFamily: FF, fontSize: isMobile ? 16 : 14, color: c.inputText,
|
||||
border: `1px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: typeof PAKETE[0]; selected: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button type="button" onClick={onClick} style={{
|
||||
width: '100%', padding: '13px 18px', textAlign: 'left', cursor: 'pointer',
|
||||
border: `2px solid ${selected ? c.cardBorderSel : c.cardBorder}`,
|
||||
background: selected ? c.cardBgSel : c.cardBg,
|
||||
fontFamily: FF, transition: 'all 0.15s',
|
||||
display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12,
|
||||
}}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: selected ? c.cardTitleSel : c.cardTitle, marginBottom: 5 }}>{pkg.title}</div>
|
||||
<div style={{ fontSize: 13, color: c.cardDesc, lineHeight: 1.45 }}>{pkg.desc}</div>
|
||||
</div>
|
||||
<div style={{
|
||||
width: 18, height: 18, borderRadius: '50%', flexShrink: 0, marginTop: 2,
|
||||
border: `2px solid ${selected ? c.cardBorderSel : c.cardBorder}`,
|
||||
background: selected ? c.radioFill : 'transparent',
|
||||
transition: 'all 0.15s',
|
||||
}} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) {
|
||||
const c = theme === 'gold' ? GOLD : DARK;
|
||||
const isMobile = useIsMobile();
|
||||
const [step, setStep] = useState(0);
|
||||
const [dir, setDir] = useState(1);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [data, setData] = useState<FormData>({ paket: '', unternehmen: '', branche: '', kontakt: '', email: '' });
|
||||
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
|
||||
|
||||
const set = (k: keyof FormData, v: string) => {
|
||||
setData(d => ({ ...d, [k]: v }));
|
||||
setErrors(e => { const n = { ...e }; delete n[k]; return n; });
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const e: typeof errors = {};
|
||||
if (step === 0 && !data.paket) e.paket = 'Bitte wählen Sie ein Paket.';
|
||||
if (step === 1 && !data.unternehmen) e.unternehmen = 'Pflichtfeld';
|
||||
if (step === 2) {
|
||||
if (!data.kontakt) e.kontakt = 'Pflichtfeld';
|
||||
if (!data.email) e.email = 'Pflichtfeld';
|
||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = 'Ungültige E-Mail';
|
||||
}
|
||||
setErrors(e);
|
||||
return Object.keys(e).length === 0;
|
||||
};
|
||||
|
||||
const next = () => { if (validate()) { setDir(1); setStep(s => s + 1); } };
|
||||
const prev = () => { setDir(-1); setStep(s => s - 1); };
|
||||
const submit = () => { if (validate()) setSubmitted(true); };
|
||||
|
||||
const progress = (step / (STEPS.length - 1)) * 100;
|
||||
|
||||
if (submitted) {
|
||||
return (
|
||||
<div style={{ padding: '64px 28px', textAlign: 'center', fontFamily: FF }}>
|
||||
<div style={{ width: 52, height: 52, background: c.successIcon, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}>
|
||||
<Check size={22} color={c.successIconIn} strokeWidth={3} />
|
||||
</div>
|
||||
<h3 style={{ fontSize: 18, fontWeight: 900, color: c.successHeading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 10 }}>
|
||||
Anfrage eingegangen
|
||||
</h3>
|
||||
<p style={{ fontSize: 12, color: c.successBody, maxWidth: 320, margin: '0 auto', lineHeight: 1.6 }}>
|
||||
Vielen Dank, <strong style={{ color: c.successHeading }}>{data.kontakt}</strong>. Wir senden Ihnen unser Sponsoring-Exposé innerhalb von 48 Stunden zu.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
|
||||
{/* Progress bar */}
|
||||
<div style={{ height: 2, background: c.progressBg, flexShrink: 0 }}>
|
||||
<div style={{ height: '100%', background: c.progressFill, width: `${progress}%`, transition: 'width 0.4s ease' }} />
|
||||
</div>
|
||||
|
||||
{/* Step tabs */}
|
||||
<div style={{ display: 'flex', borderBottom: `1px solid ${c.border}`, flexShrink: 0 }}>
|
||||
{STEPS.map((s, i) => {
|
||||
const Icon = s.icon;
|
||||
const done = i < step, active = i === step;
|
||||
return (
|
||||
<div key={i} style={{
|
||||
flex: 1, padding: '10px 4px', textAlign: 'center', fontFamily: FF, fontSize: 9,
|
||||
fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||
color: active ? c.stepActive : done ? c.stepDone : c.stepInactive,
|
||||
borderBottom: active ? `2px solid ${c.stepUnderline}` : '2px solid transparent',
|
||||
background: active ? c.stepActiveBg : 'transparent',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5,
|
||||
marginBottom: -1, transition: 'color 0.2s',
|
||||
}}>
|
||||
<Icon size={11} strokeWidth={active ? 2 : 1.5} />
|
||||
<span>{done ? '✓' : s.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: '20px 0', flex: 1, minHeight: 0, overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<AnimatePresence mode="wait" custom={dir}>
|
||||
<motion.div key={step} custom={dir} variants={variants}
|
||||
initial="enter" animate="center" exit="exit"
|
||||
transition={{ duration: 0.22, ease: 'easeInOut' }}>
|
||||
|
||||
{step === 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Schritt 1 – Paket wählen</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 12 }}>Interesse an</h3>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{PAKETE.map(pkg => (
|
||||
<PaketCard key={pkg.value} c={c} pkg={pkg} selected={data.paket === pkg.value} onClick={() => set('paket', pkg.value)} />
|
||||
))}
|
||||
</div>
|
||||
{errors.paket && <p style={{ fontFamily: FF, fontSize: 11, color: c.errorText, marginTop: 4 }}>{errors.paket}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 1 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 2 – Unternehmen</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihr Unternehmen</h3>
|
||||
</div>
|
||||
<Field c={c} label="Unternehmen *" error={errors.unternehmen}>
|
||||
<StyledInput c={c} value={data.unternehmen} onChange={e => set('unternehmen', e.target.value)} placeholder="Firmenname" />
|
||||
</Field>
|
||||
<Field c={c} label="Branche">
|
||||
<StyledInput c={c} value={data.branche} onChange={e => set('branche', e.target.value)} placeholder="z. B. Finanzdienstleistungen" />
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 3 – Kontakt</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ansprechpartner</h3>
|
||||
</div>
|
||||
<Field c={c} label="Ansprechpartner *" error={errors.kontakt}>
|
||||
<StyledInput c={c} value={data.kontakt} onChange={e => set('kontakt', e.target.value)} placeholder="Vor- und Nachname" />
|
||||
</Field>
|
||||
<Field c={c} label="E-Mail *" error={errors.email}>
|
||||
<StyledInput c={c} type="email" value={data.email} onChange={e => set('email', e.target.value)} placeholder="name@unternehmen.de" />
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Nav */}
|
||||
<div style={{ paddingTop: 14, borderTop: `1px solid ${c.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
|
||||
{step > 0 ? (
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.backText, background: 'none', border: 'none', cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
padding: isMobile ? '12px 4px' : 0, minHeight: isMobile ? 44 : undefined,
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
>
|
||||
<ArrowLeft size={13} /> Zurück
|
||||
</button>
|
||||
) : <div />}
|
||||
|
||||
{step < STEPS.length - 1 ? (
|
||||
<button type="button" onClick={next} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: isMobile ? '14px 24px' : '12px 24px', minHeight: isMobile ? 44 : undefined,
|
||||
display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
>
|
||||
Weiter <ArrowRight size={13} />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={submit} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: isMobile ? '14px 24px' : '12px 24px', minHeight: isMobile ? 44 : undefined,
|
||||
display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
>
|
||||
Exposé anfordern <ArrowRight size={13} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p style={{ fontFamily: FF, textAlign: 'center', fontSize: 12, color: c.footerText, paddingTop: 12 }}>
|
||||
Wir melden uns innerhalb von 48 Stunden.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1066
src/spa/components/layout/Layout.tsx
Normal file
1066
src/spa/components/layout/Layout.tsx
Normal file
File diff suppressed because it is too large
Load Diff
12
src/spa/components/layout/ScrollToTop.tsx
Normal file
12
src/spa/components/layout/ScrollToTop.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from '@/spa/router';
|
||||
|
||||
export default function ScrollToTop() {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
}
|
||||
1253
src/spa/components/membership/MembershipWizard.tsx
Normal file
1253
src/spa/components/membership/MembershipWizard.tsx
Normal file
File diff suppressed because it is too large
Load Diff
155
src/spa/components/ui/LoadingScreen.tsx
Normal file
155
src/spa/components/ui/LoadingScreen.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
|
||||
const GOLD = '#EFBF04';
|
||||
const DARK = '#101828';
|
||||
|
||||
const SHARDS = [
|
||||
{ clip: 'polygon(0 0, 50% 0, 50% 50%, 0 50%)', tx: -60, ty: -60 },
|
||||
{ clip: 'polygon(50% 0, 100% 0, 100% 50%, 50% 50%)', tx: 60, ty: -60 },
|
||||
{ clip: 'polygon(0 50%, 50% 50%, 50% 100%, 0 100%)', tx: -60, ty: 60 },
|
||||
{ clip: 'polygon(50% 50%, 100% 50%, 100% 100%, 50% 100%)', tx: 60, ty: 60 },
|
||||
];
|
||||
|
||||
export default function LoadingScreen({ onDone }: { onDone: () => void }) {
|
||||
const [phase, setPhase] = useState<'logo' | 'progress' | 'shatter' | 'gone'>('logo');
|
||||
const called = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const t1 = setTimeout(() => setPhase('progress'), 300);
|
||||
const t2 = setTimeout(() => setPhase('shatter'), 1700);
|
||||
const t3 = setTimeout(() => {
|
||||
setPhase('gone');
|
||||
if (!called.current) { called.current = true; onDone(); }
|
||||
}, 2500);
|
||||
return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); };
|
||||
}, [onDone]);
|
||||
|
||||
if (phase === 'gone') return null;
|
||||
|
||||
const isShatter = phase === 'shatter';
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed', inset: 0, zIndex: 9999,
|
||||
pointerEvents: isShatter ? 'none' : 'all',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* ── SOLID PHASE (logo + progress bar) ── */}
|
||||
<AnimatePresence>
|
||||
{!isShatter && (
|
||||
<motion.div
|
||||
key="splash"
|
||||
initial={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.1 }}
|
||||
style={{ position: 'absolute', inset: 0, background: DARK }}
|
||||
>
|
||||
|
||||
{/* Logo + bar centred */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
display: 'flex', flexDirection: 'column',
|
||||
alignItems: 'center', justifyContent: 'center', gap: 36,
|
||||
}}>
|
||||
<motion.img
|
||||
src="/bmp-logo.png"
|
||||
alt="BMP"
|
||||
initial={{ opacity: 0, scale: 0.86, y: 16 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{
|
||||
width: 'clamp(260px, 32vw, 480px)',
|
||||
height: 'auto',
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gold progress track */}
|
||||
<div style={{
|
||||
width: 'clamp(200px, 26vw, 320px)',
|
||||
height: 2,
|
||||
background: 'rgba(239,191,4,0.15)',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<motion.div
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: phase === 'progress' ? 1 : 0 }}
|
||||
transition={{ duration: 1.1, ease: [0.4, 0, 0.2, 1] }}
|
||||
style={{
|
||||
height: '100%',
|
||||
background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.55))`,
|
||||
boxShadow: '0 0 10px rgba(239,191,4,0.7)',
|
||||
transformOrigin: 'left',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* ── LIQUID GLASS SHATTER ── */}
|
||||
<AnimatePresence>
|
||||
{isShatter && SHARDS.map((s, i) => (
|
||||
<motion.div
|
||||
key={`shard-${i}`}
|
||||
initial={{ x: 0, y: 0, opacity: 1, filter: 'blur(0px) brightness(1)' }}
|
||||
animate={{
|
||||
x: `${s.tx}%`,
|
||||
y: `${s.ty}%`,
|
||||
opacity: 0,
|
||||
filter: 'blur(22px) brightness(2.2)',
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.65,
|
||||
ease: [0.4, 0, 1, 1],
|
||||
delay: i * 0.055,
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute', inset: 0,
|
||||
background: DARK,
|
||||
clipPath: s.clip,
|
||||
}}
|
||||
>
|
||||
{/* Ghost logo inside each shard */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<img
|
||||
src="/bmp-logo.png"
|
||||
alt=""
|
||||
aria-hidden
|
||||
style={{ width: 'clamp(260px, 32vw, 480px)', height: 'auto', opacity: 0.9 }}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Central gold burst on shatter */}
|
||||
<AnimatePresence>
|
||||
{isShatter && (
|
||||
<motion.div
|
||||
key="burst"
|
||||
initial={{ opacity: 0.7, scale: 0 }}
|
||||
animate={{ opacity: 0, scale: 4 }}
|
||||
transition={{ duration: 0.55, ease: 'easeOut' }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%', left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 160, height: 160, borderRadius: '50%',
|
||||
background: 'radial-gradient(circle, rgba(239,191,4,0.55) 0%, transparent 68%)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
src/spa/components/ui/badge.tsx
Normal file
46
src/spa/components/ui/badge.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
61
src/spa/components/ui/button.tsx
Normal file
61
src/spa/components/ui/button.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-[#EFBF04] text-[#101828] shadow-xs hover:bg-[#03093A] hover:text-white",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
accent:
|
||||
"bg-accent text-accent-foreground shadow-xs hover:bg-accent/90",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3 max-md:min-h-11",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 max-md:min-h-11",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4 max-md:min-h-11",
|
||||
icon: "size-9 max-md:min-h-11 max-md:min-w-11",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
92
src/spa/components/ui/card.tsx
Normal file
92
src/spa/components/ui/card.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn("leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
209
src/spa/components/ui/carousel.tsx
Normal file
209
src/spa/components/ui/carousel.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react"
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
import { Button } from "@/spa/components/ui/button"
|
||||
|
||||
type CarouselApi = UseEmblaCarouselType[1]
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||
type CarouselOptions = UseCarouselParameters[0]
|
||||
type CarouselPlugin = UseCarouselParameters[1]
|
||||
|
||||
type CarouselProps = {
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugin
|
||||
orientation?: "horizontal" | "vertical"
|
||||
setApi?: (api: CarouselApi) => void
|
||||
}
|
||||
|
||||
type CarouselContextProps = {
|
||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||
scrollPrev: () => void
|
||||
scrollNext: () => void
|
||||
canScrollPrev: boolean
|
||||
canScrollNext: boolean
|
||||
} & CarouselProps
|
||||
|
||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||
|
||||
function useCarousel() {
|
||||
const context = React.useContext(CarouselContext)
|
||||
if (!context) {
|
||||
throw new Error("useCarousel must be used within a <Carousel />")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
const Carousel = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||
>(
|
||||
(
|
||||
{ orientation = "horizontal", opts, setApi, plugins, className, children, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{ ...opts, axis: orientation === "horizontal" ? "x" : "y" },
|
||||
plugins,
|
||||
)
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||
|
||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||
if (!api) return
|
||||
setCanScrollPrev(api.canScrollPrev())
|
||||
setCanScrollNext(api.canScrollNext())
|
||||
}, [])
|
||||
|
||||
const scrollPrev = React.useCallback(() => { api?.scrollPrev() }, [api])
|
||||
const scrollNext = React.useCallback(() => { api?.scrollNext() }, [api])
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "ArrowLeft") { event.preventDefault(); scrollPrev() }
|
||||
else if (event.key === "ArrowRight") { event.preventDefault(); scrollNext() }
|
||||
},
|
||||
[scrollPrev, scrollNext],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api || !setApi) return
|
||||
setApi(api)
|
||||
}, [api, setApi])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) return
|
||||
onSelect(api)
|
||||
api.on("reInit", onSelect)
|
||||
api.on("select", onSelect)
|
||||
return () => { api?.off("select", onSelect) }
|
||||
}, [api, onSelect])
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
carouselRef, api, opts,
|
||||
orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||
scrollPrev, scrollNext, canScrollPrev, canScrollNext,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
className={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</CarouselContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
Carousel.displayName = "Carousel"
|
||||
|
||||
const CarouselContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { carouselRef, orientation } = useCarousel()
|
||||
return (
|
||||
<div ref={carouselRef} className="overflow-hidden">
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex",
|
||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CarouselContent.displayName = "CarouselContent"
|
||||
|
||||
const CarouselItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { orientation } = useCarousel()
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
className={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
CarouselItem.displayName = "CarouselItem"
|
||||
|
||||
const CarouselPrevious = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||
return (
|
||||
<Button
|
||||
ref={ref} variant={variant} size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal" ? "-left-12 top-1/2 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className,
|
||||
)}
|
||||
disabled={!canScrollPrev}
|
||||
onClick={scrollPrev}
|
||||
{...props}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselPrevious.displayName = "CarouselPrevious"
|
||||
|
||||
const CarouselNext = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||
return (
|
||||
<Button
|
||||
ref={ref} variant={variant} size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal" ? "-right-12 top-1/2 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className,
|
||||
)}
|
||||
disabled={!canScrollNext}
|
||||
onClick={scrollNext}
|
||||
{...props}
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselNext.displayName = "CarouselNext"
|
||||
|
||||
export {
|
||||
type CarouselApi,
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselPrevious,
|
||||
CarouselNext,
|
||||
}
|
||||
23
src/spa/components/ui/input.tsx
Normal file
23
src/spa/components/ui/input.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-11 w-full border border-stone-200 bg-white px-4 py-2 text-base md:text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
20
src/spa/components/ui/label.tsx
Normal file
20
src/spa/components/ui/label.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-[10px] font-bold uppercase tracking-[0.2em] text-[#101828]/50 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
51
src/spa/components/ui/munich-skyline-bg.tsx
Normal file
51
src/spa/components/ui/munich-skyline-bg.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* MunichSkylineBg
|
||||
* Drop inside any light section (position: relative required on parent).
|
||||
* Uses backgroundPositionY for parallax – no transform, no GPU compositing layer.
|
||||
* No mixBlendMode to avoid Safari compositing bleed into sibling elements.
|
||||
*/
|
||||
const MunichSkylineBg: React.FC = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const onScroll = () => {
|
||||
const rect = el.parentElement?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
const progress = (window.innerHeight - rect.top) / (window.innerHeight + rect.height);
|
||||
const offset = progress * 30;
|
||||
el.style.backgroundPositionY = `calc(100% - ${offset}px)`;
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '52%',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 0,
|
||||
backgroundImage: 'url(/munich-skyline.jpg)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'bottom center',
|
||||
backgroundSize: 'cover',
|
||||
opacity: 0.08,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MunichSkylineBg;
|
||||
53
src/spa/components/ui/partner-ticker.tsx
Normal file
53
src/spa/components/ui/partner-ticker.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
|
||||
interface PartnerLogo {
|
||||
id: string;
|
||||
label: React.ReactNode;
|
||||
}
|
||||
|
||||
interface PartnerTickerProps {
|
||||
logos: PartnerLogo[];
|
||||
}
|
||||
|
||||
export function PartnerTicker({ logos }: PartnerTickerProps) {
|
||||
// Duplicate logos for seamless infinite loop
|
||||
const track = [...logos, ...logos];
|
||||
|
||||
return (
|
||||
<div style={{ overflow: 'hidden', flex: 1, minWidth: 0, position: 'relative' }}>
|
||||
{/* fade edges */}
|
||||
<div style={{ position: 'absolute', inset: 0, left: 0, width: 32, background: 'linear-gradient(to right, rgba(5,26,135,0.6), transparent)', zIndex: 1, pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, right: 0, left: 'auto', width: 32, background: 'linear-gradient(to left, rgba(5,26,135,0.6), transparent)', zIndex: 1, pointerEvents: 'none' }} />
|
||||
|
||||
<div style={{ display: 'flex', animation: 'marquee 22s linear infinite', width: 'max-content' }}>
|
||||
{track.map((logo, i) => (
|
||||
<div
|
||||
key={`${logo.id}-${i}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0 24px',
|
||||
color: '#fff',
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
textTransform: 'uppercase',
|
||||
whiteSpace: 'nowrap',
|
||||
width: 140,
|
||||
flexShrink: 0,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{logo.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
149
src/spa/components/ui/process-timeline.tsx
Normal file
149
src/spa/components/ui/process-timeline.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useMeasure } from "@uidotdev/usehooks"
|
||||
import { VariantProps, cva } from "class-variance-authority"
|
||||
import {
|
||||
HTMLMotionProps,
|
||||
MotionValue,
|
||||
motion,
|
||||
useScroll,
|
||||
useTransform,
|
||||
} from "motion/react"
|
||||
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
const processCardVariants = cva("flex border backdrop-blur-lg", {
|
||||
variants: {
|
||||
variant: {
|
||||
indigo:
|
||||
"flex border text-slate-50 border-slate-700 backdrop-blur-lg bg-gradient-to-br from-[rgba(15,23,42,0.7)_40%] to-[#3730a3_120%]",
|
||||
light: "shadow",
|
||||
bmp: "flex border text-white border-[rgba(239,191,4,0.25)] bg-[#111D55]",
|
||||
},
|
||||
size: {
|
||||
sm: "min-w-[25%] max-w-[25%]",
|
||||
md: "min-w-[50%] max-w-[50%]",
|
||||
lg: "min-w-[75%] max-w-[75%]",
|
||||
xl: "min-w-full max-w-full",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "bmp",
|
||||
size: "md",
|
||||
},
|
||||
})
|
||||
|
||||
interface ContainerScrollContextValue {
|
||||
scrollYProgress: MotionValue<number>
|
||||
}
|
||||
|
||||
interface ProcessCardProps
|
||||
extends HTMLMotionProps<"div">,
|
||||
VariantProps<typeof processCardVariants> {
|
||||
itemsLength: number
|
||||
index: number
|
||||
}
|
||||
|
||||
const ContainerScrollContext = React.createContext<
|
||||
ContainerScrollContextValue | undefined
|
||||
>(undefined)
|
||||
|
||||
function useContainerScrollContext() {
|
||||
const context = React.useContext(ContainerScrollContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useContainerScrollContext must be used within a ContainerScroll Component"
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export const ContainerScroll = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.HtmlHTMLAttributes<HTMLDivElement>) => {
|
||||
const scrollRef = React.useRef<HTMLDivElement>(null)
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: scrollRef,
|
||||
})
|
||||
return (
|
||||
<ContainerScrollContext.Provider value={{ scrollYProgress }}>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className={cn("relative min-h-[120vh]", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ContainerScrollContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const ContainerSticky = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("sticky left-0 top-0 w-full overflow-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContainerSticky.displayName = "ContainerSticky"
|
||||
|
||||
export const ProcessCardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6", className)} {...props} />
|
||||
))
|
||||
ProcessCardTitle.displayName = "ProcessCardTitle"
|
||||
|
||||
export const ProcessCardBody = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col gap-8 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ProcessCardBody.displayName = "ProcessCardBody"
|
||||
|
||||
export const ProcessCard: React.FC<ProcessCardProps> = ({
|
||||
className,
|
||||
style,
|
||||
variant,
|
||||
size,
|
||||
itemsLength,
|
||||
index,
|
||||
...props
|
||||
}) => {
|
||||
const { scrollYProgress } = useContainerScrollContext()
|
||||
const start = index / itemsLength
|
||||
const end = start + 1 / itemsLength
|
||||
const { innerWidth } = window
|
||||
const [ref, { width }] = useMeasure()
|
||||
|
||||
const x = useTransform(
|
||||
scrollYProgress,
|
||||
[start, end],
|
||||
[innerWidth, -((width ?? 0) * index) + 64 * index]
|
||||
)
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
style={{
|
||||
x: index > 0 ? x : 0,
|
||||
...style,
|
||||
}}
|
||||
className={cn(processCardVariants({ variant, size }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ProcessCard.displayName = "ProcessCard"
|
||||
22
src/spa/components/ui/textarea.tsx
Normal file
22
src/spa/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/spa/lib/utils"
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[100px] w-full border border-stone-200 bg-white px-4 py-3 text-base md:text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all resize-none",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
131
src/spa/data/blog.ts
Normal file
131
src/spa/data/blog.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
export interface BlogSection {
|
||||
heading?: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface BlogArticle {
|
||||
slug: string;
|
||||
title: string;
|
||||
cat: string;
|
||||
date: string;
|
||||
author: string;
|
||||
authorRole: string;
|
||||
readTime: string;
|
||||
img: string;
|
||||
excerpt: string;
|
||||
sections: BlogSection[];
|
||||
tags: string[];
|
||||
relatedSlugs: string[];
|
||||
}
|
||||
|
||||
export const articles: BlogArticle[] = [
|
||||
{
|
||||
slug: 'ki-nutzung-bayerischer-kmu',
|
||||
title: 'Wie Bayerns KMU die KI nutzen',
|
||||
cat: 'Innovation',
|
||||
date: '10. März 2026',
|
||||
author: 'Dr. Markus Berger',
|
||||
authorRole: 'Redakteur Wirtschaft & Innovation',
|
||||
readTime: '6 min',
|
||||
img: '/images/gala-saal-overview.jpg',
|
||||
excerpt:
|
||||
'Ein Deep-Dive in die Bewerbungsunterlagen 2024 zeigt: Der Mittelstand treibt die Digitalisierung aktiv voran.',
|
||||
sections: [
|
||||
{
|
||||
body: 'Die Bewerbungsunterlagen des Bayerischen Mittelstandspreises 2024 zeichnen ein überraschendes Bild: Nicht die großen Konzerne, sondern Familienbetriebe mit 30 bis 250 Mitarbeitern treiben die KI-Transformation in Bayern voran. Über 60 Prozent der eingereichten Projekte wiesen konkrete Anwendungen künstlicher Intelligenz auf – vor zwei Jahren waren es kaum 18 Prozent.',
|
||||
},
|
||||
{
|
||||
heading: 'Vom Pilotprojekt zur Kernstrategie',
|
||||
body: 'Was früher unter dem Label „Digitalisierungsexperiment" firmierte, ist heute Teil der Unternehmensstrategie. Bavarian Sensor Systems aus Landsberg optimiert mit Machine-Learning-Modellen seine Qualitätskontrolle und konnte die Ausschussrate um 34 Prozent senken. Der Holzverarbeiter Käser & Söhne in Rosenheim nutzt KI-gestützte Absatzprognosen und hat seinen Lagerbestand um ein Drittel reduziert. Beide Unternehmen sind Erstbewerber – und beide gehören zu den Nominierten 2026.',
|
||||
},
|
||||
{
|
||||
heading: 'Drei Muster, die sich wiederholen',
|
||||
body: 'Die Analyse der Unterlagen zeigt drei wiederkehrende Anwendungsfelder: Erstens Predictive Maintenance in der Fertigung, wo Sensordaten Maschinenausfälle früh erkennen. Zweitens KI-unterstützte Kundenkommunikation, meist in Form intelligenter Chatbots oder automatisierter E-Mail-Klassifizierung. Drittens datengetriebene Lieferkettenoptimierung – besonders relevant nach den Erfahrungen der Corona-Jahre.',
|
||||
},
|
||||
{
|
||||
heading: 'Hemmnisse bleiben real',
|
||||
body: 'Die positiven Zahlen dürfen nicht darüber hinwegtäuschen, dass rund 40 Prozent der Bewerber KI noch als „zu komplex" oder „zu teuer" einstufen. Fehlende Fachkräfte, unklare Datenschutzanforderungen und der Aufwand der Datenaufbereitung werden als Haupthürden genannt. Die Jury des BMP hat diese Erkenntnis zum Anlass genommen, das Kriterium „Zukunftsfähigkeit & Innovation" für 2026 stärker auf konkrete Umsetzungsschritte auszurichten – nicht nur auf strategische Absichtserklärungen.',
|
||||
},
|
||||
{
|
||||
heading: 'Fazit: Der Mittelstand überrascht',
|
||||
body: 'Bayern beweist einmal mehr, dass wirtschaftliche Stärke und Innovationsbereitschaft kein Widerspruch sind. Der Mittelstand – oft belächelt als träge oder technikavers – erweist sich als einer der aktivsten Treiber der KI-Adoption. Die nächste Preisverleihung am 22. Oktober 2026 dürfte diese Entwicklung eindrucksvoll sichtbar machen.',
|
||||
},
|
||||
],
|
||||
tags: ['KI', 'Innovation', 'Digitalisierung', 'Mittelstand', 'Bayern'],
|
||||
relatedSlugs: ['ehrenamts-initiative-2026', 'bayerische-wirtschaft-waechst'],
|
||||
},
|
||||
{
|
||||
slug: 'ehrenamts-initiative-2026',
|
||||
title: 'Start der Ehrenamts-Initiative',
|
||||
cat: 'Engagement',
|
||||
date: '24. Februar 2026',
|
||||
author: 'Lena Hofmann',
|
||||
authorRole: 'Redakteurin Gesellschaft & Nachhaltigkeit',
|
||||
readTime: '4 min',
|
||||
img: '/images/networking-innenhof.jpg',
|
||||
excerpt:
|
||||
'Gemeinsam mit unseren Partnern fördern wir soziale Projekte mittelständischer Unternehmen.',
|
||||
sections: [
|
||||
{
|
||||
body: 'Mit dem Startschuss für die Ehrenamts-Initiative des Bayerischen Mittelstandspreises setzt der BMP ein deutliches Zeichen: Unternehmerisches Engagement hört nicht an der Werksgrenze auf. In Zusammenarbeit mit dem Europäischen Wirtschaftsforum (EWIF) und der Hochschule für angewandtes Management werden ab sofort gezielt Projekte gefördert, bei denen Mittelständler Verantwortung für ihr gesellschaftliches Umfeld übernehmen.',
|
||||
},
|
||||
{
|
||||
heading: 'Was die Initiative leistet',
|
||||
body: 'Die Initiative bietet teilnehmenden Unternehmen Sichtbarkeit, Netzwerkzugang und konkrete Unterstützung bei der Konzeption und Dokumentation ihrer sozialen Projekte. Ob Jugendförderung, regionale Integrationsarbeit oder ökologische Renaturierungsprojekte – das Programm ist bewusst offen gehalten. Entscheidend ist der nachweisbare lokale Mehrwert.',
|
||||
},
|
||||
{
|
||||
heading: 'Drei Unternehmen zeigen, wie es geht',
|
||||
body: 'Der Augsburger Fensterbauer Denk Fenster & Türen schickt seit zwei Jahren Auszubildende in lokale Schulen, um dort Handwerksberufe erlebbar zu machen. Die Münchner Softwareschmiede Alphacode Technologies stellt monatlich Entwicklerzeit für gemeinnützige Digitalprojekte bereit. Und die Bäckerei Huber aus Landshut betreibt ein Café, das Langzeitarbeitslosen einen strukturierten Wiedereinstieg ermöglicht. Alle drei wurden für die Pilotphase der Initiative ausgewählt.',
|
||||
},
|
||||
{
|
||||
heading: 'Bewertungskriterium ab 2026',
|
||||
body: 'Neu ist: Soziales Engagement fließt ab der Bewerbungsrunde 2026 offiziell in die Säule „Nachhaltigkeit & Verantwortung" ein. Unternehmen, die an der Ehrenamts-Initiative teilnehmen, erhalten bei der Jury-Sichtung einen gesonderten Hinweis auf ihre Projekte. Dies soll keine Bevorteilung sein, wohl aber eine Anerkennung von Leistungen, die in klassischen Bilanzen unsichtbar bleiben.',
|
||||
},
|
||||
{
|
||||
heading: 'Wie kann man teilnehmen?',
|
||||
body: 'Interessierte Unternehmen können sich ab sofort über das Kontaktformular des BMP anmelden. Die ersten offiziellen Projekttreffen sind für April und Mai 2026 geplant. Eine Beteiligung setzt keine laufende BMP-Bewerbung voraus – die Initiative steht allen bayerischen Mittelständlern offen.',
|
||||
},
|
||||
],
|
||||
tags: ['Engagement', 'CSR', 'Ehrenamt', 'Nachhaltigkeit', 'Partner'],
|
||||
relatedSlugs: ['ki-nutzung-bayerischer-kmu', 'bayerische-wirtschaft-waechst'],
|
||||
},
|
||||
{
|
||||
slug: 'bayerische-wirtschaft-waechst',
|
||||
title: 'Bayerische Wirtschaft wächst',
|
||||
cat: 'Wirtschaft',
|
||||
date: '14. Januar 2026',
|
||||
author: 'Thomas Steinbach',
|
||||
authorRole: 'Wirtschaftskorrespondent',
|
||||
readTime: '5 min',
|
||||
img: '/images/gala-dinner.jpg',
|
||||
excerpt:
|
||||
'Neue Prognosen zeigen ein stabiles Wachstum für den Mittelstand – gute Aussichten für die Awards.',
|
||||
sections: [
|
||||
{
|
||||
body: 'Trotz geopolitischer Unsicherheiten und anhaltender Zinsdiskussionen zeigt die bayerische Wirtschaft eine bemerkenswerte Resilienz. Laut einer aktuellen Analyse des ifo-Instituts, die in Kooperation mit dem EWIF durchgeführt wurde, erwarten 68 Prozent der befragten bayerischen Mittelständler für 2026 ein moderates bis starkes Umsatzwachstum. Der Freistaat bleibt damit Deutschlands wirtschaftsstärkste Region – und der Nährboden für außergewöhnliche Unternehmensgeschichten.',
|
||||
},
|
||||
{
|
||||
heading: 'Exportstärke als Stabilitätsanker',
|
||||
body: 'Besonders der exportorientierte Mittelstand profitiert von der Erholung der Nachfrage aus dem asiatischen Raum. Maschinen- und Anlagenbauer in der Oberpfalz berichten von vollen Auftragsbüchern bis ins dritte Quartal 2026. Die Automobilzulieferbranche hingegen kämpft weiterhin mit dem Transformationsdruck, zeigt aber in Einzelfällen beeindruckende Pivot-Strategien – mehrere Unternehmen haben sich erfolgreich in die Batteriekomponenten-Wertschöpfung eingefädelt.',
|
||||
},
|
||||
{
|
||||
heading: 'Mittelstand als Jobmotor',
|
||||
body: 'Bayerische KMU mit bis zu 500 Mitarbeitern beschäftigen laut Statistischem Landesamt knapp 3,4 Millionen Menschen – das sind rund 55 Prozent aller sozialversicherungspflichtigen Arbeitsplätze im Freistaat. Die Arbeitslosigkeit in Bayern lag zuletzt bei 3,1 Prozent und bleibt damit bundesweit auf dem niedrigsten Stand. Gerade in ländlichen Regionen wie Niederbayern oder der Oberpfalz sind lokale Mittelständler oft der einzige relevante Arbeitgeber im Umkreis.',
|
||||
},
|
||||
{
|
||||
heading: 'Was das für den BMP 2026 bedeutet',
|
||||
body: 'Für den Bayerischen Mittelstandspreis sind die Zahlen ein gutes Vorzeichen. Mehr Wirtschaftskraft bedeutet mehr Investitionsbereitschaft, mehr Innovationsprojekte – und damit mehr hochwertige Bewerbungen. Prof. Dr. Stefan Steinhoff, wissenschaftlicher Beirat des BMP, rechnet mit einem Rekordfeld: „Die Qualität der Bewerbungen steigt jedes Jahr. 2026 erwarte ich Unternehmensgeschichten, die bundesweit Schule machen."',
|
||||
},
|
||||
{
|
||||
heading: 'Ausblick: Herausforderungen nicht verschweigen',
|
||||
body: 'Trotz positiver Grundstimmung mahnt die Analyse zur Vorsicht. Der Fachkräftemangel bleibt das meistgenannte Wachstumshemmnis. Über 40 Prozent der Unternehmen geben an, offene Stellen seit mehr als sechs Monaten nicht besetzen zu können. Hinzu kommen steigende Energiekosten und bürokratische Belastungen, die vor allem kleinere Betriebe spüren. Der BMP möchte genau hier ansetzen: nicht nur Erfolge feiern, sondern auch den Dialog darüber fördern, wie Politik und Wirtschaft gemeinsam Lösungen entwickeln können.',
|
||||
},
|
||||
],
|
||||
tags: ['Wirtschaft', 'Wachstum', 'Bayern', 'Prognose', 'KMU'],
|
||||
relatedSlugs: ['ki-nutzung-bayerischer-kmu', 'ehrenamts-initiative-2026'],
|
||||
},
|
||||
];
|
||||
|
||||
export function getArticleBySlug(slug: string): BlogArticle | undefined {
|
||||
return articles.find(a => a.slug === slug);
|
||||
}
|
||||
319
src/spa/data/events.ts
Normal file
319
src/spa/data/events.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
export type UpdateType =
|
||||
| 'ankündigung'
|
||||
| 'erinnerung'
|
||||
| 'update'
|
||||
| 'highlight'
|
||||
| 'ergebnis'
|
||||
| 'wichtig'
|
||||
|
||||
export interface EventUpdate {
|
||||
id: string
|
||||
timestamp: string // ISO: "2026-03-15T10:30:00"
|
||||
type: UpdateType
|
||||
title: string
|
||||
body: string
|
||||
}
|
||||
|
||||
export interface EventEckdaten {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface BmpEvent {
|
||||
slug: string
|
||||
title: string
|
||||
subtitle: string
|
||||
date: string
|
||||
time: string
|
||||
location: string
|
||||
venue: string
|
||||
category: string
|
||||
status: 'geplant' | 'anmeldung-offen' | 'intern' | 'abgeschlossen'
|
||||
img: string
|
||||
description: string
|
||||
longDescription: string
|
||||
eckdaten: EventEckdaten[]
|
||||
updates: EventUpdate[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event 1 — Preisverleihung 2026
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const preisverleihung2026: BmpEvent = {
|
||||
slug: 'preisverleihung-2026',
|
||||
title: 'Preisverleihung 2026',
|
||||
subtitle: 'Der Bayerische Mittelstandspreis — Gala-Abend in der Münchner Residenz',
|
||||
date: '22. Oktober 2026',
|
||||
time: '18:00 Uhr',
|
||||
location: 'München',
|
||||
venue: 'Residenz München, Antiquarium',
|
||||
category: 'Gala / Event',
|
||||
status: 'geplant',
|
||||
img: '/images/buehne-moderatoren.jpg',
|
||||
description:
|
||||
'Die Preisverleihung des Bayerischen Mittelstandspreises 2026 ist der feierliche Höhepunkt des Jahrgangs. Im prunkvollen Antiquarium der Münchner Residenz werden herausragende mittelständische Unternehmen Bayerns vor geladenen Gästen aus Wirtschaft, Politik und Gesellschaft ausgezeichnet. Der Abend vereint Ehrung, Networking und ein exklusives Rahmenprogramm auf höchstem Niveau.',
|
||||
longDescription: `Die Preisverleihung des Bayerischen Mittelstandspreises findet in diesem Jahr im historischen Antiquarium der Münchner Residenz statt — einem der bedeutendsten Renaissancesäle nördlich der Alpen. Der festliche Rahmen unterstreicht die Wertschätzung, die wir den ausgezeichneten Unternehmen entgegenbringen: Sie stehen exemplarisch für unternehmerischen Mut, regionale Verwurzelung und wirtschaftliche Stärke.
|
||||
|
||||
Der Abend beginnt mit einem Empfang und Sektempfang ab 18:00 Uhr, gefolgt von der offiziellen Verleihungszeremonie mit Laudationes, musikalischen Darbietungen und Grußworten aus der Bayerischen Staatsregierung. Im Anschluss lädt ein gemeinsames Gala-Dinner zum Austausch und zur Vernetzung ein. Alle Nominierten, Preisträger und geladenen Gäste erleben einen Abend, der Tradition und modernen Unternehmergeist in Einklang bringt.`,
|
||||
eckdaten: [
|
||||
{ label: 'Datum', value: '22. Oktober 2026' },
|
||||
{ label: 'Uhrzeit', value: '18:00 Uhr (Einlass ab 17:30 Uhr)' },
|
||||
{ label: 'Ort', value: 'München' },
|
||||
{ label: 'Venue', value: 'Residenz München, Antiquarium' },
|
||||
{ label: 'Dresscode', value: 'Schwarzer Anzug / Abendgarderobe' },
|
||||
{ label: 'Kapazität', value: '350 geladene Gäste' },
|
||||
{ label: 'Anmeldeschluss', value: '01. Oktober 2026' },
|
||||
{ label: 'Veranstalter', value: 'Bayerischer Mittelstandspreis e. V.' },
|
||||
{
|
||||
label: 'Programmpunkte',
|
||||
value: 'Sektempfang, Verleihungszeremonie, Laudationes, Gala-Dinner',
|
||||
},
|
||||
],
|
||||
updates: [
|
||||
{
|
||||
id: 'pv2026-u8',
|
||||
timestamp: '2026-09-10T09:00:00',
|
||||
type: 'erinnerung',
|
||||
title: 'Anmeldeschluss naht — letzte Plätze verfügbar',
|
||||
body: 'Nur noch wenige Wochen bis zum Anmeldeschluss am 1. Oktober 2026. Wir bitten alle Nominierten und eingeladenen Gäste, ihre Teilnahme baldmöglichst zu bestätigen, da die Kapazitäten im Antiquarium begrenzt sind.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u7',
|
||||
timestamp: '2026-08-05T11:30:00',
|
||||
type: 'update',
|
||||
title: 'Abendprogramm finalisiert',
|
||||
body: 'Das Programm für den Gala-Abend ist nun vollständig abgestimmt. Die Verleihungszeremonie umfasst fünf Kategorien mit je einer Laudatio und einem Grußwort des Bayerischen Staatsministers für Wirtschaft. Musikalisch begleitet wird der Abend von einem Streichquartett der Münchner Philharmoniker.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u6',
|
||||
timestamp: '2026-07-14T14:00:00',
|
||||
type: 'highlight',
|
||||
title: 'Moderatorin bestätigt',
|
||||
body: 'Wir freuen uns, die Bestätigung von Katharina Bremer als Moderatorin des Abends bekanntzugeben. Die erfahrene Wirtschaftsjournalistin führt bereits zum zweiten Mal durch die Preisverleihung des Bayerischen Mittelstandspreises.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u5',
|
||||
timestamp: '2026-06-01T10:00:00',
|
||||
type: 'update',
|
||||
title: 'Einladungen an Gäste versendet',
|
||||
body: 'Die persönlichen Einladungen an alle 350 geladenen Gäste wurden heute postalisch sowie per E-Mail versandt. Darunter befinden sich Vertreter aus Wirtschaft, Politik, Verbänden und Medien aus ganz Bayern.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u4',
|
||||
timestamp: '2026-04-22T09:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Location offiziell bestätigt',
|
||||
body: 'Die Bayerische Schlösserverwaltung hat die Nutzung des Antiquariums in der Münchner Residenz für den 22. Oktober 2026 offiziell bestätigt. Damit steht der feierliche Rahmen der diesjährigen Preisverleihung fest.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u3',
|
||||
timestamp: '2026-03-15T08:30:00',
|
||||
type: 'update',
|
||||
title: 'Jury-Entscheidung abgeschlossen',
|
||||
body: 'Die unabhängige Jury hat ihre Beratungen beendet und die Preisträger in allen fünf Kategorien bestimmt. Die Entscheidungen werden bis zur Preisverleihung vertraulich behandelt. Eine persönliche Benachrichtigung der Preisträger erfolgt Ende August.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u2',
|
||||
timestamp: '2026-02-01T12:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Bewerbungsphase geschlossen',
|
||||
body: 'Die Bewerbungsphase für den Bayerischen Mittelstandspreis 2026 ist mit dem 31. Januar erfolgreich abgeschlossen worden. Insgesamt wurden 214 Bewerbungen eingereicht — ein neuer Rekord. Die Jury tritt im Februar zu ihrer ersten Sitzung zusammen.',
|
||||
},
|
||||
{
|
||||
id: 'pv2026-u1',
|
||||
timestamp: '2026-01-08T10:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Jahrgang 2026 offiziell eröffnet',
|
||||
body: 'Der Bayerische Mittelstandspreis 2026 ist offiziell gestartet. Ab sofort können sich Unternehmen und Institutionen für die diesjährige Auszeichnung bewerben. Alle Informationen zu den Kategorien und dem Bewerbungsverfahren stehen auf unserer Website bereit.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event 2 — Mittelstands-Gipfel 2026
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mittelstandsGipfel2026: BmpEvent = {
|
||||
slug: 'mittelstands-gipfel-2026',
|
||||
title: 'Mittelstands-Gipfel 2026',
|
||||
subtitle: 'Impulse, Dialog und Vernetzung für den bayerischen Mittelstand',
|
||||
date: '15. Juni 2026',
|
||||
time: '09:00 Uhr',
|
||||
location: 'Nürnberg',
|
||||
venue: 'NürnbergMesse, Halle 7A',
|
||||
category: 'Workshop',
|
||||
status: 'anmeldung-offen',
|
||||
img: '/images/networking-innenhof.jpg',
|
||||
description:
|
||||
'Der Mittelstands-Gipfel 2026 bringt Unternehmerinnen und Unternehmer aus ganz Bayern für einen Tag der Begegnung, des Austauschs und der konkreten Weiterbildung zusammen. In Panels, Workshops und Netzwerkformaten werden aktuelle Herausforderungen des Mittelstands diskutiert — von Digitalisierung über Fachkräftemangel bis hin zu nachhaltiger Unternehmensführung.',
|
||||
longDescription: `Der Mittelstands-Gipfel ist das zentrale Begegnungsformat des Bayerischen Mittelstandspreises für alle, die den Mittelstand aktiv gestalten. Im Mittelpunkt stehen in diesem Jahr drei Themenschwerpunkte: digitale Transformation in produzierenden Betrieben, Strategien zur Fachkräftegewinnung in ländlichen Regionen sowie resiliente Lieferketten in einer globalisierten Wirtschaft. Renommierte Referentinnen und Referenten aus Wissenschaft, Praxis und Politik gestalten das Programm.
|
||||
|
||||
Die Veranstaltung in der NürnbergMesse bietet neben Hauptbühnen-Panels auch kleinere Workshop-Formate mit begrenzter Teilnehmerzahl, in denen konkrete Lösungsansätze erarbeitet werden. Ein strukturiertes Networking-Programm am Mittag und am Nachmittag sorgt dafür, dass aus Gesprächen echte Verbindungen entstehen. Die Anmeldung ist derzeit geöffnet — Plätze sind begrenzt.`,
|
||||
eckdaten: [
|
||||
{ label: 'Datum', value: '15. Juni 2026' },
|
||||
{ label: 'Uhrzeit', value: '09:00–18:00 Uhr' },
|
||||
{ label: 'Ort', value: 'Nürnberg' },
|
||||
{ label: 'Venue', value: 'NürnbergMesse, Halle 7A' },
|
||||
{ label: 'Dresscode', value: 'Business Casual' },
|
||||
{ label: 'Kapazität', value: '600 Teilnehmerinnen und Teilnehmer' },
|
||||
{ label: 'Anmeldeschluss', value: '31. Mai 2026' },
|
||||
{ label: 'Veranstalter', value: 'Bayerischer Mittelstandspreis e. V.' },
|
||||
{
|
||||
label: 'Programmpunkte',
|
||||
value: 'Keynotes, Panel-Diskussionen, Workshops, Networking-Lunch, Abschluss-Podium',
|
||||
},
|
||||
],
|
||||
updates: [
|
||||
{
|
||||
id: 'mg2026-u7',
|
||||
timestamp: '2026-05-20T10:00:00',
|
||||
type: 'wichtig',
|
||||
title: 'Anmeldeschluss verlängert bis 31. Mai',
|
||||
body: 'Aufgrund der großen Nachfrage und mehrfacher Rückmeldungen haben wir den Anmeldeschluss vom 20. Mai auf den 31. Mai 2026 verlängert. Bitte melden Sie sich zeitnah an — die verbleibenden Plätze sind stark begrenzt.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u6',
|
||||
timestamp: '2026-04-28T09:30:00',
|
||||
type: 'highlight',
|
||||
title: 'Keynote-Speaker bestätigt',
|
||||
body: 'Wir freuen uns, Prof. Dr. Monika Hartmann (Technische Universität München) als Keynote-Speakerin gewonnen zu haben. Sie spricht zum Thema „KI im Mittelstand: Chancen jenseits des Hypes". Weitere Bestätigungen folgen in Kürze.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u5',
|
||||
timestamp: '2026-04-03T11:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Anmeldung ab sofort geöffnet',
|
||||
body: 'Die Anmeldung zum Mittelstands-Gipfel 2026 ist ab heute freigeschaltet. Teilnehmerinnen und Teilnehmer können sich über unser Online-Portal für die Gesamtveranstaltung sowie für einzelne Workshops anmelden. Frühbucher erhalten vergünstigte Konditionen bis zum 30. April.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u4',
|
||||
timestamp: '2026-03-10T14:00:00',
|
||||
type: 'update',
|
||||
title: 'Programm veröffentlicht',
|
||||
body: 'Das vollständige Tagesprogramm des Mittelstands-Gipfels 2026 ist jetzt online abrufbar. Es umfasst drei Hauptbühnen-Panels, zwölf Workshops in parallelen Tracks sowie ein moderiertes Networking-Format am Mittag.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u3',
|
||||
timestamp: '2026-02-18T09:00:00',
|
||||
type: 'update',
|
||||
title: 'NürnbergMesse als Venue bestätigt',
|
||||
body: 'Die NürnbergMesse GmbH hat die Nutzung der Halle 7A für den 15. Juni 2026 vertraglich bestätigt. Damit steht die Veranstaltungsfläche fest. Die Halle bietet optimale Bedingungen für Großveranstaltungen mit flexiblen Workshop-Räumen.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u2',
|
||||
timestamp: '2026-02-01T10:00:00',
|
||||
type: 'update',
|
||||
title: 'Themenstruktur festgelegt',
|
||||
body: 'Das Programmkomitee hat die drei inhaltlichen Schwerpunkte des Gipfels final abgestimmt: (1) Digitale Transformation in KMU, (2) Fachkräftesicherung in der Region, (3) Resiliente Lieferketten. Referentinnen und Referenten werden derzeit angefragt.',
|
||||
},
|
||||
{
|
||||
id: 'mg2026-u1',
|
||||
timestamp: '2026-01-15T08:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Mittelstands-Gipfel 2026 angekündigt',
|
||||
body: 'Der Bayerische Mittelstandspreis e. V. gibt bekannt: Der diesjährige Mittelstands-Gipfel findet am 15. Juni 2026 in Nürnberg statt. Erstmals wird die Veranstaltung auf der NürnbergMesse ausgerichtet und bietet damit deutlich mehr Raum für Teilnehmer und Formate.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event 3 — Nominierten-Auswahl 2026
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const nominiertenAuswahl2026: BmpEvent = {
|
||||
slug: 'nominierten-auswahl-2026',
|
||||
title: 'Nominierten-Auswahl 2026',
|
||||
subtitle: 'Interne Jury-Sitzung zur Festlegung der Nominierten des Jahrgangs 2026',
|
||||
date: '04. August 2026',
|
||||
time: '10:00 Uhr',
|
||||
location: 'Regensburg',
|
||||
venue: 'Haus der Bayerischen Wirtschaft, Regensburg',
|
||||
category: 'Jury-Sitzung',
|
||||
status: 'intern',
|
||||
img: '/images/saal-gedeckt.jpg',
|
||||
description:
|
||||
'Die Nominierten-Auswahl ist eine interne Jury-Sitzung, bei der die unabhängigen Jurymitglieder des Bayerischen Mittelstandspreises die nominierten Unternehmen für den Jahrgang 2026 final festlegen. Die Sitzung findet unter Ausschluss der Öffentlichkeit statt und ist nicht öffentlich zugänglich.',
|
||||
longDescription: `Die Jury-Sitzung zur Nominierten-Auswahl ist ein zentrales Element im Vergabeprozess des Bayerischen Mittelstandspreises. Die zwölf Jurymitglieder — bestehend aus renommierten Persönlichkeiten aus Wissenschaft, Wirtschaft und Zivilgesellschaft — bewerten an diesem Tag die eingegangenen Bewerbungen anhand der definierten Kriterien und legen die Nominierten in jeder Kategorie fest. Das Verfahren folgt klaren Prinzipien der Unabhängigkeit, Transparenz und Nachvollziehbarkeit.
|
||||
|
||||
Die Sitzung findet im Haus der Bayerischen Wirtschaft in Regensburg statt und dauert voraussichtlich den gesamten Arbeitstag. Im Vorfeld haben alle Jurymitglieder die anonymisierten Bewerbungsunterlagen in Einzelprüfung bewertet. Die Ergebnisse der Sitzung werden nach einer Sperrfrist von vier Wochen öffentlich kommuniziert und die Nominierten persönlich benachrichtigt.`,
|
||||
eckdaten: [
|
||||
{ label: 'Datum', value: '04. August 2026' },
|
||||
{ label: 'Uhrzeit', value: '10:00–17:00 Uhr' },
|
||||
{ label: 'Ort', value: 'Regensburg' },
|
||||
{ label: 'Venue', value: 'Haus der Bayerischen Wirtschaft, Regensburg' },
|
||||
{ label: 'Dresscode', value: 'Business' },
|
||||
{ label: 'Kapazität', value: '12 Jurymitglieder (interne Sitzung)' },
|
||||
{ label: 'Anmeldeschluss', value: 'Geschlossener Teilnehmerkreis' },
|
||||
{ label: 'Veranstalter', value: 'Bayerischer Mittelstandspreis e. V.' },
|
||||
{
|
||||
label: 'Programmpunkte',
|
||||
value: 'Einzelwertungen, Jury-Diskussion, Abstimmung, Protokollierung',
|
||||
},
|
||||
],
|
||||
updates: [
|
||||
{
|
||||
id: 'na2026-u7',
|
||||
timestamp: '2026-07-28T08:00:00',
|
||||
type: 'erinnerung',
|
||||
title: 'Unterlagen fristgerecht eingegangen',
|
||||
body: 'Alle Bewerbungsunterlagen wurden bis zum 25. Juli fristgerecht anonymisiert und an die Jurymitglieder übermittelt. Die Vorab-Einzelbewertungen sind bis zum 1. August einzureichen. Bitte beachten Sie die Vertraulichkeit der Unterlagen.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u6',
|
||||
timestamp: '2026-07-10T11:00:00',
|
||||
type: 'update',
|
||||
title: 'Bewerbungsunterlagen anonymisiert',
|
||||
body: 'Die Geschäftsstelle hat sämtliche 214 eingegangenen Bewerbungen anonymisiert und kategorisiert. Die Unterlagen werden in der kommenden Woche digital an alle Jurymitglieder übermittelt. Die Einzelbewertungsphase beginnt damit termingerecht.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u5',
|
||||
timestamp: '2026-06-15T14:00:00',
|
||||
type: 'wichtig',
|
||||
title: 'Jury vollständig bestätigt',
|
||||
body: 'Alle zwölf Jurymitglieder für den Jahrgang 2026 haben ihre Teilnahme an der Sitzung am 4. August schriftlich bestätigt. Darunter sind vier neue Mitglieder, die erstmals in der Jury des Bayerischen Mittelstandspreises mitwirken.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u4',
|
||||
timestamp: '2026-05-04T09:30:00',
|
||||
type: 'update',
|
||||
title: 'Bewertungskriterien für 2026 verabschiedet',
|
||||
body: 'Das Kuratorium hat die aktualisierten Bewertungskriterien für den Jahrgang 2026 verabschiedet. Neu hinzugekommen ist das Kriterium „Gesellschaftliche Verantwortung und Nachhaltigkeit", das künftig 15 % der Gesamtbewertung ausmacht.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u3',
|
||||
timestamp: '2026-04-01T10:00:00',
|
||||
type: 'update',
|
||||
title: 'Venue in Regensburg bestätigt',
|
||||
body: 'Das Haus der Bayerischen Wirtschaft in Regensburg steht für die Jury-Sitzung am 4. August zur Verfügung. Die Räumlichkeiten bieten optimale Arbeitsbedingungen und diskreten Rahmen für die vertraulichen Beratungen.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u2',
|
||||
timestamp: '2026-03-01T08:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Jury-Sitzung terminiert',
|
||||
body: 'Die interne Nominierten-Auswahl des Bayerischen Mittelstandspreises 2026 ist für den 4. August in Regensburg angesetzt. Die Einladungen an alle Jurymitglieder wurden versandt. Die Teilnahme ist verbindlich und wird im Jurymitgliedsvertrag geregelt.',
|
||||
},
|
||||
{
|
||||
id: 'na2026-u1',
|
||||
timestamp: '2026-01-20T09:00:00',
|
||||
type: 'ankündigung',
|
||||
title: 'Jury-Besetzung für 2026 bekannt gegeben',
|
||||
body: 'Der Bayerische Mittelstandspreis e. V. gibt die Zusammensetzung der unabhängigen Jury für den Jahrgang 2026 bekannt. Die Jury besteht aus zwölf Persönlichkeiten aus Wissenschaft, Unternehmertum, Verbänden und Zivilgesellschaft. Vier Mitglieder sind neu.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Exports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const events: BmpEvent[] = [
|
||||
preisverleihung2026,
|
||||
mittelstandsGipfel2026,
|
||||
nominiertenAuswahl2026,
|
||||
]
|
||||
|
||||
export function getEventBySlug(slug: string): BmpEvent | undefined {
|
||||
return events.find((event) => event.slug === slug)
|
||||
}
|
||||
309
src/spa/data/winners.ts
Normal file
309
src/spa/data/winners.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
export interface Winner {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
category: string;
|
||||
year: number;
|
||||
type: string;
|
||||
img: string;
|
||||
shortDesc: string;
|
||||
longDesc: string;
|
||||
quote: string;
|
||||
quotePerson: string;
|
||||
quoteRole: string;
|
||||
location: string;
|
||||
industry: string;
|
||||
website: string;
|
||||
hasMedia: boolean;
|
||||
}
|
||||
|
||||
export const WINNERS: Winner[] = [
|
||||
// ── 2025 ──────────────────────────────────────────────────────────────────
|
||||
{
|
||||
id: '1', slug: 'techvision-muenchen',
|
||||
name: 'TechVision München', category: 'Innovation & Technologie', year: 2025, type: 'Gesamtsieger',
|
||||
img: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'KI-gestützte Qualitätssicherung für die Fertigungsindustrie',
|
||||
longDesc: 'TechVision München revolutioniert die industrielle Qualitätskontrolle mit proprietären Computer-Vision-Algorithmen. Ihre Software erkennt Fertigungsfehler 40× schneller als konventionelle Methoden und reduziert den Ausschuss um bis zu 78 %. Über 120 Mittelständler bayernweit setzen bereits auf die Technologie des 2018 gegründeten Unternehmens.',
|
||||
quote: 'Der BMP ist für uns mehr als eine Auszeichnung — er ist ein Versprechen an unsere Kunden und Mitarbeiter.',
|
||||
quotePerson: 'Dr. Andreas Möller', quoteRole: 'Gründer & CEO',
|
||||
location: 'München', industry: 'Künstliche Intelligenz', website: 'www.techvision-muenchen.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '2', slug: 'alpintech-solutions',
|
||||
name: 'AlpinTech Solutions', category: 'Nachhaltigkeit', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Revolutionäre Sensorik im Bereich Katastrophenschutz und Frühwarnung',
|
||||
longDesc: 'AlpinTech Solutions entwickelt hochpräzise Umweltsensoren für Frühwarnsysteme bei Naturkatastrophen. In Zusammenarbeit mit dem Freistaat Bayern wurden über 300 Sensoren in alpinen Risikogebieten installiert. Die Technologie hat nachweislich drei Evakuierungen in Echtzeit ermöglicht.',
|
||||
quote: 'Sicherheit und Nachhaltigkeit sind keine Gegensätze — sie bedingen einander.',
|
||||
quotePerson: 'Maria Steinberg', quoteRole: 'Co-Gründerin & CTO',
|
||||
location: 'Garmisch-Partenkirchen', industry: 'Umwelttechnologie', website: 'www.alpintech.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '3', slug: 'biowerkbayern',
|
||||
name: 'BioWerk Bayern', category: 'Nachhaltigkeit', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Kreislaufwirtschaft in der Holzverarbeitung neu gedacht',
|
||||
longDesc: 'BioWerk Bayern hat einen vollständig geschlossenen Kreislauf in der Holzverarbeitung entwickelt. Sägemehl und Holzreste werden zu hochwertigen Biokomposit-Materialien verarbeitet, die fossile Kunststoffe in der Verpackungsindustrie ersetzen. Der CO₂-Fußabdruck wurde seit 2020 um 65 % reduziert.',
|
||||
quote: 'Abfall gibt es bei uns nicht — nur Rohstoffe, die noch auf ihre zweite Chance warten.',
|
||||
quotePerson: 'Klaus Huber', quoteRole: 'Geschäftsführer',
|
||||
location: 'Straubing', industry: 'Kreislaufwirtschaft', website: 'www.biowerkbayern.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '4', slug: 'handwerk40-gmbh',
|
||||
name: 'Handwerk 4.0 GmbH', category: 'Unternehmenskultur', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1560472355-536de3962603?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Digitale Lehrlingsausbildung mit VR und KI-gestütztem Feedback',
|
||||
longDesc: 'Handwerk 4.0 GmbH hat ein preisgekröntes Ausbildungskonzept entwickelt, das Virtual-Reality-Simulationen mit KI-Coaching verbindet. Azubis lernen komplexe Handgriffe in einer risikofreien Umgebung, bevor sie real ausgeführt werden. Die Fehlerquote in der Ausbildung sank um 52 %.',
|
||||
quote: 'Wir bilden nicht für die Vergangenheit aus — wir bereiten auf die Zukunft vor.',
|
||||
quotePerson: 'Thomas Baur', quoteRole: 'Gründer',
|
||||
location: 'Augsburg', industry: 'Bildungstechnologie', website: 'www.handwerk40.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '5', slug: 'greentech-bayern',
|
||||
name: 'GreenTech Bayern AG', category: 'Nachhaltigkeit', year: 2025, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Photovoltaik-Lösungen speziell für landwirtschaftliche Betriebe',
|
||||
longDesc: 'GreenTech Bayern AG hat eine speziell für Agrarbetriebe entwickelte Agri-PV-Lösung auf den Markt gebracht. Die Anlage spendet gleichzeitig Schatten für Nutzpflanzen und erzeugt Strom. Über 200 bayerische Höfe sind bereits angeschlossen, mit einem Gesamtpotenzial von 800 MW.',
|
||||
quote: 'Wenn Energie und Ernte gemeinsam wachsen, gewinnen alle.',
|
||||
quotePerson: 'Sabine Meier', quoteRole: 'Vorstandsvorsitzende',
|
||||
location: 'Landsberg am Lech', industry: 'Erneuerbare Energie', website: 'www.greentech-bayern.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '6', slug: 'neura-robotics',
|
||||
name: 'NEURA Robotics GmbH', category: 'Innovation & Technologie', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Humanoide Serviceroboter für Pflege und Logistik',
|
||||
longDesc: 'NEURA Robotics entwickelt kognitive Humanoide, die in pflegerischen und logistischen Umgebungen arbeiten. Ihr Flaggschiff-Roboter „MAiRA" unterstützt Pflegepersonal bei körperlich belastenden Tätigkeiten und ist bereits in 12 bayerischen Krankenhäusern im Einsatz.',
|
||||
quote: 'Roboter ersetzen keine Menschen — sie ermöglichen, dass Menschen mehr Mensch sein können.',
|
||||
quotePerson: 'David Reger', quoteRole: 'Gründer & CEO',
|
||||
location: 'Metzingen / München', industry: 'Robotik', website: 'www.neura-robotics.com', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '7', slug: 'planet-a-foods',
|
||||
name: 'Planet A Foods', category: 'Nachhaltigkeit', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1531482615713-2afd69097998?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Kakaofrei und fair: Schokolade aus Sonnenblumenkernen',
|
||||
longDesc: 'Planet A Foods hat ChoViva entwickelt — eine Schokoladenalternative aus Sonnenblumenkernen und geröstetem Hafer. Das Produkt ist vollständig regionaler Herkunft, enthält keinen Kakao und produziert 90 % weniger CO₂. Die Süßwarenindustrie in Europa zeigt großes Interesse.',
|
||||
quote: 'Guter Geschmack und gutes Gewissen müssen kein Widerspruch sein.',
|
||||
quotePerson: 'Maximilian Marquart', quoteRole: 'Gründer & CEO',
|
||||
location: 'München', industry: 'Food Tech', website: 'www.planet-a-foods.com', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '8', slug: 'medistat-gmbh',
|
||||
name: 'MediStat GmbH', category: 'Gesellschaftliches Engagement', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1507679799987-c73779587ccf?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Digitale Patientenakte für ländliche Gesundheitsversorgung',
|
||||
longDesc: 'MediStat GmbH verbessert die medizinische Versorgung im ländlichen Bayern durch eine vernetzte digitale Patientenakte, die Arztpraxen, Krankenhäuser und Pflegeheime verbindet. In 45 Landkreisen hat die Lösung die Behandlungsqualität messbar verbessert und Krankenhauseinweisungen um 18 % reduziert.',
|
||||
quote: 'Gute Medizin darf nicht davon abhängen, wo man aufgewachsen ist.',
|
||||
quotePerson: 'Dr. Christine Wolf', quoteRole: 'Gründerin & Ärztin',
|
||||
location: 'Regensburg', industry: 'Digital Health', website: 'www.medistat.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '9', slug: 'opticbavaria',
|
||||
name: 'OpticBavaria', category: 'Handwerk', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1553877522-43269d4ea984?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Tradition trifft Hightech in der feinmechanischen Präzisionsoptik',
|
||||
longDesc: 'OpticBavaria verbindet 140 Jahre Tradition in der Feinmechanik mit modernster CNC-Lasertechnik. Als Zulieferer für die Medizin-, Raumfahrt- und Verteidigungsbranche fertigt das Familienunternehmen in der 5. Generation optische Präzisionsbauteile mit Toleranzen im Nanometerbereich.',
|
||||
quote: 'Präzision ist bei uns keine Eigenschaft des Produkts — sie ist Haltung der Menschen.',
|
||||
quotePerson: 'Stefan Opitz', quoteRole: 'Inhaber & Geschäftsführer',
|
||||
location: 'Oberkochen', industry: 'Feinmechanik & Optik', website: 'www.opticbavaria.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '10', slug: 'weber-praezision',
|
||||
name: 'Weber Präzision GmbH', category: 'Internationales Wachstum', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Zerspanungstechnik made in Bayern — weltweit exportiert',
|
||||
longDesc: 'Weber Präzision GmbH hat sich in 20 Jahren von einer 3-Mann-Werkstatt zum exportstarken Mittelständler mit 18 internationalen Vertriebspartnern entwickelt. Ihre CNC-Zerspanungsteile finden sich in Windturbinen in Kanada, U-Booten in Australien und Medizingeräten in Japan.',
|
||||
quote: 'Der Preis hat uns nicht verändert — aber er hat der Welt gezeigt, was Bayern kann.',
|
||||
quotePerson: 'Hans Weber', quoteRole: 'Gründer & Inhaber',
|
||||
location: 'Ingolstadt', industry: 'Maschinenbau', website: 'www.weber-praezision.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '11', slug: 'ecotech-bavaria',
|
||||
name: 'EcoTech Bavaria', category: 'Digitale Transformation', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'IoT-Plattform zur Reduktion des Energieverbrauchs in KMU',
|
||||
longDesc: 'EcoTech Bavaria bietet eine vollintegrierte IoT-Lösung für mittelständische Produktionsbetriebe. Die Plattform überwacht Energieverbräuche in Echtzeit, identifiziert Einsparpotenziale automatisch und steuert Anlagen optimiert. Kunden sparen im Schnitt 23 % Energiekosten im ersten Jahr.',
|
||||
quote: 'Digitalisierung ist kein Selbstzweck. Sie muss sich rechnen — ökologisch und ökonomisch.',
|
||||
quotePerson: 'Lisa Berger', quoteRole: 'Geschäftsführerin',
|
||||
location: 'Erlangen', industry: 'CleanTech / IoT', website: 'www.ecotech-bavaria.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '12', slug: 'alpenlogistik',
|
||||
name: 'Alpenlogistik OHG', category: 'Unternehmenskultur', year: 2025, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1542744173-8e7e53415bb0?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Mitarbeiterbeteiligung und Vier-Tage-Woche in der Logistikbranche',
|
||||
longDesc: 'Alpenlogistik OHG hat als eines der ersten Logistikunternehmen Deutschlands die Vier-Tage-Woche eingeführt — ohne Lohnkürzung. Die Fluktuation sank von 34 % auf 6 %. Parallel wurden alle 280 Mitarbeiter am Unternehmen beteiligt. Das Modell wird bundesweit als Vorzeigeprojekt diskutiert.',
|
||||
quote: 'Wer seinen Mitarbeitern vertraut, dem vertrauen die Mitarbeiter zurück.',
|
||||
quotePerson: 'Georg Huber', quoteRole: 'Geschäftsführer',
|
||||
location: 'Rosenheim', industry: 'Logistik & Transport', website: 'www.alpenlogistik.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '13', slug: 'bavarian-ai-labs',
|
||||
name: 'Bavarian AI Labs', category: 'Innovation & Technologie', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Sprachmodelle für Fachsprachen in Recht, Medizin und Technik',
|
||||
longDesc: 'Bavarian AI Labs hat domänenspezifische LLMs entwickelt, die auf Fachsprachen spezialisiert sind. Der juridische KI-Assistent „LexAI" wird bereits von 400 bayerischen Kanzleien genutzt und reduziert Recherchezeiten um 70 %. Im Aufbau: medizinische und maschinenbauliche Varianten.',
|
||||
quote: 'Allgemeine KI ist gut. Fachtiefe KI ist das, was unsere Kunden brauchen.',
|
||||
quotePerson: 'Prof. Dr. Kai Fischer', quoteRole: 'Wissenschaftlicher Direktor',
|
||||
location: 'München', industry: 'Künstliche Intelligenz', website: 'www.bavarian-ai-labs.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '14', slug: 'maier-praezision',
|
||||
name: 'Maier Präzisionstechnik', category: 'Handwerk', year: 2025, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Werkzeugbau in der 4. Generation mit volldigitaler Prozesskette',
|
||||
longDesc: 'Maier Präzisionstechnik hat als traditioneller Werkzeugbauer die vollständige Digitalisierung seiner Prozesskette abgeschlossen — von der CAD-Konstruktion bis zum 5-Achs-Bearbeitungszentrum. Liefer- und Fehlerquoten haben sich seither halbiert. Der Betrieb dient als Lernfabrik für andere Handwerksbetriebe.',
|
||||
quote: 'Digitalisierung ist keine Bedrohung fürs Handwerk — sie ist sein Zukunftsversprechen.',
|
||||
quotePerson: 'Brigitte Maier', quoteRole: 'Inhaberin',
|
||||
location: 'Schweinfurt', industry: 'Werkzeugbau', website: 'www.maier-praezision.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '15', slug: 'scholz-partner',
|
||||
name: 'Scholz & Partner', category: 'Internationales Wachstum', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Ingenieurbüro für Brückenbau mit Projekten auf vier Kontinenten',
|
||||
longDesc: 'Scholz & Partner hat sich aus einem 12-köpfigen Bamberger Ingenieurbüro zum internationalen Player in der Brückenplanung entwickelt. Aktuelle Großprojekte: eine Hängebrücke in Vietnam, Stadtbahnviadukte in Kolumbien und Straßenbrücken in Kenia — überall mit bayerischer Ingenieurskunst.',
|
||||
quote: 'Bayern ist unser Zuhause, die Welt ist unser Markt.',
|
||||
quotePerson: 'Michael Scholz', quoteRole: 'Gründungspartner',
|
||||
location: 'Bamberg', industry: 'Bauingenieurwesen', website: 'www.scholz-partner.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '16', slug: 'sporttech-bavaria',
|
||||
name: 'SportTech Bavaria', category: 'Gesellschaftliches Engagement', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1556761175-4b46a572b786?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Sporttechnologie für Menschen mit Behinderung',
|
||||
longDesc: 'SportTech Bavaria entwickelt adaptive Sporttechnologien für Menschen mit körperlichen Einschränkungen. Ihre Lösung für Rollstuhlrennfahrer kombiniert KI-gestütztes Training mit Echtzeitanalyse. Das Unternehmen sponsert zudem 14 paralympische Athleten aus Bayern.',
|
||||
quote: 'Leistungssport ist kein Privileg der Gesunden — er gehört allen.',
|
||||
quotePerson: 'Anna Schmidt', quoteRole: 'Gründerin',
|
||||
location: 'Nürnberg', industry: 'Sport & Assistenztechnologie', website: 'www.sporttech-bavaria.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '17', slug: 'datasilos-gmbh',
|
||||
name: 'DataSilos GmbH', category: 'Digitale Transformation', year: 2025, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Datenstrategie und -integration für den verarbeitenden Mittelstand',
|
||||
longDesc: 'DataSilos GmbH macht Produktionsdaten aus Legacy-ERP-Systemen nutzbar. Ihre Middleware verbindet Jahrzehnte alte Maschinensteuerungen mit modernen Analyse- und KI-Plattformen — ohne aufwändigen Systemwechsel. Über 75 bayerische Produktionsunternehmen arbeiten bereits datenbankgestützt.',
|
||||
quote: 'Die wertvollsten Daten eines Unternehmens verstauben oft in 30 Jahre alten Systemen.',
|
||||
quotePerson: 'Florian Kern', quoteRole: 'CTO & Mitgründer',
|
||||
location: 'Wolfratshausen', industry: 'Data Engineering', website: 'www.datasilos.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '18', slug: 'kreativwerkstatt-passau',
|
||||
name: 'Kreativ Werkstatt Passau', category: 'Unternehmenskultur', year: 2025, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1511578314322-379afb476865?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Inklusiver Designbetrieb — 40 % der Belegschaft mit Behinderung',
|
||||
longDesc: 'Die Kreativ Werkstatt Passau ist ein Grafikdesign- und Druckbetrieb, bei dem 40 % der Mitarbeiter eine anerkannte Behinderung haben. Das Unternehmen hat bewiesen, dass inklusive Beschäftigung kein Kompromiss in der Qualität bedeutet — Großkunden wie die Stadt Passau und mehrere Landkreise lassen hier drucken.',
|
||||
quote: 'Jeder Mensch hat etwas beizutragen. Man muss nur die richtige Aufgabe finden.',
|
||||
quotePerson: 'Ute Hoffmann', quoteRole: 'Sozialunternehmerin & Leiterin',
|
||||
location: 'Passau', industry: 'Sozialwirtschaft / Design', website: 'www.kreativwerkstatt-passau.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '19', slug: 'timeteller-gmbh',
|
||||
name: 'TimeTeller GmbH', category: 'Innovation & Technologie', year: 2025, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Autonome Zeiterfassung mit biometrischer Erkennung ohne Datenspeicherung',
|
||||
longDesc: 'TimeTeller GmbH hat eine DSGVO-konforme biometrische Zeiterfassung entwickelt, die ohne Datenspeicherung auskommt. Das System erkennt Mitarbeiter on-device in Echtzeit und verwirft die Biometrie sofort nach der Verifizierung. Über 200 KMU in Bayern nutzen die Lösung bereits.',
|
||||
quote: 'Datenschutz ist nicht Hindernis — er ist Designprinzip.',
|
||||
quotePerson: 'Julia Kramer', quoteRole: 'Gründerin & CEO',
|
||||
location: 'München', industry: 'HR Tech / Datenschutz', website: 'www.timeteller.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '20', slug: 'alpenmilch-genossenschaft',
|
||||
name: 'Alpenmilch Genossenschaft', category: 'Gesellschaftliches Engagement', year: 2025, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1578357078586-491adf1aa5ba?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Faire Milchwirtschaft mit direkter Beteiligung der Landwirte',
|
||||
longDesc: 'Die Alpenmilch Genossenschaft bringt 120 bayerische Landwirtsfamilien in einer fairen Direktvermarktungs-Struktur zusammen. Landwirte erhalten 60 % mehr pro Liter als im konventionellen Handel. Das Genossenschaftsmodell gilt als Blaupause für eine neue, faire Lebensmittelkette.',
|
||||
quote: 'Wenn der Bauer verdient, verdient am Ende die ganze Region.',
|
||||
quotePerson: 'Johann Huber', quoteRole: 'Vorstandssprecher',
|
||||
location: 'Bad Tölz', industry: 'Landwirtschaft / Lebensmittel', website: 'www.alpenmilch-genossenschaft.de', hasMedia: false,
|
||||
},
|
||||
|
||||
// ── 2024 ──────────────────────────────────────────────────────────────────
|
||||
{
|
||||
id: '21', slug: 'rotkaepper-mumm',
|
||||
name: 'Rotkäppchen-Mumm', category: 'Gesellschaftliches Engagement', year: 2024, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1580893246395-52aead8960dc?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Soziales Engagement und regionale Wirtschaftsförderung in Bayern',
|
||||
longDesc: 'Rotkäppchen-Mumm hat eine Partnerschaft mit über 30 bayerischen Weingütern geschlossen und fördert aktiv die regionale Weinwirtschaft. Das Unternehmen investiert jährlich 2 Mio. Euro in Qualifizierungsprogramme für Winzer.',
|
||||
quote: 'Regionale Verantwortung bedeutet, in die Menschen zu investieren, die das Land prägen.',
|
||||
quotePerson: 'Markus Weber', quoteRole: 'Regionalleiter Bayern',
|
||||
location: 'Freyburg / München', industry: 'Getränkeindustrie', website: 'www.rotkaepper-mumm.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '22', slug: 'black-semiconductor',
|
||||
name: 'Black Semiconductor', category: 'Innovation & Technologie', year: 2024, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Graphen-basierte Halbleiter der nächsten Generation',
|
||||
longDesc: 'Black Semiconductor entwickelt Hochleistungschips auf Graphenbasis, die Silizium in bestimmten Hochtemperatur- und Hochfrequenzanwendungen übertreffen. Das Unternehmen hält 24 Patente und arbeitet eng mit dem Fraunhofer-Institut zusammen.',
|
||||
quote: 'Graphen ist das Material, auf dem die nächste Industrierevolution aufgebaut wird.',
|
||||
quotePerson: 'Dr. Satender Kumar', quoteRole: 'Gründer & CEO',
|
||||
location: 'Aachen / München', industry: 'Halbleiter', website: 'www.blacksemiconductor.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '23', slug: 'intima-gmbh',
|
||||
name: 'Intima GmbH', category: 'Gesellschaftliches Engagement', year: 2024, type: 'Schüler:innen-Preis',
|
||||
img: 'https://images.unsplash.com/photo-1531482615713-2afd69097998?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Schülerinitiative für sexuelle Aufklärung und Gesundheitsbildung',
|
||||
longDesc: 'Intima GmbH, gegründet von fünf Gymnasiasten aus München, bietet ein digitales Aufklärungsportal an, das Schüler:innen bundesweit erreicht. Die Plattform arbeitet mit Ärzten und Pädagogen zusammen und wurde vom Bayerischen Kultusministerium als Unterrichtsmaterial empfohlen.',
|
||||
quote: 'Aufklärung ist kein Tabu — sie ist ein Grundrecht.',
|
||||
quotePerson: 'Lena Fischer', quoteRole: 'Mitgründerin (18)',
|
||||
location: 'München', industry: 'Bildung / Health', website: 'www.intima-gmbh.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '24', slug: 'reverion-ag',
|
||||
name: 'Reverion AG', category: 'Nachhaltigkeit', year: 2024, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'CO₂-neutrale Biogas-Brennstoffzellen für dezentrale Energieversorgung',
|
||||
longDesc: 'Reverion AG hat eine revolutionäre Brennstoffzellentechnologie entwickelt, die Biogas mit einem Wirkungsgrad von über 80 % in Strom umwandelt. Anders als konventionelle Anlagen ist die Technologie bidirektional — sie kann auch als Elektrolyseur für grünen Wasserstoff dienen.',
|
||||
quote: 'Der Schlüssel zur Energiewende liegt in der Flexibilität.',
|
||||
quotePerson: 'Stephan Herrmann', quoteRole: 'Mitgründer & CTO',
|
||||
location: 'Garching', industry: 'Energietechnik', website: 'www.reverion.com', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '25', slug: 'planqc-gmbh',
|
||||
name: 'PlanQC GmbH', category: 'Innovation & Technologie', year: 2024, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Quantencomputer auf Basis neutraler Atome — Made in Bavaria',
|
||||
longDesc: 'PlanQC baut Quantencomputer auf Basis neutraler Atome, die in optischen Gittern gefangen werden. Diese Architektur verspricht bessere Skalierbarkeit als ionenbasierte oder supraleitende Systeme. Das Team besteht aus Spin-offs des Max-Planck-Instituts für Quantenoptik.',
|
||||
quote: 'Quantencomputing ist kein Hype — es ist die Rechenrevolution des 21. Jahrhunderts.',
|
||||
quotePerson: 'Dr. Johannes Zeiher', quoteRole: 'Gründer & CEO',
|
||||
location: 'Garching', industry: 'Quantentechnologie', website: 'www.planqc.eu', hasMedia: true,
|
||||
},
|
||||
|
||||
// ── 2023 ──────────────────────────────────────────────────────────────────
|
||||
{
|
||||
id: '26', slug: 'agrarquant-gmbh',
|
||||
name: 'AgrarQuant GmbH', category: 'Nachhaltigkeit', year: 2023, type: 'Sieger',
|
||||
img: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'KI-gestützte Bodenanalyse für präzise Düngung',
|
||||
longDesc: 'AgrarQuant GmbH kombiniert Satellitenbilder, Bodenproben und historische Erntedaten zu einem Präzisionsdünge-Plan. Landwirte sparen bis zu 30 % Düngemittel bei gleichem oder besserem Ertrag. Das Unternehmen arbeitet mit 600 bayerischen Betrieben.',
|
||||
quote: 'Die Natur gibt uns genaue Hinweise. Man muss sie nur lesen können.',
|
||||
quotePerson: 'Dr. Petra Schmid', quoteRole: 'Gründerin & Agrarwissenschaftlerin',
|
||||
location: 'Weihenstephan', industry: 'AgriTech', website: 'www.agrarquant.de', hasMedia: false,
|
||||
},
|
||||
{
|
||||
id: '27', slug: 'liqtech-bavaria',
|
||||
name: 'LiqTech Bavaria', category: 'Innovation & Technologie', year: 2023, type: 'Aufsteiger',
|
||||
img: 'https://images.unsplash.com/photo-1560472355-536de3962603?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Flüssigmetallkühlsysteme für Hochleistungsrechenzentren',
|
||||
longDesc: 'LiqTech Bavaria hat ein revolutionäres Kühlsystem für Rechenzentren entwickelt, das Flüssigmetall als Wärmeträger einsetzt. Die Effizienz liegt 40 % über konventioneller Wasserkühlung, der Wasserverbrauch sinkt um 80 %. Microsoft und SAP haben erste Testinstallationen in Auftrag gegeben.',
|
||||
quote: 'Jedes Rechenzentrum, das wir kühlen, spart den CO₂-Ausstoß von 400 Autos.',
|
||||
quotePerson: 'Thomas Keller', quoteRole: 'Gründer & CEO',
|
||||
location: 'München', industry: 'Kühlsysteme / IT', website: 'www.liqtech-bavaria.de', hasMedia: true,
|
||||
},
|
||||
{
|
||||
id: '28', slug: 'circulartech-ingolstadt',
|
||||
name: 'CircularTech Ingolstadt', category: 'Nachhaltigkeit', year: 2023, type: 'Sonderpreis',
|
||||
img: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&q=80&w=800',
|
||||
shortDesc: 'Aufbereitung von Elektroschrott zu Sekundärrohstoffen für die E-Mobility',
|
||||
longDesc: 'CircularTech Ingolstadt schließt den Kreislauf für Batterierohstoffe in der Elektromobilität. Das Unternehmen recycelt Lithium-Ionen-Akkus und gewinnt daraus Kobalt, Lithium und Nickel in Batteriequalität zurück — eine Schlüsseltechnologie für eine europäische Batteriezellproduktion.',
|
||||
quote: 'Die Rohstoffe der Zukunft liegen nicht im Boden — sie liegen in alten Produkten.',
|
||||
quotePerson: 'Dr. Markus Berg', quoteRole: 'Gründer & CEO',
|
||||
location: 'Ingolstadt', industry: 'Recycling / E-Mobility', website: 'www.circulartech.de', hasMedia: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const CATEGORIES = [...new Set(WINNERS.map(w => w.category))].sort();
|
||||
export const YEARS = [...new Set(WINNERS.map(w => w.year))].sort((a, b) => b - a);
|
||||
13
src/spa/hooks/useIsMobile.ts
Normal file
13
src/spa/hooks/useIsMobile.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useIsMobile(breakpoint = 768): boolean {
|
||||
const [isMobile, setIsMobile] = useState(() => window.innerWidth < breakpoint);
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
||||
const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
||||
mq.addEventListener('change', handler);
|
||||
setIsMobile(mq.matches);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
}, [breakpoint]);
|
||||
return isMobile;
|
||||
}
|
||||
126
src/spa/index.css
Normal file
126
src/spa/index.css
Normal file
@@ -0,0 +1,126 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-display: "IBM Plex Sans", sans-serif;
|
||||
--font-body: "Inter", sans-serif;
|
||||
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-md: calc(0.5rem - 2px);
|
||||
--radius-sm: calc(0.5rem - 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: #E4E2E3;
|
||||
--foreground: #020617;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #020617;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #020617;
|
||||
--primary: #03093A;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f8fafc;
|
||||
--secondary-foreground: #03093A;
|
||||
--muted: #f1f5f9;
|
||||
--muted-foreground: #64748b;
|
||||
--accent: #EFBF04;
|
||||
--accent-foreground: #ffffff;
|
||||
--destructive: #ef4444;
|
||||
--border: #e2e8f0;
|
||||
--input: #e2e8f0;
|
||||
--ring: #03093A;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: #020617;
|
||||
--foreground: #f8fafc;
|
||||
--card: #1e293b;
|
||||
--card-foreground: #f8fafc;
|
||||
--popover: #020617;
|
||||
--popover-foreground: #f8fafc;
|
||||
--primary: #03093A;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #1e293b;
|
||||
--secondary-foreground: #f8fafc;
|
||||
--muted: #1e293b;
|
||||
--muted-foreground: #94a3b8;
|
||||
--accent: #EFBF04;
|
||||
--accent-foreground: #ffffff;
|
||||
--destructive: #7f1d1d;
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
--input: rgba(255, 255, 255, 0.15);
|
||||
--ring: #94a3b8;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-body;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #EFBF04;
|
||||
color: #03093A;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-display font-bold;
|
||||
}
|
||||
|
||||
/* Quotes / Zitate — Inter kursiv */
|
||||
blockquote, q,
|
||||
.quote, [class*="italic"] {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Labels / Eyebrows — IBM Plex Sans */
|
||||
label,
|
||||
.label {
|
||||
font-family: "IBM Plex Sans", sans-serif;
|
||||
}
|
||||
|
||||
/* Form inputs — Inter */
|
||||
input, textarea, select, button {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.6s ease-out;
|
||||
}
|
||||
}
|
||||
6
src/spa/lib/utils.ts
Normal file
6
src/spa/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
485
src/spa/pages/About.tsx
Normal file
485
src/spa/pages/About.tsx
Normal file
@@ -0,0 +1,485 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { Star, ChevronRight } from 'lucide-react';
|
||||
import TestimonialsSection from '@/spa/components/TestimonialsSection';
|
||||
import { Button } from '@/spa/components/ui/button';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#E4E2E3';
|
||||
|
||||
// 8 highlight winners from 2025
|
||||
const HIGHLIGHTS = WINNERS.filter(w => w.year === 2025).slice(0, 8);
|
||||
|
||||
const About: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
|
||||
{/* ── 1. HERO ─────────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', minHeight: '72vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: '#060C14' }}>
|
||||
<img
|
||||
src="/images/saal-leer-atmosphaere.jpg"
|
||||
alt="BMP Gala"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
{/* Left-to-right dark overlay */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)' : 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)' }} />
|
||||
{/* Gold accent line */}
|
||||
<div style={{ position: 'absolute', top: 0, left: 80, width: 2, height: '100%', background: `linear-gradient(to bottom, transparent, ${GOLD}, transparent)`, opacity: 0.4 }} />
|
||||
{/* Gold bottom line */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)', zIndex: 2 }} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 80px', maxWidth: 860 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
|
||||
<div style={{ width: 40, height: 2, background: GOLD }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04' }}>
|
||||
Bayerischer Mittelstandspreis
|
||||
</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 6vw, 5rem)' : 'clamp(2.8rem, 6vw, 5rem)', fontWeight: 900, color: '#fff', lineHeight: 0.95, letterSpacing: '-0.03em', textTransform: 'uppercase', margin: '0 0 28px' }}>
|
||||
WERTE, DIE<br />
|
||||
<span style={{ color: GOLD }}>BAYERN BEWEGEN</span>
|
||||
</h1>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, maxWidth: 560, margin: '0 0 40px', fontWeight: 300 }}>
|
||||
Seit 2005 ehrt der BMP herausragende Leistungen im bayerischen Mittelstand. Entdecken Sie die Geschichte, die Vision und die Menschen dahinter.
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#fff', background: '#111D55', padding: '14px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'background 0.15s, color 0.15s', border: '2px solid rgba(255,255,255,0.15)' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.color = '#101828'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }}
|
||||
>
|
||||
Jetzt bewerben <ChevronRight size={16} />
|
||||
</Link>
|
||||
<Link
|
||||
to="/preistraeger"
|
||||
style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Alle Preisträger <ChevronRight size={16} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats strip */}
|
||||
<div style={{ position: 'absolute', bottom: 0, right: 0, display: isMobile ? 'none' : 'flex', borderTop: '1px solid rgba(255,255,255,0.08)', borderLeft: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
{[['20+', 'Jahre'], ['0 €', 'Teilnahme'], ['100%', 'Unabhängig']].map(([val, lbl], i) => (
|
||||
<div key={i} style={{ padding: '20px 36px', textAlign: 'center', borderRight: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 22, fontWeight: 900, color: GOLD }}>{val}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginTop: 2 }}>{lbl}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 2a. WAS IST DER PREIS? (cream, text left / image right) ─────────── */}
|
||||
<section id="was-ist" style={{ position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', minHeight: isMobile ? 'auto' : 580 }}>
|
||||
|
||||
{/* Left – text on cream */}
|
||||
<div style={{ background: CREAM, padding: isMobile ? '32px 24px 44px' : '88px 80px', display: 'flex', flexDirection: 'column', justifyContent: 'center', order: isMobile ? 2 : 0 }}>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 20 }}>Was ist der Preis?</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2.1rem, 3.4vw, 3.1rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 20px' }}>
|
||||
DER BAYERISCHE<br />MITTELSTANDS<wbr />PREIS
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.8)', lineHeight: 1.8, maxWidth: 440, marginBottom: 16 }}>
|
||||
Seit 2005 ist der BMP eine der bedeutendsten Auszeichnungen für den bayerischen Mittelstand. Vergeben von einer vollständig unabhängigen Expertenjury, ehrt er Unternehmen, die durch Innovation, Nachhaltigkeit und regionale Verwurzelung herausragen.
|
||||
</p>
|
||||
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.8)', lineHeight: 1.8, maxWidth: 440, marginBottom: isMobile ? 0 : 44 }}>
|
||||
Entstanden aus einer Initiative bayerischer Wirtschaftsvertreter, ehrt der Preis heute jährlich die Leuchttürme unserer Wirtschaft.
|
||||
</p>
|
||||
{/* Key facts – moved above the copy on mobile */}
|
||||
<div style={{ display: 'flex', order: isMobile ? -1 : 0, borderTop: isMobile ? 'none' : '1px solid #D0D5DD', borderBottom: isMobile ? '1px solid #D0D5DD' : 'none', marginBottom: isMobile ? 26 : 0 }}>
|
||||
{[
|
||||
{ val: '2005', label: 'Gegründet' },
|
||||
{ val: '0 €', label: 'Teilnahmegebühr' },
|
||||
{ val: '5', label: 'Jury-Schritte' },
|
||||
].map((f, i) => (
|
||||
<div key={i} style={{ flex: 1, padding: '20px 0', borderRight: i < 2 ? '1px solid #D0D5DD' : 'none', paddingLeft: 0, paddingRight: i < 2 ? 20 : 0 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2vw, 1.8rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.03em', lineHeight: 1 }}>{f.val}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.18em', marginTop: 6 }}>{f.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right – full-bleed image (+ heading on mobile) */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined, order: isMobile ? 1 : 0 }}>
|
||||
<img
|
||||
src="/images/preistraeger-gruppe.jpg"
|
||||
alt="BMP Gala Preisverleihung"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||
? 'linear-gradient(to top, rgba(3,9,58,0.94) 0%, rgba(3,9,58,0.55) 38%, rgba(3,9,58,0.12) 72%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.5) 0%, transparent 55%)'
|
||||
: `linear-gradient(to right, ${CREAM} 0%, transparent 25%), linear-gradient(to top, rgba(3,9,58,0.75) 0%, transparent 60%)` }} />
|
||||
{/* Heading overlay – mobile only */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 24px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Was ist der Preis?</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.6)' }}>
|
||||
DER BAYERISCHE<br />MITTELSTANDS<wbr />PREIS
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
{/* Decorative label – desktop only */}
|
||||
{!isMobile && (
|
||||
<div style={{ position: 'absolute', bottom: 36, left: 36 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', color: GOLD, marginBottom: 6 }}>BMP Preisverleihung · München</div>
|
||||
<div style={{ width: 28, height: 2, background: GOLD }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 2b. BEDEUTUNG FÜR DEN MITTELSTAND (navy, image left / text right) ── */}
|
||||
<section id="mittelstand" style={{ overflow: 'hidden' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '45% 55%', minHeight: isMobile ? 'auto' : 580 }}>
|
||||
|
||||
{/* Left – full-bleed image (+ heading on mobile) */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined }}>
|
||||
<img
|
||||
src="/images/networking-innenhof.jpg"
|
||||
alt="Bayerischer Mittelstand"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||
? 'linear-gradient(to top, rgba(3,9,58,0.94) 0%, rgba(3,9,58,0.55) 38%, rgba(3,9,58,0.12) 72%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.5) 0%, transparent 55%)'
|
||||
: `linear-gradient(to left, ${NAVY} 0%, transparent 30%)` }} />
|
||||
{!isMobile && (
|
||||
<div style={{ position: 'absolute', top: 36, left: 36 }}>
|
||||
<div style={{ width: 2, height: 40, background: GOLD }} />
|
||||
</div>
|
||||
)}
|
||||
{/* Heading overlay – mobile only */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 24px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Relevanz & Zielsetzung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.6)' }}>
|
||||
BEDEUTUNG FÜR<br />DEN MITTELSTAND
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right – text on navy */}
|
||||
<div style={{ background: NAVY, padding: isMobile ? '32px 24px 40px' : '88px 80px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 20 }}>Relevanz & Zielsetzung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.2vw, 2.9rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 20px' }}>
|
||||
BEDEUTUNG FÜR<br />DEN MITTELSTAND
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.8, maxWidth: 440, marginBottom: 16 }}>
|
||||
Der Bayerische Mittelstandspreis ist weit mehr als eine Trophäe. Seit seiner Gründung im Jahr 2005 verfolgt er das Ziel, die Vielfalt und Leistungsstärke bayerischer Familienunternehmen und KMU sichtbar zu machen.
|
||||
</p>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.8, maxWidth: 440, marginBottom: 40 }}>
|
||||
In einer globalisierten Welt sind es diese Unternehmen, die für Stabilität, Innovation und soziale Verantwortung stehen. Der BMP macht sie sichtbar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 3. PREISTRÄGER-HIGHLIGHTS ─────────────────────────────────────────── */}
|
||||
<section style={{ background: '#111D55' }}>
|
||||
{/* Section header */}
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '72px 80px 48px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'flex-end', gap: isMobile ? 16 : 0, justifyContent: 'space-between', maxWidth: 1600, margin: '0 auto' }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04', display: 'block', marginBottom: 12 }}>Preisträger-Highlights</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: 0, lineHeight: 1 }}>
|
||||
AUSGEZEICHNETE 2025
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
to="/preistraeger"
|
||||
style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: GOLD, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: `1px solid ${GOLD}`, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
Alle Preisträger <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 4-col photo grid */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? undefined : 'repeat(4, 1fr)',
|
||||
gridAutoFlow: isMobile ? 'column' : undefined,
|
||||
gridTemplateRows: isMobile ? 'repeat(2, 1fr)' : undefined,
|
||||
gridAutoColumns: isMobile ? '66vw' : undefined,
|
||||
overflowX: isMobile ? 'auto' : undefined,
|
||||
scrollSnapType: isMobile ? 'x mandatory' : undefined,
|
||||
gap: isMobile ? 8 : 0,
|
||||
padding: isMobile ? '0 16px 8px' : 0,
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'none',
|
||||
}}>
|
||||
{HIGHLIGHTS.map(w => <HighlightCard key={w.id} winner={w} />)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 4. TESTIMONIALS ──────────────────────────────────────────────────── */}
|
||||
<TestimonialsSection />
|
||||
|
||||
|
||||
{/* ── 5. GESCHICHTE & ZIELSETZUNG ──────────────────────────────────────── */}
|
||||
<section id="geschichte" style={{ overflow: 'hidden' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', minHeight: isMobile ? 'auto' : 700 }}>
|
||||
|
||||
{/* LEFT – navy, timeline */}
|
||||
<div style={{ background: NAVY, padding: isMobile ? '56px 24px' : '88px 80px', position: 'relative', overflow: 'hidden' }}>
|
||||
{/* Ghost "20" watermark – desktop only */}
|
||||
{!isMobile && <div style={{ position: 'absolute', right: -24, bottom: -40, fontFamily: FF, fontSize: 'clamp(160px, 20vw, 240px)', fontWeight: 900, color: 'rgba(239,191,4,0.04)', lineHeight: 1, userSelect: 'none', pointerEvents: 'none', letterSpacing: '-0.04em' }}>20</div>}
|
||||
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Geschichte & Meilensteine</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 1.0, margin: '0 0 52px' }}>
|
||||
20 JAHRE<br /><span style={{ color: GOLD }}>MITTELSTANDS</span><wbr />EXZELLENZ
|
||||
</h2>
|
||||
|
||||
{/* Timeline */}
|
||||
<div style={{ position: 'relative', paddingLeft: 28 }}>
|
||||
{/* Vertical gold line */}
|
||||
<div style={{ position: 'absolute', left: 0, top: 6, bottom: 6, width: 2, background: `linear-gradient(to bottom, ${GOLD}, rgba(239,191,4,0.15))` }} />
|
||||
|
||||
{[
|
||||
{ year: '2005', title: 'Gründung', desc: 'Initiative bayerischer Wirtschaftsvertreter zur Stärkung des Mittelstands.' },
|
||||
{ year: '2010', title: 'Erste mediale Partnerschaft', desc: 'Kooperation mit bayernweiten TV- und Printmedien.' },
|
||||
{ year: '2015', title: '100 Preisträger', desc: 'Jubiläum: 100 ausgezeichnete Unternehmen seit der ersten Verleihung.' },
|
||||
{ year: '2020', title: 'Digital-Transformation', desc: 'Erweiterung um digitale Bewerbungsprozesse und Online-Formate.' },
|
||||
{ year: '2025', title: 'Jubiläumsjahr', desc: '20 Jahre BMP: stärker, sichtbarer und vernetzter denn je.' },
|
||||
{ year: '2026', title: 'Bavarian Future Award', desc: 'Ergänzung des Portfolios um den Bavarian Future Award mit Fokus auf zukunftsweisende Unternehmen.' },
|
||||
].map((item, i, arr) => (
|
||||
<div key={i} style={{ position: 'relative', marginBottom: i < arr.length - 1 ? 32 : 0 }}>
|
||||
{/* Gold dot */}
|
||||
<div style={{ position: 'absolute', left: -34, top: 7, width: 10, height: 10, background: GOLD, outline: `3px solid ${NAVY}`, outlineOffset: 1, transform: 'rotate(45deg)' }} />
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(1.05rem, 1.6vw, 1.35rem)', fontWeight: 900, color: GOLD, letterSpacing: '-0.02em', lineHeight: 1, marginBottom: 4 }}>{item.year}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 5 }}>{item.title}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.38)', lineHeight: 1.65 }}>{item.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT – cream, Zielsetzung */}
|
||||
<div style={{ background: '#EFE5E3', padding: isMobile ? '56px 24px' : '88px 80px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Zielsetzung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.8rem)', fontWeight: 900, color: NAVY, textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 1.0, margin: '0 0 40px' }}>
|
||||
WOFÜR WIR<br />STEHEN
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
{[
|
||||
{ n: '01', title: 'Sichtbarkeit', desc: 'Die Vielfalt und Leistungsstärke des bayerischen Mittelstands in der Öffentlichkeit sichtbar machen.' },
|
||||
{ n: '02', title: 'Netzwerk', desc: 'Branchenübergreifende Verbindungen zwischen herausragenden Unternehmen und Entscheidern schaffen.' },
|
||||
{ n: '03', title: 'Werte', desc: 'Wertebasierte Führung, nachhaltige Unternehmenskultur und gesellschaftliche Verantwortung fördern.' },
|
||||
{ n: '04', title: 'Arbeitgebermarke', desc: 'Den Mittelstand als attraktiven Arbeitgeber stärken und Talente für Bayern gewinnen.' },
|
||||
{ n: '05', title: 'Resilienz', desc: 'Die Widerstandsfähigkeit bayerischer Unternehmen würdigen, die auch in unsicheren Zeiten Stabilität und Verantwortung beweisen.' },
|
||||
].map((item, i, arr) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{ display: 'flex', gap: 24, alignItems: 'flex-start', padding: '24px 0', borderTop: '1px solid rgba(17,29,85,0.1)', borderBottom: i === arr.length - 1 ? '1px solid rgba(17,29,85,0.1)' : 'none', cursor: 'default' }}
|
||||
>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.12em', paddingTop: 3, flexShrink: 0, minWidth: 24 }}>{item.n}</span>
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 800, color: NAVY, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 7 }}>{item.title}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 16, color: 'rgba(17,29,85,0.5)', lineHeight: 1.75 }}>{item.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 5. VORTEILE & WERTE ──────────────────────────────────────────────── */}
|
||||
<section id="vorteile" style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
{/* Header */}
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '88px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 16 : 40, alignItems: 'flex-end', borderBottom: '1px solid #D0D5DD' }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Vorteile & Werte</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.2vw, 2.9rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: 0 }}>
|
||||
WARUM DER<br />BMP ZÄHLT
|
||||
</h2>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.8, margin: 0, maxWidth: 440 }}>
|
||||
Drei Dimensionen, die den Unterschied machen: für Ihr Unternehmen, Ihre Mitarbeitenden und Ihren Marktauftritt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Editorial rows – no cards, pure typography */}
|
||||
{[
|
||||
{
|
||||
num: '01',
|
||||
title: 'Exzellenz-Siegel',
|
||||
},
|
||||
{
|
||||
num: '02',
|
||||
title: 'Partner-Netzwerk',
|
||||
},
|
||||
{
|
||||
num: '03',
|
||||
title: 'Reichweite',
|
||||
},
|
||||
].map((item, i, arr) => (
|
||||
<ValueRow key={i} item={item} last={i === arr.length - 1} />
|
||||
))}
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 5. CTA ───────────────────────────────────────────────────────────── */}
|
||||
<section style={{ background: NAVY, padding: isMobile ? '48px 24px' : '100px 80px', position: 'relative', overflow: 'hidden' }}>
|
||||
{/* Gold decorative lines */}
|
||||
<div style={{ position: 'absolute', top: 0, left: '50%', transform: 'translateX(-50%)', width: '60%', height: 1, background: `linear-gradient(to right, transparent, ${GOLD}, transparent)`, opacity: 0.3 }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translateX(-50%)', width: '40%', height: 1, background: `linear-gradient(to right, transparent, ${GOLD}, transparent)`, opacity: 0.15 }} />
|
||||
|
||||
<div style={{ textAlign: 'center', position: 'relative', zIndex: 1, maxWidth: 700, margin: '0 auto' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
|
||||
<div style={{ width: 28, height: 1, background: GOLD, opacity: 0.6 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04' }}>Starten Sie Ihre Reise</span>
|
||||
<div style={{ width: 28, height: 1, background: GOLD, opacity: 0.6 }} />
|
||||
</div>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2.2rem, 5vw, 3.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1, margin: '0 0 24px' }}>
|
||||
SCHREIBEN AUCH SIE GESCHICHTE
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.55)', lineHeight: 1.8, marginBottom: 48, fontWeight: 300 }}>
|
||||
Werden Sie Teil der Wall of Fame des bayerischen Mittelstands. Die Teilnahmegebühr beträgt 0 EUR und die Teilnahme lohnt sich in jeder Phase.
|
||||
</p>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '16px 40px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Jetzt kostenlos bewerben <ChevronRight size={16} />
|
||||
</Link>
|
||||
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'center' }}>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 11, color: 'rgba(255,255,255,0.3)', marginRight: 8 }}>Oder:</span>
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
style={{
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(239,191,4,0.7)',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
borderBottom: '1px solid rgba(239,191,4,0.3)',
|
||||
paddingBottom: 1,
|
||||
transition: 'color 0.15s, borderColor 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = '#EFBF04';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = '#EFBF04';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = 'rgba(239,191,4,0.7)';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)';
|
||||
}}
|
||||
>
|
||||
Vereinsmitglied werden <ChevronRight size={10} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── HighlightCard ─────────────────────────────────────────────────────────────
|
||||
|
||||
function HighlightCard({ winner }: { winner: import('@/spa/data/winners').Winner }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Link
|
||||
to={`/preistraeger/${winner.slug}`}
|
||||
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 220 : 280, overflow: 'hidden', scrollSnapAlign: isMobile ? 'start' : undefined }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<img
|
||||
src={winner.img}
|
||||
alt={winner.name}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.6s ease', transform: hovered ? 'scale(1.07)' : 'scale(1)', filter: 'grayscale(15%)' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.1) 55%, transparent 100%)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.2)', opacity: hovered ? 1 : 0, transition: 'opacity 0.25s' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, padding: '18px 20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 5 }}>
|
||||
<Star size={10} style={{ color: GOLD, flexShrink: 0 }} fill={GOLD} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.12em', textTransform: 'uppercase' }}>
|
||||
{winner.year} · {winner.type}
|
||||
</span>
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', margin: '0 0 3px', lineHeight: 1.3, textShadow: '0 1px 4px rgba(0,0,0,0.5)' }}>
|
||||
{winner.name}
|
||||
</h3>
|
||||
<p style={{ fontFamily: FF, fontSize: 14, color: 'rgba(255,255,255,0.75)', margin: 0, letterSpacing: '0.03em' }}>
|
||||
{winner.category}
|
||||
</p>
|
||||
</div>
|
||||
{/* hover pill */}
|
||||
<div style={{ position: 'absolute', bottom: 16, right: 16, background: GOLD, color: '#101828', padding: '6px 14px', borderRadius: 999, fontSize: 10, fontFamily: FF, fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: hovered ? 1 : 0, transform: hovered ? 'translateY(0)' : 'translateY(6px)', transition: 'all 0.25s' }}>
|
||||
Detail →
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// ── ValueRow ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function ValueRow({ item, last }: { item: { num: string; title: string }; last: boolean }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileRow = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid', gridTemplateColumns: isMobileRow ? '1fr' : '120px 1fr', gap: isMobileRow ? '16px 0' : '0 48px',
|
||||
padding: isMobileRow ? '32px 24px' : '48px 80px',
|
||||
borderBottom: last ? 'none' : '1px solid #D0D5DD',
|
||||
background: hovered ? '#EAE7E6' : 'transparent',
|
||||
transition: 'background 0.2s', cursor: 'default',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{/* Left: number */}
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.8rem)', fontWeight: 900, color: hovered ? GOLD : 'rgba(16,24,40,0.1)', lineHeight: 1, letterSpacing: '-0.04em', transition: 'color 0.2s' }}>{item.num}</div>
|
||||
</div>
|
||||
{/* Right: title (short label only) */}
|
||||
<div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.8rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: 0 }}>{item.title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About;
|
||||
300
src/spa/pages/BlogDetail.tsx
Normal file
300
src/spa/pages/BlogDetail.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
import React from 'react';
|
||||
import { useParams, Link } from '@/spa/router';
|
||||
import { ArrowLeft, Clock, User, Tag, ArrowRight } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getArticleBySlug, articles } from '@/spa/data/blog';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#EFE5E3';
|
||||
const INK = '#3A3A3A';
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
|
||||
const CAT_COLORS: Record<string, string> = {
|
||||
Innovation: '#2563EB',
|
||||
Engagement: '#16A34A',
|
||||
Wirtschaft: '#9333EA',
|
||||
};
|
||||
|
||||
export default function BlogDetail() {
|
||||
const isMobile = useIsMobile();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const article = slug ? getArticleBySlug(slug) : undefined;
|
||||
|
||||
if (!article) {
|
||||
return (
|
||||
<div style={{ minHeight: '60vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 24, padding: '80px 24px', background: CREAM }}>
|
||||
<h2 style={{ fontFamily: FF, fontSize: '2rem', fontWeight: 900, color: NAVY }}>Artikel nicht gefunden</h2>
|
||||
<Link to="/presse" style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<ArrowLeft size={14} /> Zurück zur Presse
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const related = article.relatedSlugs
|
||||
.map(s => articles.find(a => a.slug === s))
|
||||
.filter(Boolean) as typeof articles;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
{/* ── HERO ──────────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', height: isMobile ? 'auto' : '70vh', minHeight: 520, overflow: 'hidden', display: 'flex', alignItems: 'flex-end' }}>
|
||||
<img
|
||||
src={article.img}
|
||||
alt={article.title}
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%' }}
|
||||
/>
|
||||
{/* Gradient overlay */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, #111D55 0%, rgba(3,9,58,0.75) 45%, rgba(3,9,58,0.1) 100%)' }} />
|
||||
{/* Gold bottom line */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 3, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)` }} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 2, padding: isMobile ? '0 24px 48px' : '0 80px 72px', maxWidth: 860 }}>
|
||||
{/* Back link */}
|
||||
<Link
|
||||
to="/presse"
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
fontFamily: FF, fontSize: 11, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.14em',
|
||||
color: 'rgba(255,255,255,0.5)', textDecoration: 'none',
|
||||
marginBottom: 32, transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.5)')}
|
||||
>
|
||||
<ArrowLeft size={13} /> Newsroom
|
||||
</Link>
|
||||
|
||||
{/* Category badge */}
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
padding: '4px 12px',
|
||||
fontFamily: FF, fontSize: 9, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.22em',
|
||||
background: CAT_COLORS[article.cat] ?? GOLD,
|
||||
color: '#fff',
|
||||
}}>
|
||||
{article.cat}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: isMobile ? 'clamp(1.6rem, 6vw, 2.4rem)' : 'clamp(2rem, 5vw, 3.8rem)',
|
||||
fontWeight: 900,
|
||||
color: '#fff',
|
||||
lineHeight: 1.05,
|
||||
letterSpacing: '-0.03em',
|
||||
textTransform: 'uppercase',
|
||||
margin: '0 0 28px',
|
||||
maxWidth: 760,
|
||||
}}
|
||||
>
|
||||
{article.title}
|
||||
</motion.h1>
|
||||
|
||||
{/* Meta row */}
|
||||
<div style={{ display: 'flex', gap: 28, flexWrap: 'wrap' }}>
|
||||
{[
|
||||
{ icon: User, label: `${article.author} · ${article.authorRole}` },
|
||||
{ icon: Clock, label: `${article.date} · ${article.readTime} Lesezeit` },
|
||||
].map(({ icon: Icon, label }, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.5)' }}>
|
||||
<Icon size={13} color={GOLD} /> {label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── ARTICLE BODY ─────────────────────────────────────────────────── */}
|
||||
<section style={{ background: CREAM, padding: isMobile ? '48px 0 0' : '80px 0 0' }}>
|
||||
<div style={{ maxWidth: 1100, margin: '0 auto', padding: isMobile ? '0 24px' : '0 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 320px', gap: isMobile ? 40 : 80 }}>
|
||||
|
||||
{/* LEFT – article text */}
|
||||
<article>
|
||||
{/* Lead */}
|
||||
<p style={{
|
||||
fontFamily: FB, fontSize: 19, fontWeight: 300,
|
||||
color: INK, lineHeight: 1.75,
|
||||
borderLeft: `4px solid ${GOLD}`, paddingLeft: 28,
|
||||
marginBottom: 56,
|
||||
}}>
|
||||
{article.excerpt}
|
||||
</p>
|
||||
|
||||
{/* Sections */}
|
||||
{article.sections.map((section, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: idx * 0.04 }}
|
||||
style={{ marginBottom: 48 }}
|
||||
>
|
||||
{section.heading && (
|
||||
<h2 style={{
|
||||
fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.35rem)',
|
||||
fontWeight: 900, color: NAVY,
|
||||
textTransform: 'uppercase', letterSpacing: '-0.01em',
|
||||
marginBottom: 16,
|
||||
}}>
|
||||
{section.heading}
|
||||
</h2>
|
||||
)}
|
||||
<p style={{
|
||||
fontFamily: FB, fontSize: 18,
|
||||
color: 'rgba(58,58,58,0.8)', lineHeight: 1.8, margin: 0,
|
||||
}}>
|
||||
{section.body}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{/* Tags */}
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', padding: '40px 0', borderTop: '1px solid rgba(3,9,58,0.1)', marginTop: 24 }}>
|
||||
<Tag size={13} color='rgba(3,9,58,0.35)' style={{ marginTop: 2 }} />
|
||||
{article.tags.map(tag => (
|
||||
<span key={tag} style={{
|
||||
padding: '4px 12px',
|
||||
fontFamily: FF, fontSize: 10, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.12em',
|
||||
color: NAVY, background: 'rgba(3,9,58,0.07)',
|
||||
}}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{/* RIGHT – sticky sidebar */}
|
||||
<aside>
|
||||
<div style={{ position: isMobile ? 'static' : 'sticky', top: isMobile ? undefined : 100 }}>
|
||||
|
||||
{/* Author card */}
|
||||
<div style={{ background: NAVY, padding: '28px 28px 32px', marginBottom: 24 }}>
|
||||
<div style={{ width: 48, height: 48, background: GOLD, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}>
|
||||
<User size={22} color={NAVY} />
|
||||
</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', marginBottom: 4 }}>
|
||||
{article.author}
|
||||
</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.45)', lineHeight: 1.6 }}>
|
||||
{article.authorRole}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div style={{ background: '#fff', border: '1px solid rgba(3,9,58,0.1)', padding: '28px' }}>
|
||||
<div style={{ width: 32, height: 2, background: GOLD, marginBottom: 16 }} />
|
||||
<h4 style={{ fontFamily: FF, fontSize: 15, fontWeight: 900, color: NAVY, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 12 }}>
|
||||
Jetzt bewerben
|
||||
</h4>
|
||||
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(58,58,58,0.55)', lineHeight: 1.7, marginBottom: 20 }}>
|
||||
Ist Ihr Unternehmen bereit für den Bayerischen Mittelstandspreis 2026?
|
||||
</p>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||
padding: '13px 20px',
|
||||
background: NAVY, color: '#fff',
|
||||
fontFamily: FF, fontSize: 14, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
textDecoration: 'none',
|
||||
transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = GOLD)}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = NAVY)}
|
||||
>
|
||||
Zur Bewerbung <ArrowRight size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── RELATED ARTICLES ─────────────────────────────────────────────── */}
|
||||
{related.length > 0 && (
|
||||
<section style={{ background: NAVY, padding: isMobile ? '48px 0' : '80px 0' }}>
|
||||
<div style={{ maxWidth: 1100, margin: '0 auto', padding: isMobile ? '0 24px' : '0 80px' }}>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 20, marginBottom: 56 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)' }}>
|
||||
Weitere Artikel
|
||||
</span>
|
||||
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,0.1)' }} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : `repeat(${related.length}, 1fr)`, gap: 0 }}>
|
||||
{related.map((rel, idx) => (
|
||||
<RelatedCard key={rel.slug} article={rel} bordered={isMobile ? false : idx > 0} isMobile={isMobile} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RelatedCard({ article, bordered, isMobile }: { article: ReturnType<typeof getArticleBySlug> & object; bordered: boolean; isMobile: boolean }) {
|
||||
const [hovered, setHovered] = React.useState(false);
|
||||
if (!article) return null;
|
||||
return (
|
||||
<Link
|
||||
to={`/presse/blog/${article.slug}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{
|
||||
display: 'flex', flexDirection: 'column',
|
||||
borderLeft: bordered && !isMobile ? '1px solid rgba(255,255,255,0.07)' : 'none',
|
||||
borderBottom: 'none',
|
||||
textDecoration: 'none',
|
||||
paddingLeft: bordered && !isMobile ? 40 : 0,
|
||||
paddingRight: isMobile ? 0 : 40,
|
||||
paddingBottom: isMobile ? 40 : 0,
|
||||
}}
|
||||
>
|
||||
{/* Thumbnail */}
|
||||
<div style={{ height: 180, overflow: 'hidden', marginBottom: 24 }}>
|
||||
<img
|
||||
src={article.img}
|
||||
alt={article.title}
|
||||
style={{
|
||||
width: '100%', height: '100%', objectFit: 'cover',
|
||||
filter: hovered ? 'none' : 'grayscale(80%)',
|
||||
transform: hovered ? 'scale(1.04)' : 'scale(1)',
|
||||
transition: 'filter 0.4s, transform 0.4s',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
|
||||
{article.cat}
|
||||
</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1rem, 1.4vw, 1.15rem)', fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.01em', lineHeight: 1.25, marginBottom: 12 }}>
|
||||
{article.title}
|
||||
</h3>
|
||||
<div style={{ width: hovered ? 64 : 24, height: 1, background: 'rgba(239,191,4,0.5)', transition: 'width 0.35s', marginBottom: 14 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.38)', lineHeight: 1.7, flex: 1, marginBottom: 16 }}>
|
||||
{article.excerpt}
|
||||
</p>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
|
||||
Weiterlesen <ArrowRight size={12} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
279
src/spa/pages/Contact.tsx
Normal file
279
src/spa/pages/Contact.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import React, { useState } from 'react';
|
||||
import { MapPin, Mail, Phone, Clock, ChevronDown } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import KontaktForm from '@/spa/components/forms/KontaktForm';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#EFE5E3';
|
||||
const INK = '#3A3A3A';
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
q: 'Was kostet die Teilnahme am BMP?',
|
||||
a: 'Die Teilnahme am Bayerischen Mittelstandspreis ist für alle Unternehmen vollständig kostenfrei. Wir finanzieren uns durch Partner und Sponsoren, um exzellenten Mittelstand ohne finanzielle Hürden zu fördern.',
|
||||
},
|
||||
{
|
||||
q: 'Nach welchen Kriterien wird bewertet?',
|
||||
a: 'Die Bewertung basiert auf vier Säulen: Zukunftsfähigkeit & Innovation, Nachhaltigkeit & Verantwortung, Unternehmenskultur sowie Regionale Verwurzelung. Jede Säule fließt mit 25 % in das Gesamtergebnis ein.',
|
||||
},
|
||||
{
|
||||
q: 'Wie läuft der Bewerbungsprozess ab?',
|
||||
a: 'Der Prozess ist mehrstufig: 1. Online-Bewerbung oder Vorschlag, 2. Formale Prüfung & erste Jury-Sichtung, 3. Fach-Audits bei den Finalisten vor Ort, 4. Finale Jury-Sitzung und abschließende Preisverleihung.',
|
||||
},
|
||||
{
|
||||
q: 'Wie lange dauert die Bewerbung?',
|
||||
a: 'Dank unseres optimierten Bewerbungsformulars benötigen Sie für die erste Einreichung ca. 30–45 Minuten. Tiefergehende Unterlagen werden erst in der Audit-Phase angefordert.',
|
||||
},
|
||||
];
|
||||
|
||||
const deadlines = [
|
||||
{ step: '01', label: 'Bewerbungsschluss', date: '30. Juni 2026', desc: 'Letzte Möglichkeit zur Einreichung Ihrer Unterlagen.' },
|
||||
{ step: '02', label: 'Nominierung', date: '15. August 2026', desc: 'Bekanntgabe der Nominierten nach Jury-Sichtung.' },
|
||||
{ step: '03', label: 'Preisverleihung', date: '22. Oktober 2026', desc: 'Festliche Gala in München.' },
|
||||
];
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
const [openFaq, setOpenFaq] = useState<number | null>(0);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div style={{ background: CREAM }}>
|
||||
|
||||
{/* ── Hero ───────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', height: '68vh', minHeight: 520, overflow: 'hidden', display: 'flex', alignItems: 'flex-end' }}>
|
||||
<img
|
||||
src="/images/gala-dinner.jpg"
|
||||
alt="BMP Gala"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 40%' }}
|
||||
/>
|
||||
{/* Diagonal overlay */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: `linear-gradient(105deg, ${NAVY} 0%, rgba(3,9,58,0.82) 35%, rgba(3,9,58,0.2) 70%, transparent 100%)` }} />
|
||||
{/* Bottom fade to navy contact section */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 160, background: `linear-gradient(to top, ${NAVY}, transparent)` }} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 2, width: '100%', paddingBottom: isMobile ? '3rem' : '5rem', paddingLeft: isMobile ? 24 : 80, paddingRight: isMobile ? 24 : 80 }}>
|
||||
<p style={{ fontSize: 11, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: GOLD, marginBottom: 16 }}>
|
||||
Dialog & Service
|
||||
</p>
|
||||
<h1 style={{ fontSize: isMobile ? 'clamp(2rem, 10vw, 3rem)' : 'clamp(3rem, 7vw, 6rem)', fontWeight: 900, lineHeight: 1.0, color: '#fff', letterSpacing: '-0.03em', marginBottom: 24, maxWidth: 640 }}>
|
||||
Wir sind<br />für Sie da.
|
||||
</h1>
|
||||
<p style={{ fontSize: 18, color: 'rgba(255,255,255,0.72)', maxWidth: 480, lineHeight: 1.65, fontWeight: 400 }}>
|
||||
Fragen zur Bewerbung, zur Gala oder zu Partnermöglichkeiten – unser Team begleitet Sie persönlich.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Contact + Form ─────────────────────────────────────────────── */}
|
||||
<section style={{ background: NAVY, position: 'relative', overflow: 'hidden' }}>
|
||||
{/* Decorative circles */}
|
||||
<div style={{ position: 'absolute', left: -120, bottom: -120, width: 480, height: 480, borderRadius: '50%', background: 'rgba(5,26,135,0.12)', pointerEvents: 'none' }} />
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr' }}>
|
||||
|
||||
{/* Left – info panel */}
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '88px 56px 88px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative' }}>
|
||||
<p style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 20 }}>
|
||||
Direktkontakt
|
||||
</p>
|
||||
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 20 }} />
|
||||
<h2 style={{ fontSize: 'clamp(1.6rem, 3vw, 2.4rem)', fontWeight: 900, color: '#fff', lineHeight: 1.15, letterSpacing: '-0.03em', marginBottom: 52 }}>
|
||||
Kontakt­möglichkeiten
|
||||
</h2>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
{[
|
||||
{ icon: MapPin, title: 'Geschäftsstelle', lines: ['Maximilianstraße 42', '80538 München'] },
|
||||
{ icon: Mail, title: 'E-Mail', lines: ['info@bmp-bayern.de'] },
|
||||
{ icon: Phone, title: 'Telefon', lines: ['+49 89 123 456 78'] },
|
||||
{ icon: Clock, title: 'Bürozeiten', lines: ['Mo – Fr: 09:00 – 17:00 Uhr'] },
|
||||
].map(({ icon: Icon, title, lines }, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: 18, alignItems: 'flex-start' }}>
|
||||
<div style={{ width: 40, height: 40, borderRadius: 8, background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.08)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<Icon size={17} color="rgba(255,255,255,0.80)" />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'rgba(255,255,255,0.40)', textTransform: 'uppercase', letterSpacing: '0.14em', marginBottom: 4 }}>{title}</div>
|
||||
{lines.map((l, j) => <div key={j} style={{ fontSize: 17, color: '#fff', fontWeight: 500, lineHeight: 1.5 }}>{l}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 52, paddingTop: 28, borderTop: '1px solid rgba(255,255,255,0.10)' }}>
|
||||
<p style={{ fontSize: 15, color: 'rgba(255,255,255,0.40)', lineHeight: 1.65 }}>
|
||||
Nächste Preisverleihung: <strong style={{ color: '#fff' }}>22. Oktober 2026</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
{!isMobile && <div style={{ background: 'rgba(255,255,255,0.07)' }} />}
|
||||
|
||||
{/* Right – gold form panel */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: isMobile ? 'visible' : 'hidden', height: isMobile ? 'auto' : undefined, minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
<svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }} aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="kontaktCross" width="18" height="18" patternUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L18,18 M18,0 L0,18" stroke="rgba(17,29,85,0.055)" strokeWidth="0.65" />
|
||||
</pattern>
|
||||
<pattern id="kontaktDiamond" width="36" height="36" patternUnits="userSpaceOnUse">
|
||||
<polygon points="18,2 34,18 18,34 2,18" fill="none" stroke="rgba(17,29,85,0.045)" strokeWidth="0.6" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#kontaktCross)" />
|
||||
<rect width="100%" height="100%" fill="url(#kontaktDiamond)" />
|
||||
</svg>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, transparent 55%, rgba(100,70,0,0.25) 100%)', pointerEvents: 'none', zIndex: 0 }} />
|
||||
|
||||
{!isMobile && (
|
||||
<div style={{ height: 200, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
|
||||
<img src="/images/preistraeger-gruppe.jpg" alt="BMP Gala 2025" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 14, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>Gala 2025 München</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ padding: isMobile ? '36px 20px' : '32px 48px 32px 40px', flex: isMobile ? 'none' : 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 480 : 0, position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>Kontaktformular</span>
|
||||
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', letterSpacing: '-0.025em', textTransform: 'uppercase', lineHeight: 1.1, marginBottom: 24 }}>
|
||||
Schreiben Sie uns
|
||||
</h3>
|
||||
<KontaktForm theme="gold" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Deadlines ──────────────────────────────────────────────────── */}
|
||||
<section style={{ background: '#0D1640', padding: isMobile ? '56px 0' : '88px 0', position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', right: -20, top: '50%', transform: 'translateY(-50%)', fontSize: 'clamp(160px, 22vw, 280px)', fontWeight: 900, color: '#fff', opacity: 0.025, lineHeight: 1, pointerEvents: 'none', userSelect: 'none', letterSpacing: '-0.05em' }}>
|
||||
2026
|
||||
</div>
|
||||
<div className="container mx-auto px-6" style={{ position: 'relative' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 20, marginBottom: isMobile ? 48 : 72 }}>
|
||||
<span style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: '#EFBF04', whiteSpace: 'nowrap' }}>
|
||||
Fahrplan 2026
|
||||
</span>
|
||||
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,0.10)' }} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 0 }}>
|
||||
{deadlines.map((item, i) => (
|
||||
<div key={i} style={isMobile ? {
|
||||
paddingTop: i > 0 ? 36 : 0,
|
||||
paddingBottom: i < deadlines.length - 1 ? 36 : 0,
|
||||
borderBottom: i < deadlines.length - 1 ? '1px solid rgba(255,255,255,0.10)' : 'none',
|
||||
} : {
|
||||
paddingRight: i < deadlines.length - 1 ? 48 : 0,
|
||||
paddingLeft: i > 0 ? 48 : 0,
|
||||
borderRight: i < deadlines.length - 1 ? '1px solid rgba(255,255,255,0.10)' : 'none',
|
||||
}}>
|
||||
<div style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.2em', color: `rgba(239,191,4,0.5)`, marginBottom: 18, fontVariantNumeric: 'tabular-nums' }}>
|
||||
{item.step}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'rgba(255,255,255,0.45)', marginBottom: 12 }}>
|
||||
{item.label}
|
||||
</div>
|
||||
<div style={{ fontSize: 'clamp(1.4rem, 2.5vw, 2rem)', fontWeight: 900, color: '#fff', letterSpacing: '-0.03em', lineHeight: 1.1, marginBottom: 16 }}>
|
||||
{item.date}
|
||||
</div>
|
||||
<div style={{ fontSize: 16, color: 'rgba(255,255,255,0.45)', lineHeight: 1.7 }}>
|
||||
{item.desc}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── FAQ ────────────────────────────────────────────────────────── */}
|
||||
<section id="faq" style={{ background: CREAM, padding: isMobile ? '56px 0 72px' : '96px 0 112px' }}>
|
||||
<div className="container mx-auto px-6">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 2fr', gap: isMobile ? 40 : 80 }}>
|
||||
|
||||
{/* Left – sticky label */}
|
||||
<div style={isMobile ? undefined : { position: 'sticky', top: 96, alignSelf: 'start' }}>
|
||||
<p style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: '#4A8FC9', marginBottom: 20 }}>
|
||||
Antworten
|
||||
</p>
|
||||
<div style={{ width: 28, height: 2, background: GOLD, marginBottom: 20 }} />
|
||||
<h2 style={{ fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.4rem)' : 'clamp(2rem, 3.5vw, 2.8rem)', fontWeight: 900, color: NAVY, letterSpacing: '-0.03em', lineHeight: 1.1 }}>
|
||||
Häufige<br />Fragen
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Right – accordion */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{faqs.map((faq, idx) => {
|
||||
const isOpen = openFaq === idx;
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
style={{
|
||||
background: isOpen ? '#fff' : 'transparent',
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
transition: 'background 0.2s',
|
||||
border: isOpen ? `1.5px solid rgba(17,29,85,0.12)` : '1.5px solid rgba(17,29,85,0.08)',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setOpenFaq(isOpen ? null : idx)}
|
||||
style={{
|
||||
width: '100%', padding: '22px 24px',
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
background: 'none', border: 'none', cursor: 'pointer', textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 17, fontWeight: 700, color: isOpen ? NAVY : INK, flex: 1, paddingRight: 20, lineHeight: 1.4 }}>
|
||||
{faq.q}
|
||||
</span>
|
||||
<div style={{
|
||||
width: 30, height: 30, borderRadius: '50%', flexShrink: 0,
|
||||
background: isOpen ? NAVY : 'rgba(17,29,85,0.08)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
transition: 'all 0.2s',
|
||||
}}>
|
||||
<ChevronDown
|
||||
size={15}
|
||||
color={isOpen ? '#fff' : NAVY}
|
||||
style={{ transform: isOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
key="content"
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.22, ease: 'easeInOut' }}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<div style={{ padding: '0 24px 24px', fontSize: 17, color: 'rgba(58,58,58,0.72)', lineHeight: 1.75 }}>
|
||||
{faq.a}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
270
src/spa/pages/Datenschutz.tsx
Normal file
270
src/spa/pages/Datenschutz.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
import React from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const ANTHRA = '#1A2030';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#EFE5E3';
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
|
||||
const TOC = [
|
||||
{ id: 'verantwortlicher', label: 'Verantwortlicher' },
|
||||
{ id: 'datenschutz-allgemein', label: 'Datenschutz & Datenverarbeitung' },
|
||||
{ id: 'kontaktformular', label: 'Kontaktformular & E-Mail' },
|
||||
{ id: 'betroffenenrechte', label: 'Betroffenenrechte' },
|
||||
{ id: 'aufsichtsbehoerde', label: 'Beschwerderecht' },
|
||||
{ id: 'privacy-policy', label: 'Privacy Policy' },
|
||||
{ id: 'recaptcha', label: 'Google reCAPTCHA' },
|
||||
{ id: 'webanalyse', label: 'Webseitenanalyse' },
|
||||
];
|
||||
|
||||
const ADDR = (
|
||||
<>
|
||||
VM Verbands-Management GmbH<br />
|
||||
Edelsbergstr. 8<br />
|
||||
80686 München<br />
|
||||
Tel. +49 (0) 89 – 57007-0<br />
|
||||
Fax +49 (0) 89 – 57007 260<br />
|
||||
info@vm-verbands-management.de<br />
|
||||
HRB 110222 AG München, gesetzl. vertreten durch Friedrich Marx
|
||||
</>
|
||||
);
|
||||
|
||||
export default function Datenschutz() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div style={{ background: CREAM, minHeight: '100vh' }}>
|
||||
|
||||
{/* ── HERO ──────────────────────────────────────────────────────────── */}
|
||||
<section style={{ background: ANTHRA, padding: isMobile ? '48px 24px 40px' : '64px 80px 52px', position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.2), transparent)` }} />
|
||||
<Link
|
||||
to="/"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: 'rgba(255,255,255,0.4)', textDecoration: 'none', marginBottom: 32, transition: 'color 0.15s' }}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.4)')}
|
||||
>
|
||||
<ArrowLeft size={12} /> Zurück zur Website
|
||||
</Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: GOLD }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: GOLD }}>Legal</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontWeight: 900, fontSize: isMobile ? 'clamp(1.8rem, 7vw, 2.6rem)' : 'clamp(2.4rem, 4vw, 3.2rem)', color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.02, margin: '0 0 12px' }}>
|
||||
Datenschutz­erklärung
|
||||
</h1>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.45)', margin: 0 }}>
|
||||
Zuletzt aktualisiert: April 2026 · <Link to="/impressum" style={{ color: 'rgba(239,191,4,0.7)', textDecoration: 'none' }}>Zum Impressum →</Link>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* ── BODY ──────────────────────────────────────────────────────────── */}
|
||||
<div style={{ maxWidth: 1100, margin: '0 auto', padding: isMobile ? '40px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '200px 1fr', gap: isMobile ? 40 : 80, alignItems: 'start' }}>
|
||||
|
||||
{/* LEFT – sticky TOC */}
|
||||
{!isMobile && (
|
||||
<nav style={{ position: 'sticky', top: 80 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: GOLD, marginBottom: 16 }}>Inhalt</div>
|
||||
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{TOC.map(item => (
|
||||
<li key={item.id}>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
style={{ display: 'block', fontFamily: FB, fontSize: 12, color: 'rgba(17,29,85,0.5)', textDecoration: 'none', padding: '5px 0 5px 12px', borderLeft: '2px solid transparent', transition: 'color 0.15s, border-color 0.15s', lineHeight: 1.4 }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = NAVY; (e.currentTarget as HTMLElement).style.borderLeftColor = GOLD; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'rgba(17,29,85,0.5)'; (e.currentTarget as HTMLElement).style.borderLeftColor = 'transparent'; }}
|
||||
>{item.label}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{ marginTop: 32, paddingTop: 20, borderTop: '1px solid rgba(17,29,85,0.1)' }}>
|
||||
<Link to="/impressum" style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.14em', color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
Zum Impressum →
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* RIGHT – content */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 56 }}>
|
||||
|
||||
<Section id="verantwortlicher" title="Verantwortlicher">
|
||||
<p style={body}>Name und Anschrift des für die Verarbeitung Verantwortlichen im Sinne der Datenschutz-Grundverordnung:</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
</Section>
|
||||
|
||||
<Section id="datenschutz-allgemein" title="Datenschutz & Datenverarbeitung">
|
||||
<p style={body}>Alle personenbezogenen Informationen werden vertraulich und gemäß den gesetzlichen Vorschriften behandelt. Den Schutz der persönlichen Daten nehmen wir als Webseitenbetreiber sehr ernst.</p>
|
||||
<p style={body}>Ein Besuch unserer Webseiten ist möglich, ohne Angaben zu Ihrer Person zu machen. Es werden bei jedem Zugriff auf unsere Website Nutzungsdaten durch Ihren Internetbrowser übermittelt und in Protokolldaten (Server-Logfiles) gespeichert. Zu diesen gespeicherten Daten gehören z. B. Name der aufgerufenen Seite, Datum und Uhrzeit des Abrufs, übertragene Datenmenge und der anfragende Provider (IP-Adresse). Diese Daten dienen ausschließlich der Gewährleistung eines störungsfreien Betriebs unserer Website und zur Verbesserung unseres Angebotes. Eine Zuordnung dieser Daten zu einer bestimmten Person ist nicht möglich.</p>
|
||||
<p style={body}>Es erfolgt keine Zusammenführung dieser Daten mit anderen Datenquellen.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="kontaktformular" title="Kontaktformular & E-Mail">
|
||||
<p style={body}>Sofern Sie uns über unsere Kontaktdaten eine E-Mail übersenden bzw. uns über das Kontaktformular informieren, erheben wir Ihre personenbezogenen Daten (Name, E-Mail-Adresse, Telefonnummer, Betreff, Nachrichtentext) nur in dem von Ihnen zur Verfügung gestellten Umfang. Die Datenverarbeitung dient dem Zweck der Kontaktaufnahme.</p>
|
||||
<p style={body}>Mit Absenden Ihrer Nachricht willigen Sie in die Verarbeitung der übermittelten Daten ein.</p>
|
||||
<Legal>Eine Verarbeitung erfolgt auf Grundlage des Art. 6 (1) lit. a DSGVO mit Ihrer Einwilligung.</Legal>
|
||||
<p style={body}>Sie können diese Einwilligung jederzeit durch Mitteilung an uns widerrufen, ohne dass die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung berührt wird. Ihre E-Mail-Adresse wird nur zur Bearbeitung Ihrer Anfrage genutzt. Ihre Daten werden anschließend gelöscht, sofern Sie der weitergehenden Verarbeitung und Nutzung nicht zugestimmt haben.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="betroffenenrechte" title="Betroffenenrechte">
|
||||
<p style={body}>Bei Vorliegen der gesetzlichen Voraussetzungen stehen Ihnen folgende Rechte nach Art. 15 bis 20 DSGVO zu:</p>
|
||||
<ul style={{ ...body, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<li>Recht auf Auskunft</li>
|
||||
<li>Recht auf Berichtigung</li>
|
||||
<li>Recht auf Löschung</li>
|
||||
<li>Recht auf Einschränkung der Verarbeitung</li>
|
||||
<li>Recht auf Datenübertragbarkeit</li>
|
||||
</ul>
|
||||
<Legal>Außerdem steht Ihnen nach Art. 21 (1) DSGVO ein Widerspruchsrecht gegen die Verarbeitungen zu, die auf Art. 6 (1) f DSGVO beruhen, sowie gegen die Verarbeitung zum Zwecke von Direktwerbung.</Legal>
|
||||
<p style={body}>Auf Wunsch können Sie uns kontaktieren. Die Kontaktdaten finden Sie in unserem <Link to="/impressum" style={{ color: NAVY, fontWeight: 600 }}>Impressum</Link> bzw. unter <Link to="/kontakt" style={{ color: NAVY, fontWeight: 600 }}>Kontakt</Link>.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="aufsichtsbehoerde" title="Beschwerderecht bei der Aufsichtsbehörde">
|
||||
<p style={body}>Sie haben gemäß Art. 77 DSGVO das Recht, sich bei der Aufsichtsbehörde zu beschweren, wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer personenbezogenen Daten nicht rechtmäßig erfolgt.</p>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY }}>Die zuständige Aufsichtsbehörde:</p>
|
||||
<AddressBlock>
|
||||
Bayerisches Landesamt für Datenschutzaufsicht<br />
|
||||
Postfach 606<br />
|
||||
91511 Ansbach<br />
|
||||
Deutschland<br />
|
||||
Tel. 09853-1300<br />
|
||||
Fax 09853-981300<br />
|
||||
E-Mail: poststelle@lda.bayern.de
|
||||
</AddressBlock>
|
||||
</Section>
|
||||
|
||||
<Section id="privacy-policy" title="Privacy Policy (Erklärung zum Datenschutz)">
|
||||
<p style={{ ...body, fontStyle: 'italic', borderLeft: `3px solid ${GOLD}`, paddingLeft: 16, marginBottom: 32 }}>
|
||||
Die Betreiber des Portals nehmen den Schutz der privaten Daten ernst. Persönliche Daten werden gemäß den Bestimmungen des Bundesdatenschutzgesetzes BDSG verwendet; die Betreiber verpflichten sich zur Verschwiegenheit. Diese Webseiten können Links zu Webseiten anderer Anbieter enthalten, auf die sich diese Datenschutzerklärung nicht erstreckt.
|
||||
</p>
|
||||
|
||||
<SubSection num="1" title="Personenbezogene Daten">
|
||||
<p style={body}>Personenbezogene Daten sind Informationen, die dazu genutzt werden können, die Identität zu erfahren. Darunter fallen Informationen wie richtiger Name, Adresse, Postanschrift, Telefonnummer. Informationen, die nicht direkt mit der wirklichen Identität in Verbindung gebracht werden, fallen nicht darunter.</p>
|
||||
<p style={body}>Wir nutzen die persönlichen Daten zu Zwecken der technischen Administration der Webseiten und zur Kundenverwaltung nur im jeweils dafür erforderlichen Umfang. Darüber hinaus werden persönliche Daten nur dann gespeichert, wenn diese freiwillig angegeben werden.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="2" title="Weitergabe personenbezogener Daten">
|
||||
<p style={body}>Wir verwenden personenbezogene Informationen nur für diese Webseite. Wir geben die Informationen nicht ohne ausdrückliches Einverständnis an Dritte weiter. Sollten im Rahmen der Auftragsdatenverarbeitung Daten an Dienstleister weitergegeben werden, so sind diese an das BDSG und diese Privacy Policy gebunden.</p>
|
||||
<p style={body}>Erhebungen bzw. Übermittlungen persönlicher Daten an staatliche Einrichtungen und Behörden erfolgen nur im Rahmen zwingender Rechtsvorschriften.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="3" title="Kinder">
|
||||
<p style={body}>Personen unter 18 Jahren sollten ohne Zustimmung der Eltern oder Erziehungsberechtigten keine personenbezogenen Daten an uns übermitteln. Wir fordern keine personenbezogenen Daten von Kindern an, sammeln diese nicht und geben sie nicht an Dritte weiter.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="4" title="Recht auf Widerruf">
|
||||
<p style={body}>Wenn Sie uns personenbezogene Daten überlassen haben, können Sie diese jederzeit wieder ändern und löschen lassen. Für eine vollständige Löschung wenden Sie sich bitte an den Webmaster.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="5" title="Links zu anderen Websites">
|
||||
<p style={body}>Unser Online-Angebot enthält Links zu anderen Websites. Wir haben keinen Einfluss darauf, dass deren Betreiber die Datenschutzbestimmungen einhalten.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="6" title="Beiträge">
|
||||
<p style={body}>Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren Betreiber verantwortlich. Alle hier verwendeten Namen, Begriffe, Zeichen und Grafiken können Marken- oder Warenzeichen im Besitze ihrer rechtlichen Eigentümer sein.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="7" title="Fragen und Kommentare">
|
||||
<p style={body}>Bei Fragen und für Anregungen zum Thema Datenschutz bitte per Mail an den Webmaster wenden.</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
<Section id="recaptcha" title="Spamschutz mit Google reCAPTCHA">
|
||||
<p style={body}>Wir haben Google reCAPTCHA in Formularen auf unserer Webseite implementiert, um zu überprüfen, ob die in den Formularen eingegebenen Daten von menschlichen Besuchern stammen oder von Maschinen bzw. automatisierten Programmen („Bots").</p>
|
||||
<p style={body}>Dieses Tool analysiert automatisch das Verhalten der Webseitenbesucher, sobald sie mit der Webseite interagieren, unter Verwendung verschiedener Informationen wie IP-Adresse, Aufenthaltsdauer und Mausbewegungen.</p>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY, marginBottom: 8 }}>Verarbeitete Daten</p>
|
||||
<ul style={{ ...body, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 16 }}>
|
||||
<li>Nutzungsdaten: aufgerufene Webseite, Datum und Uhrzeit des Zugriffs, Mausbewegungen</li>
|
||||
<li>Kommunikationsdaten: IP-Adresse, Browsertyp, Betriebssystem</li>
|
||||
</ul>
|
||||
<p style={body}>Der Hauptzweck dieser Verarbeitung besteht darin, den Missbrauch unseres Kontaktformulars zu verhindern und dadurch die Sicherheit zu erhöhen.</p>
|
||||
<Legal>Rechtsgrundlage: berechtigtes Interesse gemäß Art. 6 Abs. 1 S. 1 lit. f. DSGVO</Legal>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY, marginBottom: 8 }}>Datenempfänger</p>
|
||||
<AddressBlock>
|
||||
Google Ireland Ltd<br />
|
||||
Google Building Gordon House, Barrow St<br />
|
||||
Dublin 4, Irland<br />
|
||||
Datenschutzerklärung: https://policies.google.com/privacy
|
||||
</AddressBlock>
|
||||
<p style={body}>Während Ihre persönlichen Daten innerhalb der EU verarbeitet werden, wird die Aufbewahrungsfrist für die verarbeiteten Daten von Google Ireland Limited bestimmt. Weitere Informationen finden Sie in der Datenschutzerklärung von Google reCAPTCHA unter <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer" style={{ color: NAVY }}>policies.google.com/privacy</a>.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="webanalyse" title="Webseitenanalyse (Jimdo Statistiken)">
|
||||
<p style={body}>Wenn Sie unsere Website besuchen, sammeln wir Informationen über Ihre Nutzung durch ein Webanalyse-Tool, das von unserem Hosting-Service bereitgestellt wird. Dieses Tool sammelt und kombiniert Ihre IP-Adresse und Ihren User Agent, verkürzt sie und speichert diese Daten mit einer Hash-Funktion.</p>
|
||||
<p style={body}>Durch diesen Vorgang wird eine Besucherkennung erstellt, die mit einem zufällig generierten Wert (SALT) verschlüsselt ist, der sich alle 24 Stunden ändert. Diese Methode stellt sicher, dass Ihre IP-Adresse nicht aus dem gespeicherten Besucheridentifikator rekonstruiert werden kann, sodass Ihre Anonymität gewahrt bleibt. Außerdem führen wir diese Informationen nicht mit anderen Daten zusammen und sie werden nur auf dem Server des Hosting-Anbieters gespeichert.</p>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY, marginBottom: 8 }}>Verarbeitete Daten</p>
|
||||
<ul style={{ ...body, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 16 }}>
|
||||
<li>Webanalysedaten: Seitenaufrufe, Besuchshäufigkeit, Verweildauer auf jeder Seite, User Agent</li>
|
||||
<li>HTTP-Daten & Nutzungsdaten: besuchte Webseiten, Zugriffszeiten</li>
|
||||
<li>Kommunikationsdaten: Browsertyp, Betriebssystem, IP-Adressen</li>
|
||||
</ul>
|
||||
<p style={body}>Mit der Verarbeitung dieser Daten wollen wir das Nutzerverhalten in zusammengefasster Form analysieren, um die Darstellung und den Inhalt unserer Website zu verbessern.</p>
|
||||
<Legal>Rechtsgrundlage: berechtigtes Interesse gemäß Art. 6 Abs. 1 lit. f. DSGVO – insbesondere an der Durchführung von Webmessungen zur Verbesserung unserer Website.</Legal>
|
||||
<p style={body}>Die erhobenen Daten werden mit unserem Website-Hosting-Anbieter geteilt und innerhalb der EU verarbeitet.</p>
|
||||
</Section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── shared styles ── */
|
||||
const body: React.CSSProperties = {
|
||||
fontFamily: '"Inter", sans-serif',
|
||||
fontSize: 15,
|
||||
color: 'rgba(58,58,58,0.82)',
|
||||
lineHeight: 1.85,
|
||||
margin: '0 0 16px',
|
||||
overflowWrap: 'anywhere',
|
||||
};
|
||||
|
||||
function Section({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div id={id} style={{ scrollMarginTop: 80 }}>
|
||||
<div style={{ borderLeft: `3px solid ${GOLD}`, paddingLeft: 16, marginBottom: 28 }}>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.2em', color: NAVY, margin: 0 }}>
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<div style={{ background: '#fff', padding: 'clamp(20px, 5vw, 28px) clamp(20px, 5vw, 32px)', boxShadow: '0 1px 3px rgba(0,0,0,0.06)' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SubSection({ num, title, children }: { num: string; title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div style={{ marginBottom: 28 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.12em' }}>{num}.</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: NAVY, margin: 0 }}>{title}</h3>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AddressBlock({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<address style={{ fontFamily: '"Inter", sans-serif', fontSize: isMobile ? 15 : 14, color: 'rgba(58,58,58,0.75)', lineHeight: 1.9, fontStyle: 'normal', background: 'rgba(17,29,85,0.04)', padding: '16px 20px', borderLeft: '2px solid rgba(239,191,4,0.4)', marginBottom: 16, overflowWrap: 'anywhere' }}>
|
||||
{children}
|
||||
</address>
|
||||
);
|
||||
}
|
||||
|
||||
function Legal({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: isMobile ? 15 : 14, color: NAVY, lineHeight: 1.75, background: 'rgba(17,29,85,0.04)', padding: '12px 16px', borderLeft: `3px solid ${NAVY}`, margin: '0 0 16px', fontWeight: 500, overflowWrap: 'anywhere' }}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
304
src/spa/pages/EventDetail.tsx
Normal file
304
src/spa/pages/EventDetail.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useParams, Link } from '@/spa/router'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Calendar, MapPin, Clock, Bell, BellOff, ArrowLeft, ChevronRight, Users, Tag } from 'lucide-react'
|
||||
import { getEventBySlug, EventUpdate, UpdateType } from '@/spa/data/events'
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile'
|
||||
|
||||
const NAVY = '#111D55'
|
||||
const GOLD = '#EFBF04'
|
||||
const CREAM = '#EFE5E3'
|
||||
const INK = '#3A3A3A'
|
||||
|
||||
// ── Timestamp formatter ──────────────────────────────────────────────────────
|
||||
const MONATE = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember']
|
||||
function formatTs(iso: string): string {
|
||||
const d = new Date(iso)
|
||||
return `${d.getDate()}. ${MONATE[d.getMonth()]} ${d.getFullYear()}, ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')} Uhr`
|
||||
}
|
||||
|
||||
// ── Update type styling ──────────────────────────────────────────────────────
|
||||
const UPDATE_STYLE: Record<UpdateType, { bg: string; color: string; label: string }> = {
|
||||
ankündigung: { bg: '#EBF4FF', color: '#1D4ED8', label: 'Ankündigung' },
|
||||
erinnerung: { bg: '#FFFBEB', color: '#B45309', label: 'Erinnerung' },
|
||||
update: { bg: '#F3F4F6', color: '#374151', label: 'Update' },
|
||||
highlight: { bg: `${NAVY}12`, color: NAVY, label: 'Highlight' },
|
||||
ergebnis: { bg: '#ECFDF5', color: '#065F46', label: 'Ergebnis' },
|
||||
wichtig: { bg: '#FEF2F2', color: '#991B1B', label: 'Wichtig' },
|
||||
}
|
||||
|
||||
// ── Status badge ─────────────────────────────────────────────────────────────
|
||||
const STATUS_LABEL: Record<string, { label: string; bg: string; color: string }> = {
|
||||
'geplant': { label: 'In Planung', bg: `${NAVY}18`, color: NAVY },
|
||||
'anmeldung-offen': { label: 'Anmeldung offen', bg: '#ECFDF5', color: '#065F46' },
|
||||
'intern': { label: 'Intern', bg: '#FEF9C3', color: '#713F12' },
|
||||
'abgeschlossen': { label: 'Abgeschlossen', bg: '#F3F4F6', color: '#6B7280' },
|
||||
}
|
||||
|
||||
// ── Update Card ───────────────────────────────────────────────────────────────
|
||||
function UpdateCard({ update, index }: { update: EventUpdate; index: number }) {
|
||||
const style = UPDATE_STYLE[update.type] ?? UPDATE_STYLE.update
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 24 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.35, delay: index * 0.07, ease: 'easeOut' }}
|
||||
style={{ display: 'flex', gap: 16, position: 'relative' }}
|
||||
>
|
||||
{/* Timeline dot */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', flexShrink: 0 }}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: '50%', background: GOLD, border: `2px solid ${GOLD}`, marginTop: 6, flexShrink: 0 }} />
|
||||
<div style={{ flex: 1, width: 1, background: `rgba(3,9,58,0.1)`, marginTop: 6 }} />
|
||||
</div>
|
||||
|
||||
{/* Card */}
|
||||
<div style={{
|
||||
flex: 1, background: '#fff', borderRadius: 12, padding: '16px 20px',
|
||||
marginBottom: 12, border: '1px solid rgba(3,9,58,0.08)',
|
||||
boxShadow: '0 1px 8px rgba(3,9,58,0.04)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
|
||||
<span style={{
|
||||
fontSize: 10, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
padding: '3px 8px', borderRadius: 20,
|
||||
background: style.bg, color: style.color,
|
||||
}}>{style.label}</span>
|
||||
<span style={{ fontSize: 11, color: 'rgba(58,58,58,0.45)' }}>{formatTs(update.timestamp)}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: NAVY, marginBottom: 4, lineHeight: 1.35 }}>{update.title}</div>
|
||||
<div style={{ fontSize: 14, color: 'rgba(58,58,58,0.65)', lineHeight: 1.65 }}>{update.body}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Main Component ────────────────────────────────────────────────────────────
|
||||
export default function EventDetail() {
|
||||
const { slug } = useParams<{ slug: string }>()
|
||||
const event = getEventBySlug(slug ?? '')
|
||||
const [subscribed, setSubscribed] = useState(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
if (!event) {
|
||||
return (
|
||||
<div style={{ paddingTop: 60, minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: CREAM, flexDirection: 'column', gap: 24 }}>
|
||||
<div style={{ fontSize: 64, fontWeight: 900, color: `${NAVY}20`, letterSpacing: '-0.04em' }}>404</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, color: NAVY }}>Event nicht gefunden</div>
|
||||
<Link to="/presse" style={{ fontSize: 15, color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, fontWeight: 600, borderBottom: `2px solid ${GOLD}`, paddingBottom: 2 }}>
|
||||
<ArrowLeft size={14} /> Zurück zur Presse-Seite
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const statusStyle = STATUS_LABEL[event.status] ?? STATUS_LABEL['geplant']
|
||||
|
||||
return (
|
||||
<div style={{ paddingTop: 60, background: CREAM, minHeight: '100vh', fontFamily: '"Inter", sans-serif' }}>
|
||||
|
||||
{/* ── Hero ─────────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', height: isMobile ? 'auto' : '65vh', minHeight: isMobile ? 520 : 480, overflow: 'hidden' }}>
|
||||
<img
|
||||
src={event.img}
|
||||
alt={event.title}
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(3,9,58,0.25) 0%, rgba(3,9,58,0.92) 100%)' }} />
|
||||
|
||||
{/* Back link */}
|
||||
<div style={{ position: 'absolute', top: 28, left: 0, right: 0, padding: isMobile ? '0 24px' : '0 80px', zIndex: 10 }}>
|
||||
<Link to="/presse" style={{ display: 'inline-flex', alignItems: 'center', gap: 8, color: 'rgba(255,255,255,0.7)', textDecoration: 'none', fontSize: 14, fontWeight: 600, transition: 'color 0.15s' }}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.7)')}
|
||||
>
|
||||
<ArrowLeft size={14} /> Alle Events
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Hero content */}
|
||||
<div style={{ position: isMobile ? 'relative' : 'absolute', bottom: 0, left: 0, right: 0, padding: isMobile ? '88px 24px 48px' : '40px 80px', zIndex: 10, display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: isMobile ? 'flex-end' : 'space-between', alignItems: isMobile ? 'flex-start' : 'flex-end', gap: isMobile ? 20 : 40, minHeight: isMobile ? 520 : undefined, boxSizing: 'border-box' }}>
|
||||
{/* Left */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontSize: 10, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '0.2em', background: statusStyle.bg, color: statusStyle.color, padding: '4px 10px', borderRadius: 20 }}>
|
||||
{statusStyle.label}
|
||||
</span>
|
||||
<span style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.15em', color: GOLD }}>
|
||||
{event.category}
|
||||
</span>
|
||||
</div>
|
||||
<h1 style={{ fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.8rem)' : 'clamp(2rem, 5vw, 4rem)', fontWeight: 900, color: '#fff', letterSpacing: '-0.03em', lineHeight: 1.05, marginBottom: 12, fontFamily: '"IBM Plex Sans", sans-serif' }}>
|
||||
{event.title}
|
||||
</h1>
|
||||
<p style={{ fontSize: isMobile ? 14 : 16, color: 'rgba(255,255,255,0.65)', maxWidth: 520, lineHeight: 1.6 }}>
|
||||
{event.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right – key facts */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, flexShrink: 0, background: 'rgba(3,9,58,0.55)', backdropFilter: 'blur(12px)', borderRadius: 16, padding: isMobile ? '16px 20px' : '24px 28px', border: '1px solid rgba(255,255,255,0.1)', width: isMobile ? '100%' : undefined, boxSizing: 'border-box' }}>
|
||||
{[
|
||||
{ icon: Calendar, label: event.date },
|
||||
{ icon: Clock, label: event.time },
|
||||
{ icon: MapPin, label: `${event.venue}, ${event.location}` },
|
||||
].map(({ icon: Icon, label }, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
||||
<Icon size={15} color={GOLD} style={{ flexShrink: 0 }} />
|
||||
<span style={{ fontSize: 14, color: '#fff', fontWeight: 500, overflowWrap: 'anywhere' }}>{label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Gold bottom line */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 3, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)` }} />
|
||||
</section>
|
||||
|
||||
{/* ── Main Content ─────────────────────────────────────────────────── */}
|
||||
<section style={{ padding: isMobile ? '48px 24px' : '64px 80px', maxWidth: 1400, margin: '0 auto' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 400px', gap: isMobile ? 48 : 64, alignItems: 'start' }}>
|
||||
|
||||
{/* Left – Description + Eckdaten */}
|
||||
<div>
|
||||
{/* Description */}
|
||||
<div style={{ marginBottom: 56 }}>
|
||||
<p style={{ fontSize: 11, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '0.25em', color: GOLD, marginBottom: 16 }}>
|
||||
Über die Veranstaltung
|
||||
</p>
|
||||
{event.longDescription.split('\n\n').map((para, i) => (
|
||||
<p key={i} style={{ fontSize: 17, color: 'rgba(58,58,58,0.75)', lineHeight: 1.8, marginBottom: 20 }}>{para}</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Eckdaten */}
|
||||
<div>
|
||||
<p style={{ fontSize: 11, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '0.25em', color: GOLD, marginBottom: 24 }}>
|
||||
Eckdaten
|
||||
</p>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 2 }}>
|
||||
{event.eckdaten.map((item, i) => (
|
||||
<div key={i} style={{
|
||||
padding: '20px 24px',
|
||||
background: i % 2 === 0 ? '#fff' : 'rgba(255,255,255,0.6)',
|
||||
borderRadius: isMobile
|
||||
? (i === 0
|
||||
? '12px 12px 0 0'
|
||||
: i === event.eckdaten.length - 1
|
||||
? '0 0 12px 12px'
|
||||
: '0')
|
||||
: (i === 0 ? '12px 0 0 0' : i === 1 ? '0 12px 0 0' : i === event.eckdaten.length - 2 ? '0 0 0 12px' : i === event.eckdaten.length - 1 ? '0 0 12px 0' : '0'),
|
||||
border: '1px solid rgba(3,9,58,0.06)',
|
||||
}}>
|
||||
<div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(58,58,58,0.4)', marginBottom: 6 }}>
|
||||
{item.label}
|
||||
</div>
|
||||
<div style={{ fontSize: 16, fontWeight: 700, color: NAVY, lineHeight: 1.35 }}>
|
||||
{item.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div style={{ marginTop: 40, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
{event.status === 'anmeldung-offen' && (
|
||||
<Link to="/teilnahme" style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
background: NAVY, color: GOLD, textDecoration: 'none',
|
||||
padding: '14px 28px', borderRadius: 10, fontWeight: 700, fontSize: 15,
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
}}>
|
||||
Jetzt anmelden <ChevronRight size={16} />
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/kontakt" style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
background: 'transparent', color: NAVY, textDecoration: 'none',
|
||||
padding: '14px 28px', borderRadius: 10, fontWeight: 700, fontSize: 15,
|
||||
border: `2px solid ${NAVY}`,
|
||||
}}>
|
||||
Frage stellen
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right – Live Updates Feed (sticky on desktop, static on mobile) */}
|
||||
<div style={isMobile ? undefined : { position: 'sticky', top: 80 }}>
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<Bell size={18} color={NAVY} />
|
||||
<span style={{ fontSize: 17, fontWeight: 800, color: NAVY, fontFamily: '"IBM Plex Sans", sans-serif' }}>
|
||||
Live Updates
|
||||
</span>
|
||||
<span style={{
|
||||
fontSize: 11, fontWeight: 800, background: NAVY, color: GOLD,
|
||||
borderRadius: 20, padding: '2px 8px',
|
||||
}}>{event.updates.length}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setSubscribed(s => !s)}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer',
|
||||
background: subscribed ? NAVY : 'transparent',
|
||||
color: subscribed ? GOLD : NAVY,
|
||||
border: `1.5px solid ${NAVY}`, borderRadius: 8,
|
||||
padding: '8px 14px', fontSize: 12, fontWeight: 700,
|
||||
transition: 'all 0.2s', fontFamily: 'inherit',
|
||||
}}
|
||||
>
|
||||
{subscribed ? <><BellOff size={13} /> Abgemeldet</> : <><Bell size={13} /> Abonnieren</>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{subscribed && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
style={{ background: '#ECFDF5', border: '1px solid #A7F3D0', borderRadius: 10, padding: '10px 14px', marginBottom: 16, fontSize: 14, color: '#065F46', fontWeight: 500 }}
|
||||
>
|
||||
✓ Sie erhalten Benachrichtigungen für dieses Event.
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Timeline */}
|
||||
<div style={{ maxHeight: isMobile ? 'none' : 560, overflowY: isMobile ? 'visible' : 'auto', paddingRight: 4 }}>
|
||||
{event.updates.map((update, i) => (
|
||||
<UpdateCard key={update.id} update={update} index={i} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Bottom CTA ───────────────────────────────────────────────────── */}
|
||||
<section style={{ background: NAVY, padding: isMobile ? '48px 24px' : '64px 80px', marginTop: 40 }}>
|
||||
<div style={{ maxWidth: 1400, margin: '0 auto', display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'flex-start' : 'center', gap: isMobile ? 24 : 0 }}>
|
||||
<div>
|
||||
<p style={{ fontSize: 11, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '0.25em', color: GOLD, marginBottom: 10 }}>
|
||||
Mehr entdecken
|
||||
</p>
|
||||
<h3 style={{ fontSize: 28, fontWeight: 900, color: '#fff', letterSpacing: '-0.03em', fontFamily: '"IBM Plex Sans", sans-serif' }}>
|
||||
Weitere Veranstaltungen
|
||||
</h3>
|
||||
</div>
|
||||
<Link
|
||||
to="/presse"
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8, textDecoration: 'none',
|
||||
background: GOLD, color: NAVY, padding: '14px 28px', borderRadius: 10,
|
||||
fontWeight: 800, fontSize: 15, fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
transition: 'background 0.15s, box-shadow 0.2s',
|
||||
}}
|
||||
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = '#FFD130'; el.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }}
|
||||
>
|
||||
Alle Events <ChevronRight size={16} />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
265
src/spa/pages/FormularUpload.tsx
Normal file
265
src/spa/pages/FormularUpload.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { Upload, FileText, CheckCircle, AlertCircle, X, ArrowRight, Clock, Shield, Info } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const GOLD = '#EFBF04';
|
||||
const NAVY = '#111D55';
|
||||
const DARK = '#101828';
|
||||
const GRAU = '#E4E2E3';
|
||||
const BORDER = '#D0D5DD';
|
||||
const HELLBLAU = '#4A8FC9';
|
||||
|
||||
type UploadFile = { name: string; size: number; status: 'ready' | 'uploading' | 'done' | 'error' };
|
||||
|
||||
function formatBytes(b: number) {
|
||||
if (b < 1024) return b + ' B';
|
||||
if (b < 1024 * 1024) return (b / 1024).toFixed(1) + ' KB';
|
||||
return (b / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
export default function FormularUpload() {
|
||||
const [files, setFiles] = useState<UploadFile[]>([]);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const addFiles = (fileList: FileList) => {
|
||||
const newFiles: UploadFile[] = Array.from(fileList).map(f => ({
|
||||
name: f.name, size: f.size, status: 'ready',
|
||||
}));
|
||||
setFiles(prev => [...prev, ...newFiles]);
|
||||
};
|
||||
|
||||
const remove = (i: number) => setFiles(prev => prev.filter((_, idx) => idx !== i));
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!files.length) return;
|
||||
setFiles(prev => prev.map(f => ({ ...f, status: 'uploading' })));
|
||||
setTimeout(() => {
|
||||
setFiles(prev => prev.map(f => ({ ...f, status: 'done' })));
|
||||
setTimeout(() => setSubmitted(true), 600);
|
||||
}, 1800);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||
|
||||
{/* ── HERO ────────────────────────────────────────── */}
|
||||
<div style={{ background: DARK, position: 'relative', overflow: 'hidden', padding: isMobile ? '64px 24px 48px' : '100px 80px 72px' }}>
|
||||
<div style={{ height: 3, background: `linear-gradient(to right, ${GOLD}, transparent)`, position: 'absolute', top: 0, left: 0, right: 0 }} />
|
||||
|
||||
<div style={{ maxWidth: 720, position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.35em', color: HELLBLAU, display: 'block', marginBottom: 16 }}>
|
||||
BMP 2026 – Bewerbungsportal
|
||||
</span>
|
||||
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 4vw, 3.6rem)' : 'clamp(2.2rem, 4vw, 3.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.0, margin: '0 0 20px' }}>
|
||||
UNTERLAGEN<br />EINREICHEN
|
||||
</h1>
|
||||
<div style={{ width: 48, height: 3, background: GOLD, marginBottom: 24 }} />
|
||||
<p style={{ fontFamily: FF, fontSize: 16, color: 'rgba(255,255,255,0.55)', lineHeight: 1.75, maxWidth: 520, margin: 0 }}>
|
||||
Laden Sie Ihre Bewerbungsunterlagen sicher hoch. Alle Einreichungen werden vertraulich behandelt und direkt an die Jury weitergeleitet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats row */}
|
||||
<div style={{ display: 'flex', flexWrap: isMobile ? 'wrap' : 'nowrap', gap: isMobile ? '24px 0' : 0, marginTop: 56, borderTop: '1px solid rgba(255,255,255,0.07)', paddingTop: 32, position: 'relative', zIndex: 1 }}>
|
||||
{[
|
||||
{ num: '100%', label: 'Vertraulich' },
|
||||
{ num: 'SSL', label: 'Verschlüsselt' },
|
||||
{ num: '30 MB', label: 'Max. Dateigröße' },
|
||||
{ num: 'PDF/DOCX', label: 'Akzeptierte Formate' },
|
||||
].map((s, i) => (
|
||||
<div key={i} style={{ flex: isMobile ? '0 0 50%' : 1, paddingRight: 32 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 22, fontWeight: 900, color: GOLD, lineHeight: 1 }}>{s.num}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.35)', marginTop: 4, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div style={{ background: '#fff', borderBottom: `1px solid ${BORDER}`, padding: isMobile ? '16px 24px' : '16px 80px' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 12, color: HELLBLAU, fontWeight: 700, letterSpacing: '0.04em' }}>Formular hochladen</span>
|
||||
</div>
|
||||
|
||||
{/* ── ERKLÄRUNG ───────────────────────────────────── */}
|
||||
<div style={{ background: GRAU, padding: isMobile ? '48px 24px' : '64px 80px' }}>
|
||||
<p style={{ fontFamily: FF, fontSize: 11, color: '#888', textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: 8 }}>Was wird benötigt?</p>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 900, color: DARK, textTransform: 'uppercase', letterSpacing: '-0.02em', marginBottom: 40 }}>
|
||||
ANFORDERUNGEN & FRISTEN
|
||||
</h2>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 1, background: BORDER }}>
|
||||
{[
|
||||
{
|
||||
icon: FileText,
|
||||
title: 'Pflichtdokumente',
|
||||
items: ['Kurzprofil des Unternehmens (max. 2 Seiten)', 'Begründung der Bewerbung (mind. 500 Wörter)', 'Anschreiben der Geschäftsführung', 'Letzte 2 Jahresabschlüsse (optional, falls vorhanden)'],
|
||||
},
|
||||
{
|
||||
icon: Clock,
|
||||
title: 'Fristen & Einreichung',
|
||||
items: ['Bewerbungsschluss: 30. Juni 2026', 'Nachreichungen bis: 15. Juli 2026', 'Benachrichtigung: August 2026', 'Gala & Verleihung: Oktober 2026'],
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: 'Technische Anforderungen',
|
||||
items: ['Formate: PDF, DOCX, XLSX', 'Max. Dateigröße: 30 MB pro Datei', 'Max. Gesamtgröße: 100 MB', 'Sprache: Deutsch'],
|
||||
},
|
||||
].map(({ icon: Icon, title, items }, i) => (
|
||||
<div key={i} style={{ background: '#fff', padding: '36px 32px' }}>
|
||||
<div style={{ width: 40, height: 40, background: DARK, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 20 }}>
|
||||
<Icon size={18} color={GOLD} />
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, color: DARK, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 16 }}>{title}</h3>
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{items.map((item, j) => (
|
||||
<li key={j} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, fontFamily: FF, fontSize: 14, color: '#555', lineHeight: 1.5 }}>
|
||||
<span style={{ color: GOLD, marginTop: 2, flexShrink: 0 }}>–</span>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── UPLOAD-BEREICH ──────────────────────────────── */}
|
||||
<div style={{ background: NAVY, padding: isMobile ? '48px 24px' : '72px 80px' }}>
|
||||
<p style={{ fontFamily: FF, fontSize: 11, color: HELLBLAU, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.25em', marginBottom: 8 }}>Einreichung</p>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', marginBottom: 40 }}>
|
||||
DATEIEN HOCHLADEN
|
||||
</h2>
|
||||
|
||||
{submitted ? (
|
||||
<div style={{ textAlign: 'center', padding: '64px 0' }}>
|
||||
<div style={{ width: 64, height: 64, background: GOLD, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px' }}>
|
||||
<CheckCircle size={28} color={DARK} />
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 22, fontWeight: 900, color: '#fff', textTransform: 'uppercase', marginBottom: 12 }}>Einreichung erfolgreich</h3>
|
||||
<p style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.45)', maxWidth: 440, margin: '0 auto 32px', lineHeight: 1.7 }}>
|
||||
Ihre Unterlagen wurden übermittelt. Sie erhalten eine Bestätigung per E-Mail. Die Jury meldet sich bis August 2026.
|
||||
</p>
|
||||
<Link to="/teilnahme" style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: DARK, background: GOLD, padding: '14px 32px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
||||
Zur Teilnahmeseite <ArrowRight size={13} />
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 360px', gap: 32, alignItems: 'start' }}>
|
||||
|
||||
{/* Drop zone */}
|
||||
<div>
|
||||
<div
|
||||
onDragOver={e => { e.preventDefault(); setDragging(true); }}
|
||||
onDragLeave={() => setDragging(false)}
|
||||
onDrop={e => { e.preventDefault(); setDragging(false); if (e.dataTransfer.files.length) addFiles(e.dataTransfer.files); }}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? GOLD : 'rgba(255,255,255,0.2)'}`,
|
||||
padding: '52px 32px',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
background: dragging ? 'rgba(239,191,4,0.05)' : 'rgba(255,255,255,0.03)',
|
||||
transition: 'all 0.2s',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<Upload size={32} color={dragging ? GOLD : 'rgba(255,255,255,0.3)'} style={{ margin: '0 auto 16px' }} />
|
||||
<p style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: '#fff', marginBottom: 8 }}>
|
||||
Dateien hierher ziehen
|
||||
</p>
|
||||
<p style={{ fontFamily: FF, fontSize: 12, color: 'rgba(255,255,255,0.35)', marginBottom: 20 }}>
|
||||
oder klicken zum Auswählen
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
|
||||
{['PDF', 'DOCX', 'XLSX', 'PPT'].map(ext => (
|
||||
<span key={ext} style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', color: GOLD, border: `1px solid rgba(239,191,4,0.4)`, padding: '3px 10px' }}>{ext}</span>
|
||||
))}
|
||||
</div>
|
||||
<input ref={inputRef} type="file" multiple accept=".pdf,.docx,.xlsx,.pptx" style={{ display: 'none' }} onChange={e => { if (e.target.files) addFiles(e.target.files); }} />
|
||||
</div>
|
||||
|
||||
{/* File list */}
|
||||
{files.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{files.map((f, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'rgba(255,255,255,0.05)', borderLeft: `3px solid ${f.status === 'done' ? GOLD : f.status === 'error' ? '#f87171' : 'rgba(255,255,255,0.15)'}` }}>
|
||||
{f.status === 'done' ? <CheckCircle size={14} color={GOLD} /> : f.status === 'error' ? <AlertCircle size={14} color="#f87171" /> : <FileText size={14} color="rgba(255,255,255,0.4)" />}
|
||||
<span style={{ fontFamily: FF, fontSize: 12, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.3)' }}>{formatBytes(f.size)}</span>
|
||||
{f.status === 'uploading' && <div style={{ width: 14, height: 14, border: '2px solid rgba(255,255,255,0.2)', borderTopColor: GOLD, borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />}
|
||||
{f.status !== 'uploading' && f.status !== 'done' && (
|
||||
<button onClick={() => remove(i)} aria-label="Datei entfernen" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.3)', padding: isMobile ? 12 : 0, margin: isMobile ? -12 : 0, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<X size={isMobile ? 18 : 14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sidebar info + submit */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||
<div style={{ padding: '24px', background: 'rgba(255,255,255,0.04)', borderTop: `2px solid ${GOLD}` }}>
|
||||
<div style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginBottom: 16 }}>
|
||||
<Info size={14} color={GOLD} style={{ marginTop: 2, flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Hinweis</span>
|
||||
</div>
|
||||
<p style={{ fontFamily: FF, fontSize: 12, color: 'rgba(255,255,255,0.4)', lineHeight: 1.7, margin: 0 }}>
|
||||
Stellen Sie sicher, dass alle Pflichtdokumente vollständig sind, bevor Sie einreichen. Unvollständige Bewerbungen können nicht berücksichtigt werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={files.length === 0}
|
||||
style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em', color: DARK, background: files.length ? GOLD : 'rgba(239,191,4,0.3)',
|
||||
border: 'none', cursor: files.length ? 'pointer' : 'not-allowed',
|
||||
padding: '16px 28px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
gap: 8, transition: 'background 0.15s, box-shadow 0.2s', width: '100%',
|
||||
}}
|
||||
onMouseEnter={e => { if (files.length) { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; } }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = files.length ? GOLD : 'rgba(239,191,4,0.3)'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
<Upload size={14} />
|
||||
{files.length ? `${files.length} Datei${files.length > 1 ? 'en' : ''} einreichen` : 'Keine Dateien ausgewählt'}
|
||||
</button>
|
||||
|
||||
<p style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.2)', lineHeight: 1.6, margin: 0 }}>
|
||||
Mit der Einreichung stimmen Sie der Verarbeitung Ihrer Daten gemäß unserer Datenschutzerklärung zu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
</div>
|
||||
|
||||
{/* ── CTA ─────────────────────────────────────────── */}
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '64px 80px', borderTop: `1px solid ${BORDER}`, display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 24 }}>
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 12, color: '#888', marginBottom: 6 }}>Noch Fragen zur Einreichung?</p>
|
||||
<Link to="/kontakt" style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
Kontakt aufnehmen <ArrowRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: DARK, background: GOLD, padding: '14px 32px', textDecoration: 'none', display: isMobile ? 'flex' : 'inline-flex', width: isMobile ? '100%' : 'auto', justifyContent: isMobile ? 'center' : undefined, alignItems: 'center', gap: 8, transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Zur Teilnahmeseite <ArrowRight size={13} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
830
src/spa/pages/Home.tsx
Normal file
830
src/spa/pages/Home.tsx
Normal file
@@ -0,0 +1,830 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Button } from '@/spa/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/spa/components/ui/card';
|
||||
import { Badge } from '@/spa/components/ui/badge';
|
||||
import { Award, Users, TrendingUp, Play, ChevronRight, Building2, ShieldCheck, Leaf, HeartHandshake, X, Star } from 'lucide-react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { PartnerTicker } from '@/spa/components/ui/partner-ticker';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import BewerbungsForm from '@/spa/components/forms/BewerbungsForm';
|
||||
import TestimonialsSection from '@/spa/components/TestimonialsSection';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const PARTNERS = [
|
||||
{ id: 'rsm', label: <><span style={{fontWeight:700,fontSize:11,letterSpacing:2}}>RSM</span><br/><span style={{fontWeight:300,fontSize:8,letterSpacing:1,opacity:.7}}>Ebner Stolz</span></> },
|
||||
{ id: 'wwk', label: <span style={{fontWeight:800,fontSize: 15,letterSpacing:3}}>WWK</span> },
|
||||
{ id: 'db', label: <span style={{fontWeight:700,fontSize:10,letterSpacing:1.5}}>Deutsche Bank</span> },
|
||||
{ id: 'bio', label: <span style={{fontFamily:'Georgia,serif',fontSize: 14,letterSpacing:.5}}>Bionorica</span> },
|
||||
{ id: 'bcl', label: <span style={{fontWeight:700,fontSize:9,letterSpacing:1.5,lineHeight:1.3,textAlign:'center' as const,display:'block'}}>Berchtesgadener<br/>Land</span> },
|
||||
{ id: 'mtz', label: <span style={{fontFamily:'Georgia,serif',fontStyle:'italic',fontSize: 14}}>Metzler</span> },
|
||||
{ id: 'vbw', label: <span style={{fontWeight:800,fontSize: 14,letterSpacing:2}}>vbw</span> },
|
||||
{ id: 'wby', label: <span style={{fontWeight:700,fontSize:9,letterSpacing:1,lineHeight:1.3,textAlign:'center' as const,display:'block'}}>Wirtschafts-<br/>beirat Bayern</span> },
|
||||
{ id: 'rgong', label: <span style={{fontWeight:700,fontSize:9,letterSpacing:1}}>Radio Gong 96.3</span> },
|
||||
{ id: 'mtvr', label: <span style={{fontWeight:700,fontSize:10,letterSpacing:1.5}}>münchen.tv</span> },
|
||||
{ id: 'ews', label: <span style={{fontWeight:800,fontSize: 14,letterSpacing:2}}>EWS</span> },
|
||||
];
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
// ─── Status Phase Slider ────────────────────────────────────────────────────
|
||||
/**
|
||||
* 🔧 DEPLOYMENT CONFIG
|
||||
* Setze ACTIVE_PHASE je nach aktuellem Stand des BMP-Zyklus:
|
||||
* 0 = Bewerbungsphase offen
|
||||
* 1 = Auswertungsphase (Jury bewertet)
|
||||
* 2 = Nach der Preisverleihung (Erfolg)
|
||||
*/
|
||||
const ACTIVE_PHASE: 0 | 1 | 2 = 0;
|
||||
|
||||
interface StatusPhase {
|
||||
num: string;
|
||||
tag: string;
|
||||
pulse: boolean;
|
||||
phase: string;
|
||||
headline: string;
|
||||
body: string;
|
||||
cta: { label: string; to: string } | null;
|
||||
accent: string;
|
||||
bg: string;
|
||||
glow: string;
|
||||
meta: string;
|
||||
}
|
||||
|
||||
const STATUS_PHASES: StatusPhase[] = [
|
||||
{
|
||||
num: '01',
|
||||
tag: 'JETZT OFFEN',
|
||||
pulse: true,
|
||||
phase: 'Bewerbungsphase 2026',
|
||||
headline: 'BEWERBEN SIE SICH JETZT.',
|
||||
body: 'Die Bewerbungsphase 2026 ist eröffnet – es ist noch nicht zu spät. Reichen Sie Ihre Unterlagen bis zum 30. Juni kostenlos ein und zeigen Sie, was den bayerischen Mittelstand ausmacht.',
|
||||
cta: { label: 'Jetzt kostenlos bewerben', to: '/teilnahme' },
|
||||
accent: '#EFBF04',
|
||||
bg: '#020A1E',
|
||||
glow: 'rgba(239,191,4,0.14)',
|
||||
meta: 'Deadline: 30. Juni 2026',
|
||||
},
|
||||
{
|
||||
num: '02',
|
||||
tag: 'IN AUSWERTUNG',
|
||||
pulse: true,
|
||||
phase: 'Auswertungsphase 2026',
|
||||
headline: 'DIE JURY BEWERTET GERADE.',
|
||||
body: 'Die Anmeldephase 2026 ist geschlossen. Unsere unabhängige Expertenjury aus Wissenschaft und Praxis sichtet und bewertet derzeit alle Einreichungen.',
|
||||
cta: null,
|
||||
accent: '#4A8FC9',
|
||||
bg: '#060D1C',
|
||||
glow: 'rgba(74,143,201,0.12)',
|
||||
meta: 'Ergebnis: August 2026',
|
||||
},
|
||||
{
|
||||
num: '03',
|
||||
tag: 'ABGESCHLOSSEN',
|
||||
pulse: false,
|
||||
phase: 'Preisverleihung · Oktober 2026',
|
||||
headline: 'DER BMP 2026 WAR EIN ERFOLG.',
|
||||
body: 'Wir danken allen 247 Bewerberinnen und Bewerbern für Mut und Exzellenz. Drei außergewöhnliche Unternehmen wurden in München ausgezeichnet – ein unvergesslicher Abend.',
|
||||
cta: { label: 'Alle Preisträger ansehen', to: '/preistraeger' },
|
||||
accent: '#EFBF04',
|
||||
bg: '#06090F',
|
||||
glow: 'rgba(239,191,4,0.08)',
|
||||
meta: 'Gala · Oktober 2026 · München',
|
||||
},
|
||||
];
|
||||
|
||||
function HomeWinnerCard({ winner }: { winner: import('@/spa/data/winners').Winner }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Link
|
||||
to={`/preistraeger/${winner.slug}`}
|
||||
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 220 : 280, overflow: 'hidden', scrollSnapAlign: isMobile ? 'start' : undefined }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<img src={winner.img} alt={winner.name} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.5s ease', transform: hovered ? 'scale(1.07)' : 'scale(1)', filter: 'grayscale(15%)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.1) 55%, transparent 100%)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.2)', opacity: hovered ? 1 : 0, transition: 'opacity 0.25s' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, padding: '18px 20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 5 }}>
|
||||
<Star size={10} style={{ color: '#EFBF04', flexShrink: 0 }} fill="#EFBF04" />
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: '#EFBF04', letterSpacing: '0.12em', textTransform: 'uppercase' }}>{winner.year} · {winner.type}</span>
|
||||
</div>
|
||||
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight: 700, color: '#fff', margin: '0 0 3px', lineHeight: 1.3 }}>{winner.name}</h3>
|
||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(255,255,255,0.75)', margin: 0 }}>{winner.category}</p>
|
||||
<div style={{ marginTop: 10, display: 'inline-block', background: '#EFBF04', color: '#101828', padding: '5px 14px', borderRadius: 999, fontSize: 10, fontFamily: '"IBM Plex Sans", sans-serif', fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: hovered ? 1 : 0, transform: hovered ? 'translateY(0)' : 'translateY(6px)', transition: 'all 0.25s' }}>
|
||||
Detail →
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
{/* Hero Section */}
|
||||
<section className="relative flex items-center overflow-hidden" style={{ background: '#111D55', height: isMobile ? '88svh' : '100vh' }}>
|
||||
<div className="absolute inset-0 z-0">
|
||||
<img
|
||||
src="/images/gala-saal-overview.jpg"
|
||||
alt="Awards Gala"
|
||||
className="w-full h-full object-cover scale-105"
|
||||
style={{ objectPosition: 'center top' }}
|
||||
/>
|
||||
<div style={{ position:'absolute', inset:0, background: isMobile ? 'linear-gradient(to top, rgba(17,29,85,0.96) 0%, rgba(17,29,85,0.82) 42%, rgba(17,29,85,0.48) 72%, rgba(17,29,85,0.22) 100%)' : 'linear-gradient(to right, #111D55 0%, rgba(17,29,85,0.90) 38%, rgba(17,29,85,0.18) 65%, transparent 100%)' }}></div>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto relative z-10" style={{ padding: isMobile ? '0 24px' : '0 24px' }}>
|
||||
<div style={{ maxWidth: isMobile ? '100%' : '48rem' }}>
|
||||
<div className="inline-flex items-center gap-3 bg-accent/20 border border-accent/30 rounded-full px-4 py-1.5 mb-8 backdrop-blur-sm animate-fade-in" style={{ marginBottom: isMobile ? 20 : undefined }}>
|
||||
<span className="flex h-2 w-2 rounded-full bg-accent"></span>
|
||||
<span className="text-accent text-[10px] uppercase tracking-[0.2em] font-bold">Bewerbungsphase 2026 geschlossen</span>
|
||||
</div>
|
||||
|
||||
<h1 className="font-display font-bold text-white mb-6 leading-[0.9] tracking-tight" style={{ fontSize: isMobile ? 'clamp(1.8rem, 10vw, 3rem)' : 'clamp(3.5rem, 8vw, 6rem)' }}>
|
||||
BAYERNS BESTE <br />
|
||||
<span className="text-accent">MITTELSTÄNDLER.</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-white/70 mb-12 font-light leading-relaxed" style={{ fontSize: isMobile ? '1.0625rem' : '1.375rem', maxWidth: isMobile ? '100%' : '36rem', marginBottom: isMobile ? 28 : undefined }}>
|
||||
Der Bayerische Mittelstandspreis würdigt herausragende Leistungen – von Innovation über Nachhaltigkeit bis hin zur Exzellenz.
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'#101828', background:'#EFBF04', padding:'16px 36px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'background 0.15s, box-shadow 0.2s', width: isMobile ? '100%' : 'auto' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Jetzt kostenlos bewerben <ChevronRight size={16} />
|
||||
</Link>
|
||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 16, alignItems: isMobile ? 'stretch' : 'center' }}>
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'rgba(255,255,255,0.85)', border:'1.5px solid rgba(255,255,255,0.35)', padding:'14px 32px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'border-color 0.15s, color 0.15s', background:'transparent', width: isMobile ? '100%' : 'auto' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#EFBF04'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.35)'; (e.currentTarget as HTMLElement).style.color = 'rgba(255,255,255,0.85)'; }}
|
||||
>
|
||||
Mitglied werden
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setShowVideo(true)}
|
||||
className="flex items-center gap-4 text-white hover:text-accent transition-colors group"
|
||||
style={{ justifyContent: isMobile ? 'center' : 'flex-start' }}
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full border border-white/30 flex items-center justify-center group-hover:border-accent group-hover:bg-accent/10 transition-all">
|
||||
<Play size={20} fill="currentColor" />
|
||||
</div>
|
||||
<span className="uppercase text-xs tracking-widest font-bold">Film abspielen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Partner Bar */}
|
||||
<div className="absolute bottom-0 left-0 w-full bg-white/5 backdrop-blur-xl border-t border-white/10 hidden lg:block">
|
||||
<div className="px-8 py-5 flex items-center justify-between opacity-60 hover:opacity-100 transition-opacity duration-500">
|
||||
{/* Left: label – stays put */}
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.5)', whiteSpace: 'nowrap', flexShrink: 0 }}>Unsere Partner</span>
|
||||
{/* Right: ticker – fixed window, exactly 4 logos wide */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0 }}>
|
||||
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.2)' }} />
|
||||
<div style={{ width: 560, overflow: 'hidden', flexShrink: 0 }}>
|
||||
<PartnerTicker logos={PARTNERS} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Schnell-Check Section */}
|
||||
<section style={{ overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', position: 'relative' }}>
|
||||
{/* Left (desktop) / Bottom (mobile) – text block (light bg, dark text) */}
|
||||
<div style={{ background: '#E4E2E3', padding: isMobile ? '32px 24px 44px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center', order: isMobile ? 2 : 0 }}>
|
||||
{/* Eyebrow + heading – desktop only (on mobile these are overlaid on the image) */}
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Schnell-Check</span>
|
||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.8rem, 2.8vw, 2.5rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
||||
SIND SIE<br />BERECHTIGT?
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
||||
Der Bayerische Mittelstandspreis richtet sich an inhabergeführte KMU im Freistaat. Fünf Kriterien entscheiden, erfüllen Sie alle fünf, steht Ihrer Bewerbung nichts im Weg.
|
||||
</p>
|
||||
{/* Criteria list */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, marginBottom: 36, borderTop: '1px solid #D0D5DD' }}>
|
||||
{[
|
||||
{ label: 'Standort Bayern', desc: 'Sitz oder Betriebsstätte im Freistaat' },
|
||||
{ label: '50–1.500 Mitarbeiter', desc: 'Klassischer Mittelstand' },
|
||||
{ label: 'Min. 3 Geschäftsjahre', desc: 'Markterfahrung und Stabilität' },
|
||||
{ label: 'Privatwirtschaft', desc: 'Kein staatlicher Träger' },
|
||||
{ label: 'Familien- bzw. eigentümergeführt', desc: 'Ihr Unternehmen ist inhabergeführt' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '13px 0', borderBottom: '1px solid #D0D5DD' }}>
|
||||
<div style={{ width: 24, height: 24, background: '#111D55', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1,5 3.5,7.5 9,2" stroke="#EFBF04" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight: 700, color: '#101828' }}>{item.label}</div>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(16,24,40,0.5)' }}>{item.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(16,24,40,0.55)', lineHeight: 1.6, marginTop: 16, marginBottom: 28 }}>
|
||||
Auch ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen mit Schwerpunkt in Bayern kann teilnehmen.
|
||||
</p>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828', background: '#EFBF04', padding: '15px 32px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Ihr Weg zum Preis <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Right (desktop) / Top (mobile) – image; on mobile the heading is overlaid here */}
|
||||
<div style={{ position: 'relative', minHeight: isMobile ? 280 : 560, overflow: 'hidden', order: isMobile ? 1 : 0 }}>
|
||||
<img
|
||||
src="/images/buehne-gewinner.jpg"
|
||||
alt="Preisverleihung Bühne"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: isMobile ? 'center right' : 'center center' }}
|
||||
/>
|
||||
{/* Overlay: mobile = navy shadow bottom-left (for the heading); desktop = leftward fade into text bg */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||
? 'linear-gradient(to top, rgba(3,9,58,0.92) 0%, rgba(3,9,58,0.55) 34%, rgba(3,9,58,0.12) 68%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.55) 0%, transparent 55%)'
|
||||
: 'linear-gradient(to left, transparent 40%, #E4E2E3 100%)' }} />
|
||||
{/* Heading overlay – mobile only */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Schnell-Check</span>
|
||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||
SIND SIE<br />BERECHTIGT?
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
{/* Gold accent line – bottom edge */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, rgba(239,191,4,0.2), #EFBF04, rgba(239,191,4,0.2))', zIndex: 3 }} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Intro Section – editorial split, no radius, no floating badge */}
|
||||
<section style={{ overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '45% 55%' }}>
|
||||
{/* Left – full-bleed image with stats overlay (+ heading on mobile) */}
|
||||
<div style={{ position: 'relative', minHeight: isMobile ? 300 : 600, overflow: 'hidden' }}>
|
||||
<img
|
||||
src="/images/preistraeger-jubel.jpg"
|
||||
alt="BMP Auszeichnung"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top' }}
|
||||
/>
|
||||
{/* Dark overlay */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||
? 'linear-gradient(to top, rgba(3,9,58,0.92) 0%, rgba(3,9,58,0.5) 36%, rgba(3,9,58,0.1) 70%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.5) 0%, transparent 55%)'
|
||||
: 'linear-gradient(to right, transparent 50%, #fff 100%), linear-gradient(to top, rgba(3,9,58,0.82) 0%, rgba(3,9,58,0.50) 60%, transparent 100%)' }} />
|
||||
{/* Heading overlay – mobile only (bottom-left) */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Ein Gewinn für alle</span>
|
||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||
AUSZEICHNUNG<br />FÜR DIE BESTEN.
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
{/* Stats bottom-left – desktop only (mobile shows them below the image) */}
|
||||
{!isMobile && (
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: '32px 40px', background: 'linear-gradient(to top, rgba(3,9,58,0.88) 0%, transparent 100%)', display: 'flex', gap: 36 }}>
|
||||
{[['20+', 'Jahre Exzellenz'], ['0 €', 'Teilnahmegebühr'], ['100%', 'Unabhängig']].map(([v, l]) => (
|
||||
<div key={l}>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 28, fontWeight: 900, color: '#EFBF04', lineHeight: 1 }}>{v}</div>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.6)', marginTop: 4 }}>{l}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right – editorial text */}
|
||||
<div style={{ background: '#fff', padding: isMobile ? '32px 24px 40px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Ein Gewinn für alle</span>
|
||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 3.2vw, 2.8rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
||||
AUSZEICHNUNG<br />FÜR DIE BESTEN.
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
{/* Stats row – mobile only, below the image */}
|
||||
{isMobile && (
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 28, paddingBottom: 24, borderBottom: '1px solid #D0D5DD' }}>
|
||||
{[['20+', 'Jahre Exzellenz'], ['0 €', 'Teilnahmegebühr'], ['100%', 'Unabhängig']].map(([v, l]) => (
|
||||
<div key={l} style={{ flex: 1 }}>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 26, fontWeight: 900, color: '#101828', lineHeight: 1 }}>{v}</div>
|
||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#4A8FC9', marginTop: 6 }}>{l}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28, display: isMobile ? 'none' : 'block' }} />
|
||||
<blockquote style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 18, fontStyle: 'italic', color: '#101828', lineHeight: 1.7, borderLeft: '3px solid #EFBF04', paddingLeft: 20, margin: '0 0 24px' }}>
|
||||
"Ein Unternehmen zu gründen erfordert nicht allein spezielle Fähigkeiten. Es erfordert Persönlichkeit!"
|
||||
</blockquote>
|
||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, margin: '0 0 36px' }}>
|
||||
Wir suchen Vorbilder, die sich trotz aller Risiken nicht scheuen, Visionen in Businesspläne und Ideen in Unternehmungen zu verwandeln, und das mit großem persönlichen Einsatz und Verantwortung für Ihre Mitarbeiter. Seit vielen Jahren würdigen wir herausragende unternehmerische Leistungen im Freistaat.
|
||||
</p>
|
||||
<Link
|
||||
to="/der-bmp"
|
||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: '#101828', background: '#EFBF04', padding: '14px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Mehr über den Preis <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Status Phase Slider */}
|
||||
<StatusSlider />
|
||||
|
||||
{/* Winners Grid – neue Preisträger-Übersicht */}
|
||||
<section style={{ background: '#111D55' }}>
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '72px 80px 48px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'flex-end', justifyContent: 'space-between', gap: isMobile ? 16 : 0 }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04', display: 'block', marginBottom: 12 }}>Success Stories</span>
|
||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: 0, lineHeight: 1 }}>
|
||||
DIE BESTEN UNTERNEHMEN IN BAYERN
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
to="/preistraeger"
|
||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#EFBF04', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: '1px solid #EFBF04', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
Alle Preisträger <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? undefined : 'repeat(4, 1fr)',
|
||||
gridAutoFlow: isMobile ? 'column' : undefined,
|
||||
gridTemplateRows: isMobile ? 'repeat(2, 1fr)' : undefined,
|
||||
gridAutoColumns: isMobile ? '66vw' : undefined,
|
||||
overflowX: isMobile ? 'auto' : undefined,
|
||||
scrollSnapType: isMobile ? 'x mandatory' : undefined,
|
||||
gap: isMobile ? 8 : 0,
|
||||
padding: isMobile ? '0 16px 8px' : 0,
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'none',
|
||||
}}>
|
||||
{WINNERS.filter(w => w.year === 2025).slice(0, 8).map(w => (
|
||||
<HomeWinnerCard key={w.id} winner={w} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection />
|
||||
|
||||
|
||||
{/* Benefits Section – editorial split */}
|
||||
<section style={{ overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', minHeight: isMobile ? 'auto' : 560 }}>
|
||||
|
||||
{/* Left – text on cream */}
|
||||
<div style={{ background: '#E4E2E3', padding: isMobile ? '32px 24px 44px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center', order: isMobile ? 2 : 0 }}>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Mehr als nur ein Preis</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 48px' }}>
|
||||
WARUM SICH DIE<br />TEILNAHME LOHNT.
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Numbered rows – no cards, no shadows */}
|
||||
<div style={{ borderTop: '1px solid #D0D5DD' }}>
|
||||
{[
|
||||
{ num: '01', title: 'Netzwerk', desc: 'Direkter Zugang zu Entscheidern aus Staatsregierung, Wirtschaft und Wissenschaft.' },
|
||||
{ num: '02', title: 'Sichtbarkeit', desc: 'Breite mediale Begleitung durch TV-, Radio- und Print-Partner bayernweit.' },
|
||||
{ num: '03', title: 'Bestätigung', desc: 'Ein unabhängiger Qualitätsnachweis – das stärkste Siegel für Ihren Marktauftritt.' },
|
||||
].map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{ display: 'grid', gridTemplateColumns: '48px 1fr', gap: '0 20px', padding: '24px 0', borderBottom: '1px solid #D0D5DD', cursor: 'default', transition: 'background 0.15s' }}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = '#EAE7E6')}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
|
||||
>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: '#EFBF04', letterSpacing: '0.1em', paddingTop: 2 }}>{item.num}</span>
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 5 }}>{item.title}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 16, color: 'rgba(16,24,40,0.55)', lineHeight: 1.6 }}>{item.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right – full-bleed photo (+ heading on mobile) */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : 'auto', order: isMobile ? 1 : 0 }}>
|
||||
<img
|
||||
src="/images/networking-innenhof.jpg"
|
||||
alt="BMP Gala Netzwerk"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center center', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||
? 'linear-gradient(to top, rgba(3,9,58,0.92) 0%, rgba(3,9,58,0.5) 36%, rgba(3,9,58,0.1) 70%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.5) 0%, transparent 55%)'
|
||||
: 'linear-gradient(to right, #E4E2E3 0%, transparent 30%), linear-gradient(to top, rgba(3,9,58,0.82) 0%, transparent 50%)' }} />
|
||||
{/* Heading overlay – mobile only (bottom-left) */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Mehr als nur ein Preis</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.4rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||
WARUM SICH DIE<br />TEILNAHME LOHNT.
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
{/* Bottom-left label – desktop only */}
|
||||
{!isMobile && (
|
||||
<div style={{ position: 'absolute', bottom: 40, left: 40 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#EFBF04', marginBottom: 6 }}>BMP Gala-Netzwerk</div>
|
||||
<div style={{ width: 32, height: 2, background: '#EFBF04' }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section – with embedded form */}
|
||||
<section style={{ background: '#111D55', position: 'relative', overflow: 'hidden', height: isMobile ? 'auto' : 'calc(100vh - 60px)', display: 'flex', flexDirection: 'column' }} id="bewerben">
|
||||
{/* Subtle radial glow behind left copy */}
|
||||
<div style={{ position: 'absolute', left: -120, top: '50%', transform: 'translateY(-50%)', width: 600, height: 600, borderRadius: '50%', background: 'radial-gradient(circle, rgba(5,26,135,0.35) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', position: 'relative', flex: isMobile ? 'none' : 1, minHeight: 0, overflow: 'hidden' }}>
|
||||
|
||||
{/* Left – pitch copy */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'hidden' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.36em', fontWeight: 700, display: 'block', marginBottom: 16, opacity: 0.8 }}>Chance ergreifen</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(1.8rem, 2.8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 1.0, marginBottom: 16 }}>
|
||||
SCHREIBEN<br />AUCH SIE<br />GESCHICHTE.
|
||||
</h2>
|
||||
<div style={{ width: 32, height: 2, background: '#EFBF04', marginBottom: 20 }} />
|
||||
<p style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
||||
Werden Sie Teil der Wall of Fame des bayerischen Mittelstands – vollständig kostenfrei.
|
||||
</p>
|
||||
|
||||
{/* Benefit rows */}
|
||||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
{[
|
||||
{ num: '01', label: 'Kostenfrei', desc: 'Ohne Teilnahmegebühr' },
|
||||
{ num: '02', label: 'Jury', desc: 'Aus Wissenschaft und Praxis' },
|
||||
{ num: '03', label: 'Sichtbarkeit', desc: 'Medial bayernweit' },
|
||||
{ num: '04', label: 'Gala', desc: 'Verleihung München' },
|
||||
].map(item => (
|
||||
<div key={item.num} style={{ display: 'grid', gridTemplateColumns: '24px 1fr', gap: '0 14px', padding: '13px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, color: 'rgba(239,191,4,0.55)', letterSpacing: '0.1em' }}>{item.num}</span>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.08em' }}>{item.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 14, color: 'rgba(255,255,255,0.45)', textAlign: 'right' }}>{item.desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
{!isMobile && <div style={{ background: 'rgba(255,255,255,0.07)' }} />}
|
||||
|
||||
{/* Right – gold premium panel */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
|
||||
{/* Premium award crosshatch pattern */}
|
||||
<svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }} aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="bmpCross" width="18" height="18" patternUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L18,18 M18,0 L0,18" stroke="rgba(17,29,85,0.055)" strokeWidth="0.65" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#bmpCross)" />
|
||||
</svg>
|
||||
|
||||
{/* Inner vignette – edges slightly deeper gold */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, transparent 55%, rgba(100,70,0,0.25) 100%)', pointerEvents: 'none', zIndex: 0 }} />
|
||||
|
||||
{/* Award ceremony photo */}
|
||||
{!isMobile && (
|
||||
<div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
|
||||
<img
|
||||
src="/images/preisuebergabe.jpg"
|
||||
alt="Preisverleihung Bayerischer Mittelstandspreis"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 25%', filter: 'sepia(0.18) brightness(0.92)' }}
|
||||
/>
|
||||
{/* Blend photo into gold panel */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 14, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>Preisverleihung 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form area */}
|
||||
<div style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile ? '82svh' : undefined, display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>Direktbewerbung 2026</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 24 }}>Kostenlos bewerben</h3>
|
||||
<BewerbungsForm theme="gold" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Footnote row */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 24px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: isMobile ? 6 : 10 }}>Kostenlos und unverbindlich –</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: 'rgba(239,191,4,0.8)' }}>keine Teilnahmegebühr</span>
|
||||
</div>
|
||||
<div style={{ height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.25), transparent)', flexShrink: 0 }} />
|
||||
</section>
|
||||
|
||||
{/* Video Modal */}
|
||||
{showVideo && (
|
||||
<div className="fixed inset-0 z-[200] bg-black flex items-center justify-center p-6 bg-opacity-95">
|
||||
<button
|
||||
onClick={() => setShowVideo(false)}
|
||||
className="absolute top-8 right-8 text-white/60 hover:text-white transition-colors"
|
||||
>
|
||||
<X size={40} />
|
||||
</button>
|
||||
<div className="w-full max-w-6xl aspect-video bg-white/5 rounded-3xl overflow-hidden flex items-center justify-center relative">
|
||||
<div className="text-center">
|
||||
<Play size={80} className="text-accent mx-auto mb-6 opacity-40" />
|
||||
<p className="text-white/40 uppercase tracking-widest text-sm">Video-Player Platzhalter</p>
|
||||
<p className="text-white/60 mt-4 text-xs italic font-body">Hier würde das Image-Video des BMP geladen werden.</p>
|
||||
<Button onClick={() => setShowVideo(false)} className="mt-8" variant="outline">Schließen</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ─── StatusSlider ─────────────────────────────────────────────────────────────
|
||||
|
||||
function StatusSlider() {
|
||||
const isMobile = useIsMobile();
|
||||
const [active, setActive] = useState<number>(ACTIVE_PHASE);
|
||||
const [dragOffset, setDragOffset] = useState(0);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const startX = useRef<number | null>(null);
|
||||
const lastDelta = useRef(0);
|
||||
|
||||
// Swipe hint on mount: peek next slide briefly, then bounce back
|
||||
useEffect(() => {
|
||||
const peek = setTimeout(() => setDragOffset(-Math.round(window.innerWidth * 0.11)), 900);
|
||||
const back = setTimeout(() => setDragOffset(0), 1700);
|
||||
return () => { clearTimeout(peek); clearTimeout(back); };
|
||||
}, []);
|
||||
|
||||
const goTo = (i: number) => setActive(Math.max(0, Math.min(STATUS_PHASES.length - 1, i)));
|
||||
|
||||
const onStart = (x: number) => { startX.current = x; lastDelta.current = 0; setIsDragging(true); };
|
||||
const onMove = (x: number) => {
|
||||
if (startX.current === null) return;
|
||||
const d = x - startX.current;
|
||||
lastDelta.current = d;
|
||||
setDragOffset(d);
|
||||
};
|
||||
const onEnd = () => {
|
||||
if (startX.current === null) return;
|
||||
const d = lastDelta.current;
|
||||
startX.current = null;
|
||||
setIsDragging(false);
|
||||
setDragOffset(0);
|
||||
if (d < -60 && active < STATUS_PHASES.length - 1) goTo(active + 1);
|
||||
else if (d > 60 && active > 0) goTo(active - 1);
|
||||
};
|
||||
|
||||
const curPhase = STATUS_PHASES[active];
|
||||
|
||||
return (
|
||||
<section
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
height: isMobile ? 'auto' : 480,
|
||||
minHeight: isMobile ? 360 : 'auto',
|
||||
cursor: isDragging ? 'grabbing' : 'grab',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
onMouseDown={e => onStart(e.clientX)}
|
||||
onMouseMove={e => onMove(e.clientX)}
|
||||
onMouseUp={onEnd}
|
||||
onMouseLeave={onEnd}
|
||||
onTouchStart={e => onStart(e.touches[0].clientX)}
|
||||
onTouchMove={e => onMove(e.touches[0].clientX)}
|
||||
onTouchEnd={onEnd}
|
||||
>
|
||||
{/* Track */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: `${STATUS_PHASES.length * 100}vw`,
|
||||
transform: `translateX(calc(${-active * 100}vw + ${dragOffset}px))`,
|
||||
transition: isDragging ? 'none' : 'transform 0.62s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
>
|
||||
{STATUS_PHASES.map(phase => (
|
||||
<StatusSlideCard key={phase.num} phase={phase} isMobile={isMobile} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Dots */}
|
||||
<div
|
||||
style={{ position: 'absolute', bottom: 28, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 6, zIndex: 10 }}
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
>
|
||||
{STATUS_PHASES.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => goTo(i)}
|
||||
style={{
|
||||
width: i === active ? 32 : 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
background: i === active ? curPhase.accent : 'rgba(255,255,255,0.22)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: 0,
|
||||
transition: 'all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Prev arrow */}
|
||||
{active > 0 && (
|
||||
<button
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
onClick={() => goTo(active - 1)}
|
||||
style={{ position: 'absolute', left: 28, top: '50%', transform: 'translateY(-50%)', zIndex: 10, background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: '50%', width: 48, height: 48, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: '#fff', fontSize: 22, lineHeight: 1, transition: 'background 0.2s' }}
|
||||
>‹</button>
|
||||
)}
|
||||
|
||||
{/* Next arrow */}
|
||||
{active < STATUS_PHASES.length - 1 && (
|
||||
<button
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
onClick={() => goTo(active + 1)}
|
||||
style={{ position: 'absolute', right: 28, top: '50%', transform: 'translateY(-50%)', zIndex: 10, background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: '50%', width: 48, height: 48, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: '#fff', fontSize: 22, lineHeight: 1, transition: 'background 0.2s' }}
|
||||
>›</button>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── StatusSlideCard ──────────────────────────────────────────────────────────
|
||||
|
||||
function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: boolean }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
background: phase.bg,
|
||||
overflow: 'hidden',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr' : '1fr 360px',
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Left content */}
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '52px 80px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', zIndex: 1, gap: isMobile ? 24 : 0 }}>
|
||||
|
||||
{/* Status badge + phase label */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: `${phase.accent}18`, border: `1px solid ${phase.accent}35`, borderRadius: 999, padding: '5px 14px' }}>
|
||||
{phase.pulse && <span className="animate-pulse" style={{ width: 6, height: 6, borderRadius: '50%', background: phase.accent, display: 'inline-block' }} />}
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.32em', textTransform: 'uppercase', color: phase.accent }}>{phase.tag}</span>
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.15em', textTransform: 'uppercase' }}>{phase.phase}</span>
|
||||
</div>
|
||||
|
||||
{/* Headline + body + CTA */}
|
||||
<div>
|
||||
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(2.4rem, 3.8vw, 3.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 0.94, margin: '0 0 24px' }}>
|
||||
{phase.headline}
|
||||
</h2>
|
||||
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: 17, color: 'rgba(255,255,255,0.68)', lineHeight: 1.8, marginBottom: 32, maxWidth: 520 }}>
|
||||
{phase.body}
|
||||
</p>
|
||||
{phase.cta ? (
|
||||
<Link
|
||||
to={phase.cta.to}
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#101828', background: phase.accent, padding: '14px 32px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, transition: 'opacity 0.2s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
{phase.cta.label} <ChevronRight size={14} />
|
||||
</Link>
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 20, height: 1, background: 'rgba(255,255,255,0.2)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.2em', textTransform: 'uppercase' }}>Kein Handlungsbedarf</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Meta */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 20, height: 1, background: phase.accent, opacity: 0.35 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: `${phase.accent}70`, letterSpacing: '0.18em', textTransform: 'uppercase' }}>{phase.meta}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right visual panel */}
|
||||
{!isMobile && <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 36, padding: '52px 48px', position: 'relative', zIndex: 1, borderLeft: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
{phase.num !== '03' ? (
|
||||
/* Concentric rings for active phases */
|
||||
<div style={{ position: 'relative', width: 180, height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{([0, 20, 44] as const).map((inset, i) => (
|
||||
<div key={inset} style={{ position: 'absolute', inset, borderRadius: '50%', border: `1px solid ${phase.accent}${['12', '25', '45'][i]}` }} />
|
||||
))}
|
||||
<div style={{ width: 76, height: 76, borderRadius: '50%', background: `${phase.accent}15`, border: `1px solid ${phase.accent}55`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 900, color: phase.accent, letterSpacing: '0.05em' }}>{phase.num}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Success stats */
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 68, fontWeight: 900, color: phase.accent, lineHeight: 1, opacity: 0.9 }}>247</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4, marginBottom: 20 }}>Bewerbungen</div>
|
||||
<div style={{ width: 1, height: 28, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
||||
<div style={{ fontFamily: FF, fontSize: 44, fontWeight: 900, color: '#fff', lineHeight: 1 }}>3</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4 }}>Preisträger</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Membership CTA – for active phases only */}
|
||||
{phase.num !== '03' && (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.14em',
|
||||
textTransform: 'uppercase',
|
||||
color: phase.accent,
|
||||
border: `1.5px solid ${phase.accent}55`,
|
||||
background: 'transparent',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: '11px 22px',
|
||||
transition: 'border-color 0.2s, color 0.2s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
(e.currentTarget as HTMLElement).style.borderColor = phase.accent;
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLElement).style.borderColor = `${phase.accent}55`;
|
||||
}}
|
||||
>
|
||||
Mitglied werden <ChevronRight size={13} />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
265
src/spa/pages/Impressum.tsx
Normal file
265
src/spa/pages/Impressum.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import React from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const ANTHRA = '#1A2030';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#EFE5E3';
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
|
||||
const TOC = [
|
||||
{ id: 'betreiber', label: 'Angaben gem. §5 TMG' },
|
||||
{ id: 'datenschutz', label: 'Datenschutz' },
|
||||
{ id: 'kontaktformular', label: 'Kontaktformular' },
|
||||
{ id: 'betroffenenrechte', label: 'Betroffenenrechte' },
|
||||
{ id: 'aufsichtsbehoerde', label: 'Beschwerderecht' },
|
||||
{ id: 'verantwortlicher', label: 'Verantwortlicher' },
|
||||
{ id: 'privacy-policy', label: 'Privacy Policy' },
|
||||
{ id: 'bildnachweise', label: 'Bildnachweise' },
|
||||
{ id: 'haftung', label: 'Haftungsausschluss' },
|
||||
];
|
||||
|
||||
const ADDR = (
|
||||
<>
|
||||
VM Verbands-Management GmbH<br />
|
||||
Edelsbergstr. 8<br />
|
||||
80686 München<br />
|
||||
Tel. +49 (0) 89 – 57007-0<br />
|
||||
Fax +49 (0) 89 – 57007 260<br />
|
||||
info@vm-verbands-management.de<br />
|
||||
HRB 110222 AG München, gesetzl. vertreten durch Friedrich Marx
|
||||
</>
|
||||
);
|
||||
|
||||
export default function Impressum() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div style={{ background: CREAM, minHeight: '100vh' }}>
|
||||
|
||||
{/* ── HERO ──────────────────────────────────────────────────────────── */}
|
||||
<section style={{ background: ANTHRA, padding: isMobile ? '48px 24px 40px' : '64px 80px 52px', position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.2), transparent)` }} />
|
||||
<Link
|
||||
to="/"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: 'rgba(255,255,255,0.4)', textDecoration: 'none', marginBottom: 32, transition: 'color 0.15s' }}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.4)')}
|
||||
>
|
||||
<ArrowLeft size={12} /> Zurück zur Website
|
||||
</Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: GOLD }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: GOLD }}>Legal</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontWeight: 900, fontSize: isMobile ? 'clamp(1.8rem, 7vw, 2.6rem)' : 'clamp(2.4rem, 4vw, 3.2rem)', color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.02, margin: '0 0 12px' }}>
|
||||
Impressum
|
||||
</h1>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.45)', margin: 0 }}>
|
||||
Angaben gemäß §5 TMG · <Link to="/datenschutz" style={{ color: 'rgba(239,191,4,0.7)', textDecoration: 'none' }}>Zur Datenschutzerklärung →</Link>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* ── BODY ──────────────────────────────────────────────────────────── */}
|
||||
<div style={{ maxWidth: 1100, margin: '0 auto', padding: isMobile ? '40px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '200px 1fr', gap: isMobile ? 40 : 80, alignItems: 'start' }}>
|
||||
|
||||
{/* LEFT – sticky TOC */}
|
||||
{!isMobile && (
|
||||
<nav style={{ position: 'sticky', top: 80 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: GOLD, marginBottom: 16 }}>Inhalt</div>
|
||||
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{TOC.map(item => (
|
||||
<li key={item.id}>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
style={{ display: 'block', fontFamily: FB, fontSize: 12, color: 'rgba(17,29,85,0.5)', textDecoration: 'none', padding: '5px 0 5px 12px', borderLeft: '2px solid transparent', transition: 'color 0.15s, border-color 0.15s', lineHeight: 1.4 }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = NAVY; (e.currentTarget as HTMLElement).style.borderLeftColor = GOLD; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'rgba(17,29,85,0.5)'; (e.currentTarget as HTMLElement).style.borderLeftColor = 'transparent'; }}
|
||||
>{item.label}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{ marginTop: 32, paddingTop: 20, borderTop: '1px solid rgba(17,29,85,0.1)' }}>
|
||||
<Link to="/datenschutz" style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.14em', color: NAVY, textDecoration: 'none' }}>
|
||||
Datenschutzerklärung →
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* RIGHT – content */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 56 }}>
|
||||
|
||||
<Section id="betreiber" title="Angaben gemäß §5 TMG">
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY, marginBottom: 8 }}>Informationen über den Verantwortlichen</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY, marginBottom: 8 }}>Angaben zum Webseitenbetreiber</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
<p style={body}>Inhaltlich Verantwortlicher gemäß TMG: VM Verbands-Management GmbH, gesetzl. vertreten durch Friedrich Marx</p>
|
||||
</Section>
|
||||
|
||||
<Section id="datenschutz" title="Datenschutz & Datenverarbeitung">
|
||||
<p style={body}>Alle personenbezogenen Informationen werden vertraulich und gemäß den gesetzlichen Vorschriften behandelt, wie in der <Link to="/datenschutz" style={{ color: NAVY, fontWeight: 600 }}>Datenschutzerklärung</Link> erläutert. Den Schutz der persönlichen Daten nehmen wir als Webseitenbetreiber sehr ernst.</p>
|
||||
<p style={body}>Ein Besuch unserer Webseiten ist möglich, ohne Angaben zu Ihrer Person zu machen. Es werden bei jedem Zugriff auf unsere Website Nutzungsdaten durch Ihren Internetbrowser übermittelt und in Protokolldaten (Server-Logfiles) gespeichert. Zu diesen gespeicherten Daten gehören z. B. Name der aufgerufenen Seite, Datum und Uhrzeit des Abrufs, übertragene Datenmenge und der anfragende Provider (IP-Adresse). Diese Daten dienen ausschließlich der Gewährleistung eines störungsfreien Betriebs unserer Website und zur Verbesserung unseres Angebotes. Eine Zuordnung dieser Daten zu einer bestimmten Person ist nicht möglich.</p>
|
||||
<p style={body}>Es erfolgt keine Zusammenführung dieser Daten mit anderen Datenquellen.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="kontaktformular" title="Erhebung und Verarbeitung beim Kontaktformular">
|
||||
<p style={body}>Sofern Sie uns über unsere Kontaktdaten eine E-Mail übersenden bzw. uns über das Kontaktformular informieren, erheben wir Ihre personenbezogenen Daten (Name, E-Mail-Adresse, Telefonnummer, Betreff, Nachrichtentext) nur in dem von Ihnen zur Verfügung gestellten Umfang. Die Datenverarbeitung dient dem Zweck der Kontaktaufnahme.</p>
|
||||
<p style={body}>Mit Absenden Ihrer Nachricht willigen Sie in die Verarbeitung der übermittelten Daten ein.</p>
|
||||
<Legal>Eine Verarbeitung erfolgt auf Grundlage des Art. 6 (1) lit. a DSGVO mit Ihrer Einwilligung.</Legal>
|
||||
<p style={body}>Sie können diese Einwilligung jederzeit durch Mitteilung an uns widerrufen. Ihre E-Mail-Adresse wird nur zur Bearbeitung Ihrer Anfrage genutzt. Ihre Daten werden anschließend gelöscht, sofern Sie der weitergehenden Verarbeitung und Nutzung nicht zugestimmt haben.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="betroffenenrechte" title="Betroffenenrechte">
|
||||
<p style={body}>Bei Vorliegen der gesetzlichen Voraussetzungen stehen Ihnen folgende Rechte nach Art. 15 bis 20 DSGVO zu:</p>
|
||||
<ul style={{ ...body, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<li>Recht auf Auskunft</li>
|
||||
<li>Recht auf Berichtigung</li>
|
||||
<li>Recht auf Löschung</li>
|
||||
<li>Recht auf Einschränkung der Verarbeitung</li>
|
||||
<li>Recht auf Datenübertragbarkeit</li>
|
||||
</ul>
|
||||
<Legal>Außerdem steht Ihnen nach Art. 21 (1) DSGVO ein Widerspruchsrecht gegen die Verarbeitungen zu, die auf Art. 6 (1) f DSGVO beruhen, sowie gegen die Verarbeitung zum Zwecke von Direktwerbung.</Legal>
|
||||
<p style={body}>Auf Wunsch können Sie uns kontaktieren. Die Kontaktdaten finden Sie unter <Link to="/kontakt" style={{ color: NAVY, fontWeight: 600 }}>Kontakt</Link>.</p>
|
||||
</Section>
|
||||
|
||||
<Section id="aufsichtsbehoerde" title="Beschwerderecht bei der Aufsichtsbehörde">
|
||||
<p style={body}>Sie haben gemäß Art. 77 DSGVO das Recht, sich bei der Aufsichtsbehörde zu beschweren, wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer personenbezogenen Daten nicht rechtmäßig erfolgt.</p>
|
||||
<p style={{ ...body, fontWeight: 600, color: NAVY }}>Die zuständige Aufsichtsbehörde:</p>
|
||||
<AddressBlock>
|
||||
Bayerisches Landesamt für Datenschutzaufsicht<br />
|
||||
Postfach 606<br />
|
||||
91511 Ansbach<br />
|
||||
Deutschland<br />
|
||||
Tel. 09853-1300<br />
|
||||
Fax 09853-981300<br />
|
||||
E-Mail: poststelle@lda.bayern.de
|
||||
</AddressBlock>
|
||||
</Section>
|
||||
|
||||
<Section id="verantwortlicher" title="Name und Anschrift des Verantwortlichen">
|
||||
<p style={body}>Verantwortlicher im Sinne der Datenschutz-Grundverordnung und sonstiger datenschutzrechtlicher Bestimmungen ist der Webseitenbetreiber:</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
</Section>
|
||||
|
||||
<Section id="privacy-policy" title="Erklärung zum Datenschutz (Privacy Policy)">
|
||||
<p style={{ ...body, fontStyle: 'italic', borderLeft: `3px solid ${GOLD}`, paddingLeft: 16, marginBottom: 32 }}>
|
||||
Die Betreiber des Portals nehmen den Schutz der privaten Daten ernst. Die besondere Beachtung der Privatsphäre bei der Verarbeitung persönlicher Daten ist ein wichtiges Anliegen. Persönliche Daten werden gemäß den Bestimmungen des Bundesdatenschutzgesetzes BDSG verwendet; die Betreiber dieser Website verpflichten sich zur Verschwiegenheit. Diese Webseiten können Links zu Webseiten anderer Anbieter enthalten, auf die sich diese Datenschutzerklärung nicht erstreckt. Weitere wichtige Informationen finden sich auch in den Allgemeinen Nutzungsbedingungen.
|
||||
</p>
|
||||
|
||||
<SubSection num="1" title="Personenbezogene Daten">
|
||||
<p style={body}>Personenbezogene Daten sind Informationen, die dazu genutzt werden können, die Identität zu erfahren. Darunter fallen Informationen wie richtiger Name, Adresse, Postanschrift, Telefonnummer. Informationen, die nicht direkt mit der wirklichen Identität in Verbindung gebracht werden (wie zum Beispiel favorisierte Webseiten oder Anzahl der Nutzer einer Site) fallen nicht darunter.</p>
|
||||
<p style={body}>Die statistische Auswertung anonymisierter Datensätze bleibt vorbehalten. Wir nutzen die persönlichen Daten zu Zwecken der technischen Administration der Webseiten und zur Kundenverwaltung nur im jeweils dafür erforderlichen Umfang. Darüber hinaus werden persönliche Daten nur dann gespeichert, wenn diese freiwillig angegeben werden.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="2" title="Weitergabe personenbezogener Daten">
|
||||
<p style={body}>Wir verwenden personenbezogene Informationen nur für diese Webseite. Wir geben die Informationen nicht ohne ausdrückliches Einverständnis an Dritte weiter. Sollten im Rahmen der Auftragsdatenverarbeitung Daten an Dienstleister weitergegeben werden, so sind diese an das Bundesdatenschutzgesetz BDSG, andere gesetzliche Vorschriften und an diese Privacy Policy gebunden.</p>
|
||||
<p style={body}>Erhebungen beziehungsweise Übermittlungen persönlicher Daten an staatliche Einrichtungen und Behörden erfolgen nur im Rahmen zwingender Rechtsvorschriften.</p>
|
||||
<p style={body}>Bitte beachten Sie gesondert die Hinweise zur Datenverarbeitung im Zusammenhang mit Google reCAPTCHA unter <Link to="/datenschutz#recaptcha" style={{ color: NAVY }}>Datenschutz</Link>.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="3" title="Kinder">
|
||||
<p style={body}>Personen unter 18 Jahren sollten ohne Zustimmung der Eltern oder Erziehungsberechtigten keine personenbezogenen Daten an uns übermitteln. Wir fordern keine personenbezogenen Daten von Kindern an, sammeln diese nicht und geben sie nicht an Dritte weiter.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="4" title="Recht auf Widerruf">
|
||||
<p style={body}>Wenn Sie uns personenbezogene Daten überlassen haben, können Sie diese jederzeit im Benutzerprofil wieder ändern und löschen. Für eine vollständige Löschung des Accounts bitte an den Webmaster wenden. Bis zu diesem Zeitpunkt erfolgte Beiträge in Foren, Kommentaren, Terminankündigungen und Artikeln bleiben allerdings unter Umständen erhalten – Informationen dazu auch bei den allgemeinen Nutzungsbedingungen.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="5" title="Links zu anderen Websites">
|
||||
<p style={body}>Unser Online-Angebot enthält Links zu anderen Websites. Wir haben keinen Einfluss darauf, dass deren Betreiber die Datenschutzbestimmungen einhalten.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="6" title="Beiträge">
|
||||
<p style={body}>Die Beiträge auf unserer Seite sind für jeden zugänglich. Beiträge sollten vor der Veröffentlichung sorgfältig darauf überprüft werden, ob sie Angaben enthalten, die nicht für die Öffentlichkeit bestimmt sind. Die Beiträge werden möglicherweise in Suchmaschinen erfasst und auch ohne gezielten Aufruf dieser Website weltweit zugreifbar.</p>
|
||||
<p style={body}>Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren Betreiber verantwortlich. Alle hier verwendeten Namen, Begriffe, Zeichen und Grafiken können Marken- oder Warenzeichen im Besitze ihrer rechtlichen Eigentümer sein. Die Rechte aller erwähnten und benutzten Marken- und Warenzeichen liegen ausschließlich bei deren Besitzern.</p>
|
||||
</SubSection>
|
||||
|
||||
<SubSection num="7" title="Fragen und Kommentare">
|
||||
<p style={body}>Bei Fragen und für Anregungen und Kommentare zum Thema Datenschutz bitte per Mail an den Webmaster wenden.</p>
|
||||
<AddressBlock>{ADDR}</AddressBlock>
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
<Section id="bildnachweise" title="Bildnachweise">
|
||||
<ul style={{ ...body, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<li>Headline Maximilianeum: © Dietmar Egerer, München</li>
|
||||
<li>Ingrid Großmann, Taufkirchen</li>
|
||||
<li>EWIF Deutschland, München</li>
|
||||
<li>icon.icons.com</li>
|
||||
<li>Bilder zum Bayerischen Mittelstandspreis: © VM Verbands-Management GmbH, gesetzl. vertreten durch Friedrich Marx</li>
|
||||
</ul>
|
||||
</Section>
|
||||
|
||||
<Section id="haftung" title="Haftungsausschluss">
|
||||
<p style={body}>Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren Betreiber verantwortlich.</p>
|
||||
<p style={body}>Alle hier verwendeten Namen, Begriffe, Zeichen und Grafiken können Marken- oder Warenzeichen im Besitze ihrer rechtlichen Eigentümer sein. Die Rechte aller erwähnten und benutzten Marken- und Warenzeichen liegen ausschließlich bei deren Besitzern.</p>
|
||||
<p style={body}>Einen allgemeinen Haftungsausschluss finden Sie unter:{' '}
|
||||
<a href="https://www.disclaimer.de/disclaimer.htm" target="_blank" rel="noopener noreferrer" style={{ color: NAVY }}>disclaimer.de</a>
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── shared styles ── */
|
||||
const body: React.CSSProperties = {
|
||||
fontFamily: '"Inter", sans-serif',
|
||||
fontSize: 15,
|
||||
color: 'rgba(58,58,58,0.82)',
|
||||
lineHeight: 1.85,
|
||||
margin: '0 0 16px',
|
||||
overflowWrap: 'anywhere',
|
||||
};
|
||||
|
||||
function Section({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div id={id} style={{ scrollMarginTop: 80 }}>
|
||||
<div style={{ borderLeft: `3px solid ${GOLD}`, paddingLeft: 16, marginBottom: 28 }}>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.2em', color: NAVY, margin: 0 }}>
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<div style={{ background: '#fff', padding: 'clamp(20px, 5vw, 28px) clamp(20px, 5vw, 32px)', boxShadow: '0 1px 3px rgba(0,0,0,0.06)' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SubSection({ num, title, children }: { num: string; title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div style={{ marginBottom: 28 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.12em' }}>{num}.</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: NAVY, margin: 0 }}>{title}</h3>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AddressBlock({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<address style={{ fontFamily: '"Inter", sans-serif', fontSize: isMobile ? 15 : 14, color: 'rgba(58,58,58,0.75)', lineHeight: 1.9, fontStyle: 'normal', background: 'rgba(17,29,85,0.04)', padding: '16px 20px', borderLeft: '2px solid rgba(239,191,4,0.4)', marginBottom: 16, overflowWrap: 'anywhere' }}>
|
||||
{children}
|
||||
</address>
|
||||
);
|
||||
}
|
||||
|
||||
function Legal({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: isMobile ? 15 : 14, color: NAVY, lineHeight: 1.75, background: 'rgba(17,29,85,0.04)', padding: '12px 16px', borderLeft: `3px solid ${NAVY}`, margin: '0 0 16px', fontWeight: 500, overflowWrap: 'anywhere' }}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
1479
src/spa/pages/MitgliedWerden.tsx
Normal file
1479
src/spa/pages/MitgliedWerden.tsx
Normal file
File diff suppressed because it is too large
Load Diff
876
src/spa/pages/Netzwerk.tsx
Normal file
876
src/spa/pages/Netzwerk.tsx
Normal file
@@ -0,0 +1,876 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { ChevronRight, X, ArrowRight, Globe, Linkedin, Building2, MapPin, ExternalLink } from 'lucide-react';
|
||||
import SponsoringForm from '@/spa/components/forms/SponsoringForm';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#E4E2E3';
|
||||
|
||||
const ROLE_COLOR: Record<string, string> = {
|
||||
'Hauptsponsor': '#EFBF04',
|
||||
'Sponsor': '#2563EB',
|
||||
'Finanzpartner': '#16A34A',
|
||||
'Partner': '#111D55',
|
||||
'Medienpartner': '#DC2626',
|
||||
};
|
||||
|
||||
// ── PersonCard (Schirmherrschaft) ─────────────────────────────────────────────
|
||||
|
||||
interface PersonCardProps {
|
||||
imgSrc: string;
|
||||
imgAlt: string;
|
||||
name: string;
|
||||
titleLine1: string;
|
||||
titleLine2: string;
|
||||
institution: string;
|
||||
borderRight?: boolean;
|
||||
}
|
||||
|
||||
function PersonCard({ imgSrc, imgAlt, name, titleLine1, titleLine2, institution, borderRight }: PersonCardProps) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileCard = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
style={{ borderRight: borderRight && !isMobileCard ? '1px solid rgba(255,255,255,0.08)' : 'none', display: 'flex', flexDirection: 'column', ...(isMobileCard ? { minWidth: '85vw', maxWidth: '85vw', flexShrink: 0, scrollSnapAlign: 'start', borderRadius: 16, overflow: 'hidden' } : {}) }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<div style={{ position: 'relative', height: isMobileCard ? 250 : 320, overflow: 'hidden', width: '100%' }}>
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt={imgAlt}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', filter: hovered ? 'grayscale(0%)' : 'grayscale(100%)', transition: 'filter 0.55s ease, transform 0.55s ease', transform: hovered ? 'scale(1.04)' : 'scale(1)' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(3,9,58,0.8) 0%, transparent 60%)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 3, background: GOLD }} />
|
||||
</div>
|
||||
<div style={{ padding: isMobileCard ? '28px 24px' : '36px 40px', background: NAVY, flex: 1 }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 20, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 10 }}>{name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 400, color: `${GOLD}CC`, lineHeight: 1.6, marginBottom: 2 }}>{titleLine1}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 400, color: 'rgba(255,255,255,0.5)', lineHeight: 1.6 }}>{titleLine2}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.2em', color: 'rgba(255,255,255,0.3)', marginTop: 12 }}>{institution}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── JuryCard ──────────────────────────────────────────────────────────────────
|
||||
|
||||
interface JuryMember {
|
||||
name: string;
|
||||
role: string;
|
||||
bio: string;
|
||||
img: string;
|
||||
}
|
||||
|
||||
const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileCard = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ borderLeft: !isMobileCard && idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none', display: 'flex', flexDirection: 'column', cursor: 'default', background: hovered ? 'rgba(255,255,255,0.03)' : 'transparent', transition: 'background 0.2s', ...(isMobileCard ? { minWidth: '74vw', maxWidth: '74vw', flexShrink: 0, scrollSnapAlign: 'start', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 16, overflow: 'hidden' } : {}) }}
|
||||
>
|
||||
<div style={{ position: 'relative', overflow: 'hidden', aspectRatio: '3/4' }}>
|
||||
<img src={member.img} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '40%', background: 'linear-gradient(to top, rgba(3,9,58,0.65), transparent)' }} />
|
||||
</div>
|
||||
<div style={{ padding: isMobileCard ? '28px 28px 32px' : '32px 32px 36px' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 20, fontWeight: 700, color: '#fff', marginBottom: 5 }}>{member.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 12, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.16em', fontWeight: 700, marginBottom: 14 }}>{member.role}</div>
|
||||
<div style={{ width: hovered ? '100%' : 24, height: 1, background: GOLD, opacity: 0.4, transition: 'width 0.4s ease', marginBottom: 14 }} />
|
||||
<div style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.55)', lineHeight: 1.7 }}>{member.bio}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── JuryChairCard (Vorsitz – prominent) ───────────────────────────────────────
|
||||
|
||||
const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileCard = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobileCard ? '1fr' : '380px 1fr',
|
||||
gap: 0,
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
border: `1px solid ${GOLD}40`,
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative', overflow: 'hidden', aspectRatio: isMobileCard ? '16/10' : '3/4', minHeight: isMobileCard ? 240 : 'auto' }}>
|
||||
<img src={member.img} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '45%', background: 'linear-gradient(to top, rgba(3,9,58,0.7), transparent)' }} />
|
||||
</div>
|
||||
<div style={{ padding: isMobileCard ? '32px 28px 36px' : '56px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<span style={{ alignSelf: 'flex-start', fontFamily: FF, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: NAVY, background: GOLD, padding: '7px 16px', borderRadius: 999, marginBottom: 24 }}>
|
||||
Jury-Vorsitz
|
||||
</span>
|
||||
<div style={{ fontFamily: FF, fontSize: isMobileCard ? 26 : 34, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, marginBottom: 10 }}>{member.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 13, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.18em', fontWeight: 700, marginBottom: 24 }}>{member.role}</div>
|
||||
<div style={{ width: 48, height: 2, background: GOLD, opacity: 0.5, marginBottom: 24 }} />
|
||||
<div style={{ fontFamily: FB, fontSize: isMobileCard ? 18 : 21, color: 'rgba(255,255,255,0.6)', lineHeight: 1.7, maxWidth: 620 }}>{member.bio}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── PartnerCell ───────────────────────────────────────────────────────────────
|
||||
|
||||
interface Partner {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
tier: 1 | 2 | 3;
|
||||
desc: string;
|
||||
fullDesc: string;
|
||||
logo: React.ReactNode;
|
||||
industry: string;
|
||||
location: string;
|
||||
website: string;
|
||||
heroImg?: string;
|
||||
linkedin?: string;
|
||||
}
|
||||
|
||||
const PartnerCell: React.FC<{ partner: Partner; idx: number; total: number; onClick: () => void }> = ({ partner, idx, total, onClick }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileCell = useIsMobile();
|
||||
const borderLeft = isMobileCell ? 'none' : (idx % 4 === 0 ? 'none' : '1px solid rgba(3,9,58,0.08)');
|
||||
const showBottom = isMobileCell ? idx < total - 1 : idx < 8;
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ padding: isMobileCell ? '24px 20px' : '36px 32px', borderLeft, borderBottom: showBottom ? '1px solid rgba(3,9,58,0.08)' : 'none', display: 'flex', flexDirection: 'column', gap: 16, cursor: 'pointer', background: hovered ? '#E8E5E4' : 'transparent', transition: 'background 0.15s' }}
|
||||
>
|
||||
<div style={{ height: 48, display: 'flex', alignItems: 'center' }}>{partner.logo}</div>
|
||||
<div style={{ width: hovered ? 48 : 20, height: 1, background: GOLD, transition: 'width 0.3s ease' }} />
|
||||
<div style={{ fontFamily: FF, fontSize: 18, fontWeight: 700, color: '#101828' }}>{partner.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.2em', fontWeight: 700 }}>{partner.role}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.45)', lineHeight: 1.6, flexGrow: 1 }}>{partner.desc}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
Details <ArrowRight size={10} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── SponsorCell (tiered) ──────────────────────────────────────────────────────
|
||||
|
||||
const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx: number; cols: number; onClick: () => void }> = ({ partner, variant, idx, cols, onClick }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobileCell = useIsMobile();
|
||||
const lg = variant === 'lg';
|
||||
const sm = variant === 'sm';
|
||||
const lastCol = (idx % cols) === cols - 1;
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{
|
||||
padding: isMobileCell ? '24px 20px' : lg ? '40px 36px' : sm ? '28px 28px' : '32px 32px',
|
||||
borderRight: !lastCol && !isMobileCell ? '1px solid rgba(3,9,58,0.08)' : 'none',
|
||||
borderBottom: '1px solid rgba(3,9,58,0.08)',
|
||||
display: 'flex', flexDirection: 'column', gap: 14,
|
||||
cursor: 'pointer',
|
||||
background: hovered ? '#E8E5E4' : 'transparent',
|
||||
transition: 'background 0.15s',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{lg && (
|
||||
<span style={{ position: 'absolute', top: 18, right: 18, fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: '#101828', background: GOLD, padding: '4px 10px', borderRadius: 100, zIndex: 1 }}>Premium</span>
|
||||
)}
|
||||
<div style={{ height: lg ? 52 : 44, display: 'flex', alignItems: 'center' }}>{partner.logo}</div>
|
||||
<div style={{ width: hovered ? 48 : 20, height: 1, background: GOLD, transition: 'width 0.3s ease' }} />
|
||||
<div style={{ fontFamily: FF, fontSize: lg ? 22 : 18, fontWeight: 700, color: '#101828', letterSpacing: '-0.01em', lineHeight: 1.15 }}>{partner.name}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: sm ? 10 : 11, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.2em', fontWeight: 700 }}>{partner.role}</div>
|
||||
{!sm && <div style={{ fontFamily: FB, fontSize: lg ? 17 : 16, color: 'rgba(16,24,40,0.45)', lineHeight: 1.6, flexGrow: 1 }}>{partner.desc}</div>}
|
||||
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
Details <ArrowRight size={10} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── PartnerModal ─────────────────────────────────────────────────────────────
|
||||
|
||||
function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => void }) {
|
||||
const accentColor = ROLE_COLOR[partner.role] ?? GOLD;
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => { document.body.style.overflow = ''; };
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
||||
style={{
|
||||
position: 'fixed', inset: 0, zIndex: 500,
|
||||
background: 'rgba(0,0,0,0.4)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
WebkitBackdropFilter: 'blur(10px)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
padding: 24, overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
background: '#fff', width: '100%', maxWidth: 720,
|
||||
boxShadow: '0 32px 80px rgba(0,0,0,0.35)',
|
||||
overflow: 'hidden', flexShrink: 0,
|
||||
maxHeight: '92vh', overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
{/* ── Hero ── */}
|
||||
<div style={{ position: 'relative', height: 220, flexShrink: 0, overflow: 'hidden' }}>
|
||||
{/* Background */}
|
||||
{partner.heroImg
|
||||
? <img src={partner.heroImg} alt="" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
: <div style={{ position: 'absolute', inset: 0, background: `linear-gradient(135deg, ${accentColor}33 0%, ${NAVY} 70%)` }} />
|
||||
}
|
||||
{/* Gradient to white */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0) 35%, rgba(255,255,255,0.6) 75%, #fff 100%)' }} />
|
||||
|
||||
{/* Category badge – top left */}
|
||||
<div style={{ position: 'absolute', top: 20, left: 24 }}>
|
||||
<span style={{
|
||||
background: 'rgba(255,255,255,0.15)',
|
||||
backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
|
||||
border: '1px solid rgba(255,255,255,0.25)',
|
||||
color: '#fff', padding: '5px 13px',
|
||||
fontFamily: FF, fontSize: 9, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.22em',
|
||||
display: 'inline-block',
|
||||
}}>
|
||||
{partner.role}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Social + Close – top right */}
|
||||
<div style={{ position: 'absolute', top: 16, right: 20, display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
{partner.linkedin && (
|
||||
<a href={partner.linkedin} target="_blank" rel="noopener noreferrer" style={{
|
||||
width: 36, height: 36, borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.92)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: NAVY, textDecoration: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.18)',
|
||||
}}>
|
||||
<Linkedin size={14} />
|
||||
</a>
|
||||
)}
|
||||
{partner.website && (
|
||||
<a href={`https://${partner.website}`} target="_blank" rel="noopener noreferrer" style={{
|
||||
width: 36, height: 36, borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.92)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: NAVY, textDecoration: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.18)',
|
||||
}}>
|
||||
<Globe size={14} />
|
||||
</a>
|
||||
)}
|
||||
<button onClick={onClose} style={{
|
||||
width: 36, height: 36, borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.92)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
border: 'none', cursor: 'pointer',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.18)', color: NAVY,
|
||||
}}>
|
||||
<X size={15} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Logo box – bottom left (overflows hero) */}
|
||||
<div style={{
|
||||
position: 'absolute', bottom: -28, left: 28,
|
||||
width: 80, height: 80, background: '#fff',
|
||||
borderRadius: 12, boxShadow: '0 4px 24px rgba(0,0,0,0.14)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
padding: 10, zIndex: 2,
|
||||
}}>
|
||||
{partner.logo}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Content ── */}
|
||||
<div style={{ padding: '52px 32px 32px' }}>
|
||||
<h2 style={{
|
||||
fontFamily: FF, fontSize: 'clamp(1.5rem, 3vw, 2rem)',
|
||||
fontWeight: 900, color: '#101828',
|
||||
letterSpacing: '-0.025em', lineHeight: 1.05,
|
||||
margin: '0 0 10px',
|
||||
}}>
|
||||
{partner.name}
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.65, margin: '0 0 24px' }}>
|
||||
{partner.desc}
|
||||
</p>
|
||||
|
||||
{/* 3 info cards */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginBottom: 28 }}>
|
||||
{[
|
||||
{ Icon: Building2, label: 'Branche', value: partner.industry },
|
||||
{ Icon: MapPin, label: 'Standort', value: partner.location },
|
||||
{ Icon: Globe, label: 'Web', value: partner.website },
|
||||
].map(({ Icon, label, value }) => (
|
||||
<div key={label} style={{ background: 'hsl(220,40%,97%)', padding: '14px 16px', borderRadius: 8 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
|
||||
<Icon size={11} color="rgba(16,24,40,0.35)" />
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'rgba(16,24,40,0.35)' }}>{label}</span>
|
||||
</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 17, fontWeight: 600, color: '#101828', lineHeight: 1.4, wordBreak: 'break-word' }}>{value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<div style={{ height: 1, background: 'rgba(16,24,40,0.08)', marginBottom: 28 }} />
|
||||
|
||||
{/* Über section */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
|
||||
<div style={{ width: 4, height: 20, background: accentColor, borderRadius: 2, flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828' }}>
|
||||
Über das Unternehmen
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.6)', lineHeight: 1.82, marginBottom: 36 }}>
|
||||
{partner.fullDesc}
|
||||
</p>
|
||||
|
||||
{/* CTAs */}
|
||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
{partner.website && (
|
||||
<a
|
||||
href={`https://${partner.website}`}
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
style={{
|
||||
padding: '13px 28px', background: GOLD, color: '#101828',
|
||||
fontFamily: FF, fontSize: 11, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
transition: 'background 0.15s, box-shadow 0.2s',
|
||||
}}
|
||||
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = '#FFD130'; el.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }}
|
||||
>
|
||||
Website besuchen <ExternalLink size={12} />
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
padding: '13px 28px', background: 'transparent',
|
||||
color: 'rgba(16,24,40,0.45)', border: '1px solid rgba(16,24,40,0.12)',
|
||||
fontFamily: FF, fontSize: 11, fontWeight: 700,
|
||||
textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
cursor: 'pointer', transition: 'all 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = NAVY; el.style.color = '#fff'; el.style.borderColor = NAVY; }}
|
||||
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = 'transparent'; el.style.color = 'rgba(16,24,40,0.45)'; el.style.borderColor = 'rgba(16,24,40,0.12)'; }}
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Footer strip ── */}
|
||||
<div style={{
|
||||
background: 'hsl(220,20%,96%)', padding: '14px 32px',
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
borderTop: '1px solid rgba(16,24,40,0.06)',
|
||||
}}>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(16,24,40,0.3)' }}>
|
||||
Bayerischer Mittelstandspreis 2026
|
||||
</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: accentColor }}>
|
||||
Offizieller {partner.role}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Netzwerk ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const Netzwerk: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [selectedPartner, setSelectedPartner] = useState<Partner | null>(null);
|
||||
|
||||
const juryMembers: JuryMember[] = [
|
||||
{ name: 'Prof. Dr. Klaus Bergmann', role: 'Vorsitzender / LMU München', bio: 'Spezialist für KMU-Strategien und Innovations-Ökosysteme.', img: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=400' },
|
||||
{ name: 'Dr. Sabine Hofmann', role: 'IHK Bayern', bio: 'Expertin für digitale Transformation und regionale Wertschöpfung.', img: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400' },
|
||||
{ name: 'Maximilian Reiter', role: 'Unternehmer', bio: 'CEO der Reiter Group, bringt die wertvolle Unternehmer-Perspektive ein.', img: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=400' },
|
||||
{ name: 'Dr. Elena Fischer', role: 'TU München', bio: 'Lehrstuhl für nachhaltige Unternehmensführung.', img: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&q=80&w=400' },
|
||||
];
|
||||
|
||||
const partners: Partner[] = [
|
||||
/* ── TIER 1 · Hauptsponsoren ─────────────────────────────── */
|
||||
{
|
||||
id: 'rsm', name: 'RSM Ebner Stolz', role: 'Hauptsponsor', tier: 1,
|
||||
desc: 'Führende mittelständische Wirtschaftsprüfungs- und Steuerberatungsgesellschaft.',
|
||||
fullDesc: 'RSM Ebner Stolz ist eine der größten unabhängigen Wirtschaftsprüfungs- und Steuerberatungsgesellschaften Deutschlands mit besonderer Expertise im Mittelstand. Als Hauptsponsor des BMP unterstützen sie die Identifikation herausragender Unternehmen und bringen ihre Netzwerke aktiv in die Nominierungsphase ein.',
|
||||
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ display: 'flex', gap: 2 }}><span style={{ width: 10, height: 10, background: '#6b7280', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#22c55e', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#3b82f6', display: 'block' }} /></span><span style={{ fontWeight: 900, fontSize: 15, letterSpacing: '-0.02em', color: '#374151', fontFamily: FF }}>RSM <span style={{ color: '#6b7280', fontWeight: 600 }}>EBNER STOLZ</span></span></span>),
|
||||
industry: 'Wirtschaftsprüfung & Beratung', location: 'München, Bayern', website: 'rsm-ebner-stolz.de', linkedin: 'https://linkedin.com/company/rsm-ebner-stolz',
|
||||
},
|
||||
{
|
||||
id: 'wwk', name: 'WWK', role: 'Hauptsponsor', tier: 1,
|
||||
desc: 'Eine starke Gemeinschaft – Lebensversicherungsgruppe mit bayerischen Wurzeln.',
|
||||
fullDesc: 'Die WWK Lebensversicherung a. G. ist eine der leistungsstärksten deutschen Lebensversicherungsgruppen. Als Münchner Traditionsunternehmen verbindet die WWK tiefe bayerische Verwurzelung mit finanzieller Stärke – Werte, die sie mit dem Bayerischen Mittelstandspreis teilt.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 24, letterSpacing: '-0.02em', color: '#16a34a', fontFamily: 'Arial Black, sans-serif' }}>WWK</span>),
|
||||
industry: 'Lebensversicherung', location: 'München, Bayern', website: 'wwk.de', linkedin: 'https://linkedin.com/company/wwk-versicherung',
|
||||
},
|
||||
|
||||
/* ── TIER 2 · Medienpartner ──────────────────────────────── */
|
||||
{
|
||||
id: 'radiogong', name: 'Radio Gong 96.3', role: 'Medienpartner', tier: 2,
|
||||
desc: 'Der reichweitenstärkste Radiosender Münchens – Stimme des Mittelstands.',
|
||||
fullDesc: 'Radio Gong 96.3 ist der reichweitenstärkste Radiosender Münchens. Als Medienpartner des BMP sorgt er für Aufmerksamkeit für die Nominierten in Sendemitschnitten und redaktionellen Beiträgen.',
|
||||
logo: (<span style={{ background: '#e11d48', color: '#fff', fontWeight: 900, fontSize: 14, padding: '4px 10px', lineHeight: 1.25, display: 'inline-block', textAlign: 'center' as const, fontFamily: '"IBM Plex Sans", sans-serif' }}>Radio<br/>Gong <span style={{ color: '#fde047' }}>96.3</span></span>),
|
||||
industry: 'Medien', location: 'München', website: 'radiogong.com',
|
||||
},
|
||||
{
|
||||
id: 'muenchentv', name: 'münchen.tv', role: 'Medienpartner', tier: 2,
|
||||
desc: 'Der lokale Fernsehsender der Landeshauptstadt München.',
|
||||
fullDesc: 'münchen.tv ist der lokale Fernsehsender der Landeshauptstadt München. Als Medienpartner überträgt münchen.tv Highlights der BMP-Gala und produziert Porträts der Nominierten.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 15, color: '#0ea5e9', fontFamily: '"Inter", sans-serif' }}>münchen<span style={{ color: '#1e293b' }}>.tv</span></span>),
|
||||
industry: 'Medien', location: 'München', website: 'muenchen.tv',
|
||||
},
|
||||
|
||||
/* ── TIER 3 · Weitere Sponsoren ──────────────────────────── */
|
||||
{
|
||||
id: 'metzler', name: 'METZLER', role: 'Sponsor', tier: 3,
|
||||
desc: 'Älteste Privatbank Deutschlands – Unabhängigkeit seit 1674.',
|
||||
fullDesc: 'B. Metzler seel. Sohn & Co. KGaA ist die älteste deutsche Privatbank in Familienbesitz – seit über 350 Jahren unabhängig. Ihr Engagement für den BMP unterstreicht die tiefe Verbundenheit mit dem Mittelstand, dessen Werte wie Verlässlichkeit, Substanz und Langfristigkeit Metzler selbst verkörpert.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 21, letterSpacing: '0.15em', color: '#1a1a1a', fontFamily: 'Georgia, serif' }}>METZLER</span>),
|
||||
industry: 'Privatbankwesen', location: 'Frankfurt a. M.', website: 'metzler.com', linkedin: 'https://linkedin.com/company/metzler',
|
||||
},
|
||||
{
|
||||
id: 'wieselhuber', name: 'Dr. Wieselhuber & Partner', role: 'Sponsor', tier: 3,
|
||||
desc: 'Unabhängige Top-Management-Beratung für Familienunternehmen und Mittelstand.',
|
||||
fullDesc: 'Dr. Wieselhuber & Partner ist eine unabhängige, branchenübergreifende Top-Management-Beratung mit besonderem Fokus auf Familienunternehmen und Mittelstand. Als Sponsor bringt das Haus seine Strategie- und Transformationsexpertise in das Netzwerk des BMP ein.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 16, letterSpacing: '-0.01em', color: '#1a2b4a', fontFamily: FF }}>W&P <span style={{ fontWeight: 600, color: '#5a6b85' }}>WIESELHUBER</span></span>),
|
||||
industry: 'Unternehmensberatung', location: 'München, Bayern', website: 'wieselhuber.de',
|
||||
},
|
||||
{
|
||||
id: 'fristads', name: 'Fristads', role: 'Sponsor', tier: 3,
|
||||
desc: 'Premium-Arbeitsbekleidung – Funktion, Qualität und Design.',
|
||||
fullDesc: 'Fristads steht für hochwertige, funktionale Arbeitsbekleidung, die Schutz, Komfort und Design vereint. Als Sponsor des Bayerischen Mittelstandspreises unterstützt das Unternehmen die Auszeichnung herausragender mittelständischer Betriebe.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 19, letterSpacing: '0.02em', color: '#1a1a1a', fontFamily: 'Arial Black, sans-serif' }}>FRISTADS</span>),
|
||||
industry: 'Workwear & Textil', location: 'Deutschland', website: 'fristads.com',
|
||||
},
|
||||
{
|
||||
id: 'newedge', name: 'New Edge', role: 'Sponsor', tier: 3,
|
||||
desc: 'Digitale Markenführung, Web und Design für den Mittelstand.',
|
||||
fullDesc: 'New Edge gestaltet Marken, Web-Erlebnisse und digitale Auftritte für mittelständische Unternehmen. Als Sponsor des BMP unterstützt die Agentur die Sichtbarkeit und das digitale Profil ausgezeichneter Unternehmen.',
|
||||
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 22, height: 22, background: '#3a3a3a', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, fontSize: 11, fontFamily: FF }}>NE</span><span style={{ fontWeight: 700, fontSize: 15, color: '#005E67', fontFamily: FF }}>New Edge</span></span>),
|
||||
industry: 'Branding & Digital', location: 'Bayern', website: 'newedgebrand.com',
|
||||
},
|
||||
{
|
||||
id: 'moodtalk', name: 'Moodtalk', role: 'Sponsor', tier: 3,
|
||||
desc: 'Plattform für Teamkultur und Mitarbeiter-Feedback.',
|
||||
fullDesc: 'Moodtalk ist eine Plattform für Teamkultur, Stimmung und kontinuierliches Mitarbeiter-Feedback. Als Sponsor des BMP steht das Unternehmen für moderne, wertebasierte Unternehmensführung.',
|
||||
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 20, height: 20, borderRadius: '50%', background: '#7c3aed', display: 'block' }} /><span style={{ fontWeight: 700, fontSize: 16, color: '#7c3aed', fontFamily: FF }}>moodtalk</span></span>),
|
||||
industry: 'HR & Software', location: 'Bayern', website: 'moodtalk.io',
|
||||
},
|
||||
{
|
||||
id: 'deutschebank', name: 'Deutsche Bank', role: 'Sponsor', tier: 3,
|
||||
desc: 'Partnerbank für die Finanzierung und Skalierung von KMU-Wachstum.',
|
||||
fullDesc: 'Die Deutsche Bank begleitet mittelständische Unternehmen als verlässlicher Finanzpartner durch alle Wachstumsphasen. Im Rahmen des BMP stellt sie ihr deutschlandweites Netzwerk, spezifische Mittelstandsprodukte und direkte Ansprechpartner für die Nominierten bereit.',
|
||||
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}><svg width="20" height="20" viewBox="0 0 20 20"><rect x="1" y="1" width="18" height="18" fill="none" stroke="#1d1d1b" strokeWidth="1.5" /><line x1="5" y1="15" x2="15" y2="5" stroke="#1d1d1b" strokeWidth="2" /></svg><span style={{ fontWeight: 600, fontSize: 15, color: '#1d1d1b', fontFamily: FB }}>Deutsche Bank</span></span>),
|
||||
industry: 'Bankwesen & Finanzen', location: 'Frankfurt a. M.', website: 'db.com', linkedin: 'https://linkedin.com/company/deutsche-bank',
|
||||
},
|
||||
{
|
||||
id: 'bionorica', name: 'Bionorica', role: 'Sponsor', tier: 3,
|
||||
desc: 'Weltmarktführer für pflanzliche Arzneimittel aus dem bayerischen Neumarkt.',
|
||||
fullDesc: 'Bionorica SE ist ein international agierendes Pharmaunternehmen mit Sitz in Neumarkt i. d. OPf. und Weltmarktführer bei pflanzlichen Arzneimitteln. Das Unternehmen selbst ist ein Paradebeispiel eines bayerischen Mittelständlers mit globalem Anspruch und verkörpert die Innovationskraft, die der BMP würdigt.',
|
||||
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="9" fill="#16a34a" /><path d="M10 3 Q14 7 14 10 Q14 14 10 17 Q6 14 6 10 Q6 7 10 3Z" fill="white" opacity="0.8" /></svg><span style={{ fontWeight: 700, fontSize: 15, color: '#16a34a', fontFamily: FB }}>Bionorica<sup style={{ fontSize: 8 }}>®</sup></span></span>),
|
||||
industry: 'Pharmaindustrie', location: 'Neumarkt i.d.OPf., Bayern', website: 'bionorica.de', linkedin: 'https://linkedin.com/company/bionorica',
|
||||
},
|
||||
{
|
||||
id: 'primus', name: 'Primus', role: 'Sponsor', tier: 3,
|
||||
desc: 'Partner des Bayerischen Mittelstandspreises.',
|
||||
fullDesc: 'Primus unterstützt als Sponsor den Bayerischen Mittelstandspreis und sein Engagement für herausragende Unternehmen des bayerischen Mittelstands.',
|
||||
logo: (<span style={{ fontWeight: 900, fontSize: 18, letterSpacing: '0.04em', color: '#b8860b', fontFamily: 'Georgia, serif' }}>PRIMUS</span>),
|
||||
industry: 'Partner', location: 'Bayern', website: '',
|
||||
},
|
||||
];
|
||||
|
||||
const expertiseRows = [
|
||||
{ num: '01', label: 'Aktenstudium', body: 'Alle Einreichungen werden zunächst vollständig gesichtet und nach den festgelegten Kriterien geprüft.' },
|
||||
{ num: '02', label: 'Scoring-Matrix', body: 'Jedes Jurymitglied bewertet die Einreichungen unabhängig nach den festgelegten Kriterien.' },
|
||||
{ num: '03', label: 'Jury-Plenum', body: 'In der gemeinsamen Sitzung werden Argumente und Ansichten ausgetauscht, Eigenschaften verglichen und gemeinsam bewertet. Daraus entsteht die Liste der Finalisten.' },
|
||||
{ num: '04', label: 'Ergebnis', body: 'Die Gewinner des Bayerischen Mittelstandspreises werden nach vorgegebenem Punktesystem bestimmt, erweitert um Jury-Mitglieder aus der Praxis.' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
|
||||
{/* ── 1. HERO ──────────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', minHeight: '72vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: '#060C14' }}>
|
||||
<img src="/images/netzwerk-hero.jpg" alt="BMP Netzwerk Preisverleihung" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)' : 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)' }} />
|
||||
<div style={{ position: 'absolute', top: 0, left: 80, width: 2, height: '100%', background: `linear-gradient(to bottom, transparent, ${GOLD}, transparent)`, opacity: 0.4 }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)', zIndex: 2 }} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 88px', maxWidth: 860 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
|
||||
<div style={{ width: 40, height: 2, background: GOLD }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD }}>Jury & Partner</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 6vw, 5rem)' : 'clamp(2.8rem, 6vw, 5rem)', fontWeight: 900, color: '#fff', lineHeight: 0.95, letterSpacing: '-0.03em', textTransform: 'uppercase', margin: '0 0 28px' }}>
|
||||
DAS NETZWERK<br />HINTER DEM <span style={{ color: GOLD }}>AWARD.</span>
|
||||
</h1>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, maxWidth: 560, margin: 0, fontWeight: 300 }}>
|
||||
Der Bayerische Mittelstandspreis wird getragen von einem starken Netzwerk aus Schirmherrschaft, unabhängiger Fach-Jury und langjährigen Partnern aus Wirtschaft und Gesellschaft.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 2. SCHIRMHERRSCHAFT ──────────────────────────────────────────────── */}
|
||||
<section id="schirmherrschaft" style={{ background: NAVY }}>
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 16 }}>Die Schirmherrschaft</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1, margin: '0 0 20px' }}>Schirmherrin und Schirmherr</h2>
|
||||
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 14 }} />
|
||||
<p style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', margin: 0 }}>des Bayerischen Mittelstandspreises</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : '1fr 1fr', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, gap: isMobile ? 14 : 0, padding: isMobile ? '0 24px 28px' : 0, WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none' }}>
|
||||
<PersonCard
|
||||
imgSrc="https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&q=80&w=800"
|
||||
imgAlt="Ilse Aigner"
|
||||
name="Ilse Aigner MdL"
|
||||
titleLine1="Präsidentin des Bayerischen Landtags"
|
||||
titleLine2="Bayerische Staatsministerin für Wirtschaft a.D."
|
||||
institution="Bayerischer Landtag"
|
||||
borderRight
|
||||
/>
|
||||
<PersonCard
|
||||
imgSrc="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=800"
|
||||
imgAlt="Hubert Aiwanger"
|
||||
name="Hubert Aiwanger MdL"
|
||||
titleLine1="Bayerischer Staatsminister für Wirtschaft,"
|
||||
titleLine2="Landesentwicklung und Energie · Stv. Ministerpräsident"
|
||||
institution="Bayerische Staatsregierung"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Grußwort – Ilse Aigner */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', borderTop: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
<div style={{ padding: isMobile ? '32px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '40% 60%', gap: isMobile ? '24px 0' : '0 64px', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 8 }}>Grußwort</span>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'rgba(255,255,255,0.3)', marginBottom: 16 }}>Schirmherrin</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.15, marginBottom: 10 }}>Ilse Aigner MdL</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.6, marginBottom: 4 }}>Präsidentin des Bayerischen Landtags</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)', lineHeight: 1.6 }}>Bayerische Staatsministerin für Wirtschaft a.D.</div>
|
||||
</div>
|
||||
<div>
|
||||
<blockquote style={{ fontFamily: FF, fontSize: 18, fontStyle: 'italic', color: 'rgba(255,255,255,0.8)', lineHeight: 1.75, margin: 0, paddingLeft: 24, borderLeft: `3px solid ${GOLD}` }}>
|
||||
"Der Bayerische Mittelstandspreis steht für das, was Bayern stark macht: Unternehmergeist, Verantwortung und Qualität. Mit großer Freude übernehme ich erneut die Schirmherrschaft für diesen bedeutenden Preis."
|
||||
</blockquote>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.35)', marginTop: 20, paddingLeft: 24 }}>
|
||||
– Ilse Aigner MdL, Präsidentin des Bayerischen Landtags
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.2), transparent)` }} />
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 3. JURY-INTRO (editorial 2-col split) ────────────────────────────── */}
|
||||
<section style={{ display: isMobile ? 'block' : 'grid', gridTemplateColumns: isMobile ? undefined : '40% 60%', minHeight: isMobile ? 'auto' : 400, position: 'relative', overflow: 'hidden', isolation: 'isolate', background: isMobile ? NAVY : undefined }}>
|
||||
{!isMobile && <MunichSkylineBg />}
|
||||
{isMobile && (
|
||||
<div aria-hidden style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 0, backgroundImage: 'url(/munich-skyline.jpg)', backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom center', backgroundSize: 'cover', opacity: 0.07 }} />
|
||||
)}
|
||||
{isMobile ? (
|
||||
/* Mobile: one cohesive navy module – copy + compact stat strip */
|
||||
<div style={{ padding: '48px 24px', position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 18 }}>Wer entscheidet?</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.7rem, 8vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 22px' }}>
|
||||
UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND.
|
||||
</h2>
|
||||
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 22 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.55)', lineHeight: 1.75, margin: 0 }}>
|
||||
Die Jury des Bayerischen Mittelstandspreises besteht ausschließlich aus ehrenamtlich tätigen Expertinnen und Experten. Kein Mitglied steht in wirtschaftlicher Verbindung zu einem Bewerber – Transparenz und Unparteilichkeit sind die Grundpfeiler unseres Verfahrens.
|
||||
</p>
|
||||
{/* Stat strip */}
|
||||
<div style={{ display: 'flex', marginTop: 32, paddingTop: 26, borderTop: '1px solid rgba(255,255,255,0.14)' }}>
|
||||
{[
|
||||
{ v: '11', l: 'Jurymitglieder' },
|
||||
{ v: 'Mehrstufig', l: 'Bewertungsverfahren' },
|
||||
{ v: '100%', l: 'Unabhängig' },
|
||||
].map((s, i) => (
|
||||
<div key={i} style={{ flex: 1, minWidth: 0, paddingLeft: i > 0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(255,255,255,0.14)' : 'none' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(2.2rem, 9vw, 3rem)', fontWeight: 900, color: GOLD, letterSpacing: '-0.03em', lineHeight: 1 }}>{s.v}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(255,255,255,0.5)', marginTop: 8, lineHeight: 1.3 }}>{s.l}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Left – Cream, stats */}
|
||||
<div style={{ background: CREAM, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>11</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>Unabhängige Jurymitglieder</div>
|
||||
</div>
|
||||
<div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} />
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(2.4rem, 4vw, 3.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.03em', lineHeight: 1 }}>Mehrstufig</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 8 }}>Bewertungsverfahren</div>
|
||||
</div>
|
||||
<div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} />
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>100%</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>Unabhängig & ehrenamtlich</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right – Navy, editorial copy */}
|
||||
<div style={{ background: NAVY, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, marginBottom: 20 }}>Wer entscheidet?</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.6rem, 2.5vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 24px' }}>
|
||||
UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND.
|
||||
</h2>
|
||||
<div style={{ width: 40, height: 2, background: GOLD }} />
|
||||
<p style={{ marginTop: 24, fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.45)', lineHeight: 1.85 }}>
|
||||
Die Jury des Bayerischen Mittelstandspreises besteht ausschließlich aus ehrenamtlich tätigen Expertinnen und Experten. Kein Mitglied steht in wirtschaftlicher Verbindung zu einem Bewerber – Transparenz und Unparteilichkeit sind die Grundpfeiler unseres Verfahrens.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 4. JURY-MITGLIEDER ───────────────────────────────────────────────── */}
|
||||
<section id="jury" style={{ background: NAVY, overflow: 'hidden', position: 'relative' }}>
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 16 : 48, alignItems: 'flex-end', borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>Das Gremium</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.5vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 0.95, margin: 0 }}>UNSERE JURY 2026</h2>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.4)', lineHeight: 1.8, margin: 0 }}>
|
||||
Unabhängige Expertinnen und Experten aus Wirtschaft, Wissenschaft und Verbänden, für einen vollständig unabhängigen Bewertungsprozess.
|
||||
</p>
|
||||
</div>
|
||||
{/* ── Prominenter Vorsitz-Bereich ─────────────────────────────────── */}
|
||||
{/* TODO: echten Jury-Vorsitzenden bestätigen (Name & Foto sind Platzhalter) */}
|
||||
<div style={{ padding: isMobile ? '28px 24px 8px' : '64px 80px 16px' }}>
|
||||
<JuryChairCard member={juryMembers[0]} />
|
||||
</div>
|
||||
|
||||
{/* ── Übrige Jury-Mitglieder ──────────────────────────────────────── */}
|
||||
<div style={{ display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : 'repeat(3, 1fr)', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, gap: isMobile ? 14 : 0, padding: isMobile ? '24px 24px 12px' : '0', WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none', borderTop: isMobile ? 'none' : '1px solid rgba(255,255,255,0.07)' }}>
|
||||
{juryMembers.slice(1).map((member, idx) => (
|
||||
<JuryCard key={idx} member={member} idx={idx} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── Hinweis: Doppelrolle Jury / Partner (#179) ──────────────────── */}
|
||||
<div style={{ padding: isMobile ? '4px 24px 36px' : '8px 80px 56px' }}>
|
||||
<p style={{ fontFamily: FB, fontSize: isMobile ? 13 : 14, color: 'rgba(255,255,255,0.35)', lineHeight: 1.6, margin: 0, maxWidth: 720 }}>
|
||||
Hinweis: Einzelne Persönlichkeiten engagieren sich sowohl in der Jury als auch als Partner des BMP – beide Rollen werden auf dieser Seite getrennt ausgewiesen.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 5. HINTERGRUND & EXPERTISE ───────────────────────────────────────── */}
|
||||
<section style={{ background: '#101828', overflow: 'hidden', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', borderTop: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
<div style={{ padding: isMobile ? '48px 24px' : 80, display: 'flex', flexDirection: 'column', justifyContent: 'center', background: '#101828' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>Hintergrund & Expertise</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 3vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.0, margin: '0 0 24px' }}>WIE BEWERTET DIE JURY?</h2>
|
||||
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 40 }} />
|
||||
<div>
|
||||
{expertiseRows.map((row) => (
|
||||
<div key={row.num} style={{ display: 'grid', gridTemplateColumns: '40px 1fr', gap: '0 20px', padding: '20px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(239,191,4,0.5)' }}>{row.num}</div>
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>{row.label}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.4)', lineHeight: 1.6 }}>{row.body}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '80px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', borderLeft: isMobile ? 'none' : '1px solid rgba(255,255,255,0.07)', borderTop: isMobile ? '1px solid rgba(255,255,255,0.07)' : 'none', overflow: 'hidden', background: '#101828' }}>
|
||||
<span style={{ fontFamily: 'Georgia, serif', fontSize: 'clamp(60px, 8vw, 120px)', color: 'rgba(239,191,4,0.1)', lineHeight: 0.8, display: 'block', marginBottom: -20 }}>“</span>
|
||||
<p style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 300, color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, margin: '0 0 40px' }}>
|
||||
Die Jury des BMP ist vollständig von wirtschaftlichen Interessen unabhängig. Jede Entscheidung wird transparent nachvollzogen – das ist unser Versprechen an die Unternehmen Bayerns.
|
||||
</p>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 40, height: 2, background: GOLD, flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)' }}>Prof. Dr. Klaus Bergmann, Juryvorsitzender</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 6. PARTNER & SPONSOREN ───────────────────────────────────────────── */}
|
||||
<section id="partner" style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 12 : 0, justifyContent: 'space-between', alignItems: isMobile ? 'flex-start' : 'flex-end', borderBottom: '1px solid rgba(3,9,58,0.1)' }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: '#4A8FC9', display: 'block', marginBottom: 16 }}>Netzwerkqualität</span>
|
||||
<h2 style={{ fontFamily: FF, fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(2rem, 3.5vw, 3rem)', lineHeight: 0.95, margin: 0 }}>PARTNER & SPONSOREN</h2>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.4)', maxWidth: isMobile ? '100%' : 220, textAlign: isMobile ? 'left' : 'right', lineHeight: 1.6, margin: 0 }}>
|
||||
Partner in drei Kategorien: Hauptsponsoren, Medienpartner und weitere Sponsoren. Klicken für Details.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tiered grid – continuous, ranks marked by label bars */}
|
||||
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||
{([
|
||||
{ tier: 1 as const, label: 'Hauptsponsoren', note: 'Premium', variant: 'lg' as const, cols: isMobile ? 1 : 2 },
|
||||
{ tier: 2 as const, label: 'Medienpartner', note: '', variant: 'md' as const, cols: isMobile ? 2 : 2 },
|
||||
{ tier: 3 as const, label: 'Weitere Sponsoren', note: '', variant: 'sm' as const, cols: isMobile ? 2 : 4 },
|
||||
]).map(t => {
|
||||
const items = partners.filter(p => p.tier === t.tier);
|
||||
return (
|
||||
<div key={t.tier} style={{ display: 'grid', gridTemplateColumns: `repeat(${t.cols}, 1fr)` }}>
|
||||
{items.map((p, i) => (
|
||||
<SponsorCell key={p.id} partner={p} variant={t.variant} idx={i} cols={t.cols} onClick={() => setSelectedPartner(p)} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 7. SPONSORING-FORM ───────────────────────────────────────────────── */}
|
||||
<section id="sponsoring" style={{
|
||||
background: NAVY, overflow: 'hidden', position: 'relative',
|
||||
minHeight: isMobile ? 'auto' : 'calc(100vh - 60px)',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
}}>
|
||||
<div style={{ flex: 1, position: 'relative', zIndex: 1, display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', minHeight: 0, overflow: isMobile ? 'visible' : 'visible' }}>
|
||||
{/* Left – pitch */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'visible' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 8 }}>Wachstum durch Partnerschaft</span>
|
||||
<h2 style={{ fontFamily: FF, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(1.6rem, 2.4vw, 2.4rem)', lineHeight: 1.0, margin: '0 0 12px' }}>WIR FREUEN UNS ÜBER NEUE SPONSOREN.</h2>
|
||||
<div style={{ width: 36, height: 2, background: GOLD, margin: '0 0 14px', flexShrink: 0 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 'clamp(15px, 1.2vw, 17px)', color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, marginBottom: 16 }}>
|
||||
Positionieren Sie Ihre Marke im exklusivsten Netzwerk des bayerischen Mittelstands – mit maßgeschneiderten Sponsoring-Paketen.
|
||||
</p>
|
||||
{[
|
||||
{ num: '01', label: 'Markenpräsenz', sub: 'Bühne, Drucksachen, digitale Kanäle' },
|
||||
{ num: '02', label: 'Top-Entscheider', sub: 'Exklusives Netzwerk-Dinner für Partner' },
|
||||
{ num: '03', label: 'Multichannel', sub: 'Online, Print, Radio & TV-Partner' },
|
||||
].map((item) => (
|
||||
<div key={item.num} style={{ display: 'grid', gridTemplateColumns: '32px 1fr', gap: '0 12px', padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(239,191,4,0.5)' }}>{item.num}</span>
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(14px, 1.05vw, 16px)', fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 2 }}>{item.label}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 'clamp(13px, 1vw, 15px)', color: 'rgba(255,255,255,0.65)', lineHeight: 1.4 }}>{item.sub}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Center divider */}
|
||||
{!isMobile && <div style={{ background: 'rgba(255,255,255,0.07)' }} />}
|
||||
{/* Right – gold form panel */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: 'visible', minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
<svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }} aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="sponsoringCross" width="18" height="18" patternUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L18,18 M18,0 L0,18" stroke="rgba(17,29,85,0.055)" strokeWidth="0.65" />
|
||||
</pattern>
|
||||
<pattern id="sponsoringDiamond" width="36" height="36" patternUnits="userSpaceOnUse">
|
||||
<polygon points="18,2 34,18 18,34 2,18" fill="none" stroke="rgba(17,29,85,0.045)" strokeWidth="0.6" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#sponsoringCross)" />
|
||||
<rect width="100%" height="100%" fill="url(#sponsoringDiamond)" />
|
||||
</svg>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, transparent 55%, rgba(100,70,0,0.25) 100%)', pointerEvents: 'none', zIndex: 0 }} />
|
||||
|
||||
{!isMobile && (
|
||||
<div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
|
||||
<img src="/images/networking-innenhof.jpg" alt="BMP Partnernetzwerk" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 40%', filter: 'sepia(0.18) brightness(0.92)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 14, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>BMP Partnernetzwerk</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ padding: isMobile ? '28px 20px' : '24px 48px 32px 40px', flex: 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 560 : 0, position: 'relative', zIndex: 1 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: 'rgba(17,29,85,0.5)', display: 'block', marginBottom: 8 }}>Sponsoring 2026</span>
|
||||
<SponsoringForm theme="gold" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Footnote */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 20px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: 10 }}>Oder:</span>
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
style={{
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(239,191,4,0.7)',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
borderBottom: '1px solid rgba(239,191,4,0.3)',
|
||||
paddingBottom: 1,
|
||||
transition: 'color 0.15s, borderColor 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = '#EFBF04';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = '#EFBF04';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = 'rgba(239,191,4,0.7)';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)';
|
||||
}}
|
||||
>
|
||||
Vereinsmitglied werden <ChevronRight size={11} />
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ position: 'relative', zIndex: 1, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)`, flexShrink: 0 }} />
|
||||
</section>
|
||||
|
||||
|
||||
{/* ── 8. PARTNER MODAL ─────────────────────────────────────────────────── */}
|
||||
{selectedPartner && (
|
||||
<PartnerModal partner={selectedPartner} onClose={() => setSelectedPartner(null)} />
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Netzwerk;
|
||||
608
src/spa/pages/Participation.tsx
Normal file
608
src/spa/pages/Participation.tsx
Normal file
@@ -0,0 +1,608 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { MapPin, Users, TrendingUp, Building2, ArrowRight, CheckCircle2, UserCheck, Send, Star } from 'lucide-react';
|
||||
import { Link } from '@/spa/router';
|
||||
import BewerbungsForm from '@/spa/components/forms/BewerbungsForm';
|
||||
import WegZurAuszeichnungSection from '@/spa/components/WegZurAuszeichnungSection';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#E4E2E3';
|
||||
|
||||
const Participation: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const qualSectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
|
||||
{/* ── HERO ─────────────────────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', minHeight: '65vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: NAVY }}>
|
||||
<img
|
||||
src="/images/preisuebergabe.jpg"
|
||||
alt="Teilnahme BMP"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)' : 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)` }} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 80px', maxWidth: 800 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 24 }}>
|
||||
<div style={{ width: 36, height: 2, background: GOLD }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: '#EFBF04' }}>Bewerbungsphase 2026</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 6vw, 5rem)' : 'clamp(2.8rem, 6vw, 5rem)', fontWeight: 900, color: '#fff', lineHeight: 0.95, letterSpacing: '-0.03em', textTransform: 'uppercase', margin: '0 0 24px' }}>
|
||||
GESTALTEN SIE<br />BAYERNS ZUKUNFT.
|
||||
</h1>
|
||||
<div style={{ width: 48, height: 2, background: GOLD, marginBottom: 24 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.6)', lineHeight: 1.7, maxWidth: 520, fontWeight: 300 }}>
|
||||
Alles, was Sie für Ihre erfolgreiche Bewerbung zum Bayerischen Mittelstandspreis wissen müssen.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── TIMELINE – direkt nach Hero ──────────────────────────────────────── */}
|
||||
<WegZurAuszeichnungSection />
|
||||
|
||||
{/* ── QUALIFICATIONS ───────────────────────────────────────────────────── */}
|
||||
<section id="voraussetzungen" ref={qualSectionRef} style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
|
||||
{/* Header */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 24 : 48, alignItems: 'flex-end', borderBottom: `1px solid #D0D5DD` }}>
|
||||
<div>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Berechtigung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.5vw, 3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.03, margin: 0 }}>
|
||||
WER KANN<br />TEILNEHMEN?
|
||||
</h2>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: isMobile ? 'flex-start' : 'flex-end', justifyContent: 'space-between', gap: 24 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, flexShrink: 0 }}>
|
||||
<Link
|
||||
to="#bewerben"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '11px 22px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'opacity 0.2s', whiteSpace: 'nowrap' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Jetzt bewerben <ArrowRight size={13} />
|
||||
</Link>
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: NAVY, border: `1.5px solid rgba(3,9,58,0.3)`, padding: '10px 22px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'border-color 0.2s, color 0.2s', whiteSpace: 'nowrap' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = NAVY; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(3,9,58,0.3)'; }}
|
||||
>
|
||||
Mitglied werden
|
||||
</Link>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.8, margin: 0, textAlign: isMobile ? 'left' : 'right' }}>
|
||||
Vier klar definierte Kriterien: Erfüllen Sie alle, bewerben Sie sich kostenlos und ohne bürokratischen Aufwand.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 4 criteria as editorial rows */}
|
||||
<div style={{ position: 'relative', zIndex: 1, borderBottom: `1px solid #D0D5DD` }}>
|
||||
{[
|
||||
{ num: '01', title: 'Standort Bayern', body: 'Sitz oder wesentliche Betriebsstätte im Freistaat Bayern.', Icon: MapPin },
|
||||
{ num: '02', title: 'KMU-Größe', body: '50 bis 1.500 Mitarbeiter: klassischer Mittelstand im privatwirtschaftlichen Bereich.', Icon: Users },
|
||||
{ num: '03', title: 'Marktreife', body: 'Mindestens drei vollständige Geschäftsjahre am Markt tätig.', Icon: TrendingUp },
|
||||
{ num: '04', title: 'Privatwirtschaft', body: 'Keine überwiegende Zugehörigkeit zu staatlichen oder kommunalen Trägern.', Icon: Building2 },
|
||||
].map((item, i, arr) => {
|
||||
const Icon = item.Icon;
|
||||
return (
|
||||
<QualRow key={i} item={item} Icon={Icon} last={i === arr.length - 1} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Zusätzliche Hinweise: Verbund + Wiederbewerber */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '28px 24px' : '40px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 24 : 48, borderBottom: '1px solid #D0D5DD' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 14 }}>
|
||||
<Building2 size={18} style={{ color: GOLD, flexShrink: 0, marginTop: 3 }} strokeWidth={1.7} />
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 13, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Verbund & Gemeinschaft</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, margin: 0 }}>
|
||||
Oder Sie sind ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen, die einen Schwerpunkt in Bayern hat.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 14 }}>
|
||||
<UserCheck size={18} style={{ color: GOLD, flexShrink: 0, marginTop: 3 }} strokeWidth={1.7} />
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 13, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>Erneute Bewerbung</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, margin: 0 }}>
|
||||
Nominierte ohne Gewinn können sich sofort erneut bewerben. Preisträger können sich nach 7 Jahren erneut bewerben.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA nudge */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '24px 24px' : '32px 80px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'center', justifyContent: 'space-between', gap: 24 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<CheckCircle2 size={18} style={{ color: GOLD, flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 18, fontWeight: 600, color: '#101828' }}>Alle 4 Punkte erfüllt? Dann jetzt kostenlos bewerben.</span>
|
||||
</div>
|
||||
<Link
|
||||
to="#bewerben"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '13px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'opacity 0.2s', whiteSpace: 'nowrap' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Zum Formular <ArrowRight size={13} />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── CRITERIA / BEWERTUNG ─────────────────────────────────────────────── */}
|
||||
<section id="kriterien" style={{ background: NAVY, overflow: 'hidden', position: 'relative' }}>
|
||||
|
||||
{/* TOP: editorial split – image left (45%) / header right (55%) */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '45% 55%', minHeight: isMobile ? 'auto' : 480 }}>
|
||||
|
||||
{/* Left – full-bleed image with gradient blend into navy on the right (+ heading on mobile) */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined }}>
|
||||
<img
|
||||
src="/images/buehne-gewinner.jpg"
|
||||
alt="Jury-Bewertung BMP"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center', display: 'block' }}
|
||||
/>
|
||||
{/* Dark overlay for depth */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(3,9,58,0.35) 0%, rgba(3,9,58,0.15) 50%, rgba(3,9,58,0.35) 100%)' }} />
|
||||
{/* Right-side blend into navy (desktop) */}
|
||||
{!isMobile && <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to right, transparent 50%, rgba(3,9,58,0.7) 80%, #111D55 100%)' }} />}
|
||||
{/* Mobile heading shadow (bottom-left) */}
|
||||
{isMobile && <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(3,9,58,0.94) 0%, rgba(3,9,58,0.55) 38%, rgba(3,9,58,0.12) 72%, transparent 100%), linear-gradient(to right, rgba(3,9,58,0.5) 0%, transparent 55%)' }} />}
|
||||
{/* Bottom blend for continuity with grid below */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 120, background: 'linear-gradient(to bottom, transparent, #111D55)' }} />
|
||||
{/* Heading overlay – mobile only */}
|
||||
{isMobile && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 24px', zIndex: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Bewertung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 9vw, 2.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 0.97, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.6)' }}>
|
||||
UNSERE<br />KRITERIEN.
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right – overline + headline + intro */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 40px' : '80px 80px 72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 20 }}>Bewertung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2.4rem, 4vw, 3.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 0.97, margin: '0 0 28px' }}>
|
||||
UNSERE<br />KRITERIEN.
|
||||
</h2>
|
||||
</>
|
||||
)}
|
||||
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 28 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.68)', lineHeight: 1.8, margin: 0, maxWidth: 420 }}>
|
||||
Vier zentrale Säulen, bewertet durch eine vollständig unabhängige Expertenjury aus Wirtschaft, Wissenschaft und Gesellschaft.
|
||||
<br />
|
||||
<br />
|
||||
Die Kriterien werden nicht gleich gewichtet.
|
||||
</p>
|
||||
<a
|
||||
href="https://www.der-bayerische-mittelstandspreis.de/der-preis/kriterien/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: GOLD, textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 28, borderBottom: `1px solid rgba(239,191,4,0.4)`, paddingBottom: 2, alignSelf: 'flex-start' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.8')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Kriterien & Bewertung <ArrowRight size={13} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* BOTTOM: full-width 4-column criteria grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(4, 1fr)', borderTop: '1px solid rgba(255,255,255,0.07)', position: 'relative' }}>
|
||||
{[
|
||||
{ title: 'Innovations\u00ADkraft', desc: 'Zukunftsweisende Produkte, Dienstleistungen oder interne Prozessinnovationen.' },
|
||||
{ title: 'Nachhaltigkeit', desc: 'Ökologische Verantwortung und Ressourcen-Effizienz im operativen Kern.' },
|
||||
{ title: 'Unternehmens\u00ADkultur', desc: 'Mitarbeiterbindung, Aus- und Weiterbildung sowie wertebasierte Führung.' },
|
||||
{ title: 'Regionale Wurzeln', desc: 'Engagement am Standort Bayern und Beitrag zur regionalen Wertschöpfung.' },
|
||||
].map((crit, i) => (
|
||||
<CriteriaCell key={i} crit={crit} idx={i} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.2), transparent)` }} />
|
||||
</section>
|
||||
|
||||
{/* ── BEWERBUNGSWEGE ───────────────────────────────────────────────────── */}
|
||||
<BewerbungswegeSection />
|
||||
|
||||
{/* ── DATES BANNER ─────────────────────────────────────────────────────── */}
|
||||
<section id="fristen" style={{ background: GOLD }}>
|
||||
{isMobile ? (
|
||||
/* Mobile: slim horizontal bar */
|
||||
<div style={{ padding: '24px 22px 26px' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.2em', marginBottom: 18 }}>Wichtige Termine 2026</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{[
|
||||
{ date: '30. Juni 2026', label: 'Bewerbungsschluss' },
|
||||
{ date: 'August 2026', label: 'Nominierte' },
|
||||
{ date: 'Oktober 2026', label: 'Gala-Verleihung' },
|
||||
].map((d, i) => (
|
||||
<div key={i} style={{ flex: 1, minWidth: 0, paddingLeft: i > 0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(3,9,58,0.2)' : 'none' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', lineHeight: 1.12, marginBottom: 6 }}>{d.date}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 9.5, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.55)', lineHeight: 1.25 }}>{d.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Desktop: full banner */
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1px 1fr 1px 1fr 1px 1fr', maxWidth: 1200, margin: '0 auto' }}>
|
||||
<div style={{ padding: '48px 56px' }}>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', marginBottom: 6 }}>Wichtige Termine 2026</h3>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.6)', margin: 0 }}>Planen Sie Ihre Teilnahme rechtzeitig.</p>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(3,9,58,0.12)' }} />
|
||||
{[
|
||||
{ date: '30. Juni 2026', label: 'Bewerbungsschluss' },
|
||||
{ date: 'August 2026', label: 'Bekanntgabe Nominierte' },
|
||||
{ date: 'Oktober 2026', label: 'Gala-Verleihung' },
|
||||
].map((d, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<div style={{ padding: '48px 40px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', marginBottom: 6 }}>{d.date}</div>
|
||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.55)' }}>{d.label}</div>
|
||||
</div>
|
||||
{i < 2 && <div style={{ background: 'rgba(3,9,58,0.12)' }} />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* ── FORM SECTION ─────────────────────────────────────────────────────── */}
|
||||
<section style={{ background: NAVY, position: 'relative', overflow: 'hidden', height: isMobile ? 'auto' : 'calc(100vh - 60px)', display: 'flex', flexDirection: 'column' }} id="bewerben">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', flex: 1, minHeight: 0, overflow: isMobile ? 'visible' : 'hidden' }}>
|
||||
{/* Left – copy */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: isMobile ? 'visible' : 'hidden' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 20 }}>Direktbewerbung</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.2vw, 2.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 20px' }}>
|
||||
BEWERBEN ODER<br />VORSCHLAGEN.
|
||||
</h2>
|
||||
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.68)', lineHeight: 1.8, maxWidth: 400, marginBottom: 48 }}>
|
||||
Sie können sich selbst bewerben oder ein herausragendes Unternehmen vorschlagen. Firmenverbunde können sich ebenfalls bewerben. Die Teilnahme ist vollständig kostenfrei.
|
||||
</p>
|
||||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
{[
|
||||
{ num: '01', label: '0 EUR', desc: 'Teilnahmegebühr' },
|
||||
{ num: '02', label: 'Schnell', desc: 'Ca. 30–45 Minuten' },
|
||||
{ num: '03', label: 'Mehrstufig', desc: 'Transparenter Prozess' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ display: 'grid', gridTemplateColumns: '36px 1fr', gap: '0 16px', padding: '16px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(239,191,4,0.5)', letterSpacing: '0.1em' }}>{item.num}</span>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.08em' }}>{item.label}</span>
|
||||
<span style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.62)' }}>{item.desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
{!isMobile && <div style={{ background: 'rgba(255,255,255,0.07)' }} />}
|
||||
|
||||
{/* Right – gold form panel */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: isMobile ? 'visible' : 'hidden', minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
<svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }} aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="participationCross" width="18" height="18" patternUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L18,18 M18,0 L0,18" stroke="rgba(17,29,85,0.055)" strokeWidth="0.65" />
|
||||
</pattern>
|
||||
<pattern id="participationDiamond" width="36" height="36" patternUnits="userSpaceOnUse">
|
||||
<polygon points="18,2 34,18 18,34 2,18" fill="none" stroke="rgba(17,29,85,0.045)" strokeWidth="0.6" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#participationCross)" />
|
||||
<rect width="100%" height="100%" fill="url(#participationDiamond)" />
|
||||
</svg>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, transparent 55%, rgba(100,70,0,0.25) 100%)', pointerEvents: 'none', zIndex: 0 }} />
|
||||
|
||||
{!isMobile && (
|
||||
<div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
|
||||
<img src="/images/preistraeger-jubel.jpg" alt="Preisverleihung 2025" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 16, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>Preisverleihung 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ padding: isMobile ? '28px 20px' : '24px 48px 32px 40px', flex: 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 560 : 0, position: 'relative', zIndex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 24 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.28em', fontWeight: 700 }}>Bewerbung 2026</span>
|
||||
<Link
|
||||
to="/formular-hochladen"
|
||||
style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(17,29,85,0.55)', border: '1px solid rgba(17,29,85,0.2)', padding: '7px 14px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, transition: 'color 0.15s, border-color 0.15s', whiteSpace: 'nowrap', flexShrink: 0 }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = '#111D55'; (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'rgba(17,29,85,0.55)'; (e.currentTarget as HTMLElement).style.borderColor = 'rgba(17,29,85,0.2)'; }}
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none"><path d="M6 1v7M3 4l3-3 3 3M1 10h10" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
PDF hochladen
|
||||
</Link>
|
||||
</div>
|
||||
<BewerbungsForm theme="gold" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 20px 20px' : '14px 56px 18px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, justifyContent: 'center', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
||||
<span style={{ fontFamily: FB, fontSize: 14, color: 'rgba(255,255,255,0.55)', lineHeight: 1.6, maxWidth: 520 }}>
|
||||
Sie haben Ihre Unterlagen bereits als PDF vorbereitet? Laden Sie sie direkt hoch, statt das Formular auszufüllen.
|
||||
</span>
|
||||
<Link to="/formular-hochladen" style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(239,191,4,0.7)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, borderBottom: '1px solid rgba(239,191,4,0.3)', paddingBottom: 1 }}>
|
||||
Formular hochladen →
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.25), transparent)', flexShrink: 0 }} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── QualRow ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function QualRow({ item, Icon, last }: { item: { num: string; title: string; body: string }; Icon: React.ElementType; last: boolean }) {
|
||||
const [hovered, setHovered] = React.useState(false);
|
||||
const isMobileRow = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
style={{ display: 'grid', gridTemplateColumns: isMobileRow ? '40px 1fr' : '80px 200px 1fr 48px', gap: isMobileRow ? '0 16px' : '0 32px', padding: isMobileRow ? '24px 24px' : '28px 80px', borderBottom: last ? 'none' : '1px solid #D0D5DD', alignItems: isMobileRow ? 'flex-start' : 'center', background: hovered ? '#EAE7E6' : 'transparent', transition: 'background 0.15s', cursor: 'default' }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: hovered ? GOLD : 'rgba(239,191,4,0.5)', letterSpacing: '0.1em', transition: 'color 0.2s', paddingTop: 2 }}>{item.num}</span>
|
||||
{isMobileRow ? (
|
||||
<div>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 4 }}>{item.title}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.5)', lineHeight: 1.6 }}>{item.body}</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em' }}>{item.title}</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.5)', lineHeight: 1.6 }}>{item.body}</div>
|
||||
<div style={{ width: 36, height: 36, background: hovered ? GOLD : 'rgba(3,9,58,0.06)', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 0.2s' }}>
|
||||
<Icon size={16} style={{ color: hovered ? '#101828' : 'rgba(16,24,40,0.3)', transition: 'color 0.2s' }} strokeWidth={1.5} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── CriteriaCell ──────────────────────────────────────────────────────────────
|
||||
|
||||
function CriteriaCell({ crit, idx }: { crit: { title: string; desc: string }; idx: number }) {
|
||||
const [hovered, setHovered] = React.useState(false);
|
||||
const isMobileCell = useIsMobile();
|
||||
return (
|
||||
<div
|
||||
style={{ padding: isMobileCell ? '32px 24px' : '56px 40px', borderLeft: !isMobileCell && idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none', borderTop: isMobileCell && idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none', display: 'flex', flexDirection: 'column', gap: 0, cursor: 'default', transition: 'background 0.2s', background: hovered ? 'rgba(255,255,255,0.03)' : 'transparent' }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 24 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.15em', textTransform: 'uppercase' }}>0{idx + 1}</span>
|
||||
</div>
|
||||
<div style={{ width: hovered ? '100%' : 28, height: 1, background: hovered ? 'rgba(239,191,4,0.6)' : 'rgba(239,191,4,0.3)', marginBottom: 20, transition: 'width 0.5s ease' }} />
|
||||
<h4 style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.08em', lineHeight: 1.3, marginBottom: 14 }}>{crit.title}</h4>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, flex: 1 }}>{crit.desc}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── BewerbungswegeSection ─────────────────────────────────────────────────────
|
||||
|
||||
const INSTITUTIONEN = [
|
||||
'Senatoren und Mitglieder der Verbandsgruppe „Wir Eigentümerunternehmer"',
|
||||
'Bundesverband Deutscher Mittelstand (BM)',
|
||||
'Europäisches Wirtschaftsforum (EWIF)',
|
||||
'Union Mittelständischer Unternehmen (UMU)',
|
||||
'HAM – Hochschule für angewandtes Management und deren Regionalpartner',
|
||||
'Mitglieder des Wirtschaftsbeirates Bayern',
|
||||
'Ehemalige Preisträger des Bayerischen Mittelstandspreises',
|
||||
];
|
||||
|
||||
function BewerbungswegeSection() {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<section style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
|
||||
{/* Header */}
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', borderBottom: '1px solid #D0D5DD' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Bewerbung 2026</span>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 16 : 48, alignItems: 'flex-end' }}>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.5vw, 3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.03, margin: 0 }}>
|
||||
HIER KÖNNEN SIE SICH<br />BEWERBEN ODER EIN<br />UNTERNEHMEN VORSCHLAGEN.
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.55)', lineHeight: 1.8, margin: 0 }}>
|
||||
Als Unternehmen haben Sie zwei Möglichkeiten: Sie werden von einem unterstützenden Partner oder einer Institution vorgeschlagen, oder Sie ergreifen selbst die Initiative und füllen unsere Anmeldung aus. Die Teilnahme ist in allen Fällen kostenfrei.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3 Wege – premium swipe cards on mobile, 3-col grid on desktop */}
|
||||
<div style={{ position: 'relative', zIndex: 1, display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : 'repeat(3, 1fr)', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none', gap: isMobile ? 14 : 0, padding: isMobile ? '28px 24px 36px' : 0 }}>
|
||||
|
||||
{/* ── MOBILE: unified premium cards ── */}
|
||||
{isMobile && ([
|
||||
{ weg: 'Weg 01', title: 'Vorschlag unterbreiten', desc: 'Durch ehemalige Preisträger, die HAM, Senatoren und den Preis unterstützende Institutionen.', Icon: Send, checkItems: INSTITUTIONEN, cta: 'Vorschlag unterbreiten', featured: false },
|
||||
{ weg: 'Weg 02', title: 'Initiativbewerbung', desc: 'Für Unternehmer, die selbst die Initiative ergreifen.', Icon: UserCheck, kvItems: [ { k: '0 EUR', v: 'Teilnahmegebühr' }, { k: 'Eigeninitiative', v: 'Direkt ohne Intermediär' }, { k: 'Für Unternehmer', v: 'Inhabergeführte KMU in Bayern' }, { k: 'Deadline', v: '30. Juni 2026' } ], cta: 'Jetzt bewerben', featured: true },
|
||||
{ weg: 'Weg 03 · Sonderpreis', title: 'Bavarian Future Award', desc: 'Sonderpreis, initiiert durch die Studierenden der HAM, Hochschule für angewandtes Management.', Icon: Star, kvItems: [ { k: 'Vorschlag möglich', v: 'Durch HAM & Partner' }, { k: 'Selbstbewerbung', v: 'Direkte Einreichung' }, { k: 'Initiiert von', v: 'Studierenden der HAM' }, { k: 'Kategorie', v: 'Zukunft & Innovation' } ], cta: 'Zum Formular', featured: false },
|
||||
] as { weg: string; title: string; desc: string; Icon: any; checkItems?: string[]; kvItems?: { k: string; v: string }[]; cta: string; featured: boolean }[]).map((w) => {
|
||||
const Icon = w.Icon;
|
||||
return (
|
||||
<div key={w.weg} style={{ flexShrink: 0, minWidth: '85vw', maxWidth: '85vw', scrollSnapAlign: 'center', display: 'flex', flexDirection: 'column', background: '#fff', borderRadius: 18, overflow: 'hidden', border: w.featured ? `1.5px solid ${GOLD}` : '1px solid rgba(17,29,85,0.10)', boxShadow: w.featured ? '0 14px 34px rgba(239,191,4,0.20), 0 6px 16px rgba(3,9,58,0.10)' : '0 10px 28px rgba(3,9,58,0.08)' }}>
|
||||
{/* Header */}
|
||||
<div style={{ padding: '22px 22px 0', position: 'relative' }}>
|
||||
{w.featured && (
|
||||
<span style={{ position: 'absolute', top: 22, right: 22, fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '5px 10px', borderRadius: 100 }}>Empfohlen</span>
|
||||
)}
|
||||
<div style={{ width: 46, height: 46, borderRadius: 12, background: w.featured ? GOLD : NAVY, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 18 }}>
|
||||
<Icon size={20} style={{ color: w.featured ? '#101828' : GOLD }} strokeWidth={1.6} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: w.featured ? '#A87800' : 'rgba(16,24,40,0.4)', display: 'block', marginBottom: 8 }}>{w.weg}</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: '1.45rem', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 10px' }}>{w.title}</h3>
|
||||
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(16,24,40,0.55)', lineHeight: 1.6, margin: '0 0 20px' }}>{w.desc}</p>
|
||||
<div style={{ width: 36, height: 2, background: GOLD }} />
|
||||
</div>
|
||||
{/* Detail list */}
|
||||
<div style={{ padding: '4px 22px 0', flex: 1 }}>
|
||||
{w.checkItems && w.checkItems.map((inst, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '12px 0', borderBottom: '1px solid rgba(17,29,85,0.08)' }}>
|
||||
<div style={{ width: 18, height: 18, borderRadius: 5, background: GOLD, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, marginTop: 1 }}>
|
||||
<svg width="9" height="9" viewBox="0 0 8 8"><polyline points="1,4 3,6 7,2" stroke="#101828" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
</div>
|
||||
<span style={{ fontFamily: FB, fontSize: 16, color: 'rgba(16,24,40,0.7)', lineHeight: 1.5 }}>{inst}</span>
|
||||
</div>
|
||||
))}
|
||||
{w.kvItems && w.kvItems.map((item, i) => (
|
||||
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12, padding: '13px 0', borderBottom: '1px solid rgba(17,29,85,0.08)' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 14, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.06em', flexShrink: 0 }}>{item.k}</span>
|
||||
<span style={{ fontFamily: FB, fontSize: 15, color: 'rgba(16,24,40,0.5)', textAlign: 'right' }}>{item.v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* CTA */}
|
||||
<div style={{ padding: '18px 22px 22px' }}>
|
||||
<a href="#bewerben" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, minHeight: 52, background: GOLD, color: '#101828', fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', textDecoration: 'none', borderRadius: 10 }}>{w.cta} <ArrowRight size={14} /></a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* ── DESKTOP: original 3 columns ── */}
|
||||
{!isMobile && (<>
|
||||
|
||||
{/* ── Weg A: Vorschlag ── */}
|
||||
<div style={{ padding: isMobile ? '32px 24px' : '56px 48px 56px', borderRight: '1px solid #D0D5DD', display: 'flex', flexDirection: 'column', ...(isMobile ? { minWidth: '83vw', maxWidth: '83vw', flexShrink: 0, scrollSnapAlign: 'start' } : {}) }}>
|
||||
<div style={{ width: 44, height: 44, background: NAVY, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 28 }}>
|
||||
<Send size={18} style={{ color: GOLD }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.4)', display: 'block', marginBottom: 10 }}>Weg 01</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: '0 0 8px' }}>
|
||||
Vorschlag<br />unterbreiten
|
||||
</h3>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.7, margin: '0 0 32px' }}>
|
||||
Durch ehemalige Preisträger, die HAM, Senatoren und den Preis unterstützende Institutionen.
|
||||
</p>
|
||||
|
||||
{/* Institution list */}
|
||||
<div style={{ borderTop: '1px solid #D0D5DD', flex: 1 }}>
|
||||
{INSTITUTIONEN.map((inst, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '12px 0', borderBottom: '1px solid #D0D5DD' }}>
|
||||
<div style={{ width: 16, height: 16, background: GOLD, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, marginTop: 2 }}>
|
||||
<svg width="8" height="8" viewBox="0 0 8 8"><polyline points="1,4 3,6 7,2" stroke="#101828" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
</div>
|
||||
<span style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.65)', lineHeight: 1.55 }}>{inst}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#bewerben"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Vorschlag unterbreiten <ArrowRight size={13} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* ── Weg B: Initiativbewerbung ── */}
|
||||
<div style={{ padding: isMobile ? '32px 24px' : '56px 48px 56px', borderRight: '1px solid #D0D5DD', display: 'flex', flexDirection: 'column', background: NAVY, ...(isMobile ? { minWidth: '83vw', maxWidth: '83vw', flexShrink: 0, scrollSnapAlign: 'start' } : {}) }}>
|
||||
<div style={{ width: 44, height: 44, background: GOLD, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 28 }}>
|
||||
<UserCheck size={18} style={{ color: '#101828' }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)', display: 'block', marginBottom: 10 }}>Weg 02</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: '0 0 8px' }}>
|
||||
Initiativbewerbung
|
||||
</h3>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.62)', lineHeight: 1.7, margin: '0 0 32px' }}>
|
||||
Für Unternehmer, die selbst die Initiative ergreifen.
|
||||
</p>
|
||||
|
||||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.1)', flex: 1 }}>
|
||||
{[
|
||||
{ label: '0 EUR', desc: 'Teilnahmegebühr' },
|
||||
{ label: 'Eigeninitiative', desc: 'Direkt ohne Intermediär' },
|
||||
{ label: 'Für Unternehmer', desc: 'Inhabergeführte KMU in Bayern' },
|
||||
{ label: 'Deadline', desc: '30. Juni 2026' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '14px 0', borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.07em' }}>{item.label}</span>
|
||||
<span style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.50)' }}>{item.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#bewerben"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Jetzt bewerben <ArrowRight size={13} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* ── Weg C: Sonderpreis ── */}
|
||||
<div style={{ padding: isMobile ? '32px 24px' : '56px 48px 56px', display: 'flex', flexDirection: 'column', ...(isMobile ? { minWidth: '83vw', maxWidth: '83vw', flexShrink: 0, scrollSnapAlign: 'start' } : {}) }}>
|
||||
<div style={{ width: 44, height: 44, background: NAVY, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 28 }}>
|
||||
<Star size={18} style={{ color: GOLD }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.4)', display: 'block', marginBottom: 10 }}>Weg 03 · Sonderpreis</span>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: '0 0 8px' }}>
|
||||
Bavarian<br />Future Award
|
||||
</h3>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.7, margin: '0 0 32px' }}>
|
||||
Sonderpreis, initiiert durch die Studierenden der HAM, Hochschule für angewandtes Management.
|
||||
</p>
|
||||
|
||||
<div style={{ borderTop: '1px solid #D0D5DD', flex: 1 }}>
|
||||
{[
|
||||
{ label: 'Vorschlag möglich', desc: 'Durch HAM & Partner' },
|
||||
{ label: 'Selbstbewerbung', desc: 'Direkte Einreichung' },
|
||||
{ label: 'Initiiert von', desc: 'Studierenden der HAM' },
|
||||
{ label: 'Kategorie', desc: 'Zukunft & Innovation' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '14px 0', borderBottom: '1px solid #D0D5DD' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em' }}>{item.label}</span>
|
||||
<span style={{ fontFamily: FB, fontSize: 16, color: 'rgba(16,24,40,0.45)' }}>{item.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#bewerben"
|
||||
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
|
||||
>
|
||||
Zum Formular <ArrowRight size={13} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</>)}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Participation;
|
||||
324
src/spa/pages/Preistraeger.tsx
Normal file
324
src/spa/pages/Preistraeger.tsx
Normal file
@@ -0,0 +1,324 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { Trophy, Search, ChevronDown, ChevronRight, X, ArrowRight } from 'lucide-react';
|
||||
import { WINNERS, CATEGORIES, YEARS } from '@/spa/data/winners';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const BORDER = '#D0D5DD';
|
||||
const GRAY = '#666666';
|
||||
const BG_ALT = '#E4E2E3';
|
||||
|
||||
export default function Preistraeger() {
|
||||
const isMobile = useIsMobile();
|
||||
const [activeYear, setActiveYear] = useState(YEARS[0]); // default: most recent
|
||||
const [filterCategory, setFilterCategory] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const [mediaOnly, setMediaOnly] = useState(false);
|
||||
|
||||
const filtered = useMemo(() => WINNERS.filter(w => {
|
||||
if (w.year !== activeYear) return false;
|
||||
if (filterCategory && w.category !== filterCategory) return false;
|
||||
if (search && !w.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
if (mediaOnly && !w.hasMedia) return false;
|
||||
return true;
|
||||
}), [activeYear, filterCategory, search, mediaOnly]);
|
||||
|
||||
const reset = () => { setFilterCategory(''); setSearch(''); setMediaOnly(false); };
|
||||
const hasFilter = !!(filterCategory || search || mediaOnly);
|
||||
|
||||
return (
|
||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||
{/* Hero */}
|
||||
<div style={{ width: '100%', height: '50vh', overflow: 'hidden', position: 'relative' }}>
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1492684223066-81342ee5ff30?auto=format&fit=crop&q=80&w=2000"
|
||||
alt="Preisverleihung"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, transparent 40%, rgba(3,9,58,0.82) 100%)' }} />
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)' }} />
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div style={{ background: '#fff', borderBottom: `1px solid ${BORDER}`, padding: isMobile ? '18px 24px' : '18px 80px' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 16, color: GOLD, fontWeight: 600, letterSpacing: '0.04em' }}>Preisträger:innen</span>
|
||||
</div>
|
||||
|
||||
{/* Filter Section */}
|
||||
<div style={{ background: '#fff', padding: isMobile ? '32px 24px 24px' : '48px 80px 32px' }}>
|
||||
{/* #93: Headline entfernt – Seitenname steht bereits oben im Breadcrumb. */}
|
||||
|
||||
{/* Year Tabs */}
|
||||
<div style={{ display: 'flex', flexWrap: isMobile ? 'nowrap' : 'wrap', gap: 0, marginBottom: 32, borderBottom: `2px solid ${BORDER}`, overflowX: isMobile ? 'auto' : 'visible', WebkitOverflowScrolling: 'touch', maxWidth: '100%' }}>
|
||||
{YEARS.map(year => (
|
||||
<button
|
||||
key={year}
|
||||
onClick={() => { setActiveYear(year); reset(); }}
|
||||
style={{
|
||||
fontFamily: FF, fontSize: 16, fontWeight: 700,
|
||||
padding: isMobile ? '12px 20px' : '12px 32px',
|
||||
flexShrink: 0,
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
color: activeYear === year ? NAVY : GRAY,
|
||||
borderBottom: activeYear === year ? `2px solid ${GOLD}` : '2px solid transparent',
|
||||
marginBottom: -2,
|
||||
letterSpacing: '0.04em',
|
||||
transition: 'color 0.15s, border-color 0.15s',
|
||||
}}
|
||||
>
|
||||
{year}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Filter Row */}
|
||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 16, alignItems: 'stretch', marginBottom: 20, flexWrap: 'wrap' }}>
|
||||
<SelectControl
|
||||
value={filterCategory}
|
||||
onChange={setFilterCategory}
|
||||
placeholder="Preiskategorie …"
|
||||
options={CATEGORIES.map(c => ({ label: c, value: c }))}
|
||||
width={isMobile ? undefined : 280}
|
||||
/>
|
||||
<SearchControl value={search} onChange={setSearch} />
|
||||
</div>
|
||||
|
||||
{/* Toggle row */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: '#101828' }}>
|
||||
Diese Erfolgsgeschichten müssen erzählt werden.
|
||||
</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ fontSize: 15 }}>💬</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY }}>Mit Medienbeiträgen (Videos & Storys)</span>
|
||||
<button
|
||||
onClick={() => setMediaOnly(v => !v)}
|
||||
style={{
|
||||
width: 40, height: 22, borderRadius: 11, border: 'none', cursor: 'pointer',
|
||||
background: mediaOnly ? NAVY : BORDER,
|
||||
position: 'relative', transition: 'background 0.2s',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
position: 'absolute', top: 3, left: mediaOnly ? 20 : 3, width: 16, height: 16,
|
||||
borderRadius: '50%', background: '#fff', transition: 'left 0.2s',
|
||||
}} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{hasFilter && (
|
||||
<button
|
||||
onClick={reset}
|
||||
style={{ fontFamily: FF, fontSize: 16, color: GRAY, background: 'none', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6 }}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = GOLD)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = GRAY)}
|
||||
>
|
||||
<X size={14} /> Filter zurücksetzen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Count bar */}
|
||||
<div style={{ padding: isMobile ? '12px 24px' : '12px 80px', background: BG_ALT, borderTop: `1px solid ${BORDER}`, borderBottom: `1px solid ${BORDER}`, display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Trophy size={13} style={{ color: GOLD }} fill={GOLD} />
|
||||
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY }}>
|
||||
<strong style={{ color: '#101828' }}>{activeYear}</strong> – {filtered.length} Preisträger:innen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
{filtered.length === 0 ? (
|
||||
<div style={{ padding: isMobile ? '56px 24px' : '80px', textAlign: 'center', color: GRAY, fontFamily: FF }}>
|
||||
Keine Preisträger für diese Auswahl.
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? undefined : 'repeat(4, 1fr)',
|
||||
gridAutoFlow: isMobile ? 'column' : undefined,
|
||||
gridTemplateRows: isMobile ? 'repeat(2, 1fr)' : undefined,
|
||||
gridAutoColumns: isMobile ? '74vw' : undefined,
|
||||
overflowX: isMobile ? 'auto' : undefined,
|
||||
scrollSnapType: isMobile ? 'x mandatory' : undefined,
|
||||
gap: isMobile ? 8 : 0,
|
||||
padding: isMobile ? '0 16px 8px' : 0,
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'none',
|
||||
}}>
|
||||
{filtered.map(w => (
|
||||
<WinnerCard key={w.id} winner={w} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bottom CTA */}
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '64px 80px', borderTop: `1px solid ${BORDER}`, textAlign: 'center' }}>
|
||||
<p style={{ fontFamily: FF, fontSize: 17, color: GRAY, marginBottom: 20 }}>Werden Sie der nächste Preisträger.</p>
|
||||
<Link
|
||||
to="/teilnahme"
|
||||
style={{
|
||||
fontFamily: FF, fontSize: 16, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em', color: '#101828', background: GOLD,
|
||||
padding: '15px 36px', textDecoration: 'none',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'background 0.15s, box-shadow 0.2s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||
>
|
||||
Jetzt kostenlos bewerben <ArrowRight size={14} />
|
||||
</Link>
|
||||
<div style={{ marginTop: 20, display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : undefined, flexWrap: isMobile ? 'wrap' : 'nowrap', gap: 8 }}>
|
||||
<span style={{ width: 20, height: 1, background: 'rgba(239,191,4,0.3)', display: 'inline-block' }} />
|
||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 11, color: 'rgba(16,24,40,0.4)' }}>Diese Arbeit unterstützen:</span>
|
||||
<Link
|
||||
to="/mitglied-werden"
|
||||
style={{
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(239,191,4,0.7)',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
borderBottom: '1px solid rgba(239,191,4,0.3)',
|
||||
paddingBottom: 1,
|
||||
transition: 'color 0.15s, borderColor 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = '#EFBF04';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = '#EFBF04';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLElement).style.color = 'rgba(239,191,4,0.7)';
|
||||
(e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)';
|
||||
}}
|
||||
>
|
||||
Vereinsmitglied werden <ChevronRight size={10} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── WinnerCard ───────────────────────────────────────────────────────────────
|
||||
|
||||
function WinnerCard({ winner }: { winner: import('@/spa/data/winners').Winner }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Link
|
||||
to={`/preistraeger/${winner.slug}`}
|
||||
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 260 : 340, overflow: 'hidden', boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.07)', scrollSnapAlign: isMobile ? 'start' : undefined }}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<img
|
||||
src={winner.img}
|
||||
alt={winner.name}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.5s ease', transform: hovered ? 'scale(1.06)' : 'scale(1)' }}
|
||||
/>
|
||||
{/* base gradient */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.15) 55%, transparent 100%)' }} />
|
||||
{/* hover overlay */}
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.22)', opacity: hovered ? 1 : 0, transition: 'opacity 0.25s' }} />
|
||||
|
||||
{/* Text */}
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, padding: '18px 22px', width: '100%', boxSizing: 'border-box' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 }}>
|
||||
<Trophy size={12} style={{ color: GOLD, flexShrink: 0 }} fill={GOLD} />
|
||||
<span style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: GOLD, letterSpacing: '0.12em', textTransform: 'uppercase' }}>
|
||||
{winner.year} · {winner.type}
|
||||
</span>
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 700, color: '#fff', margin: 0, lineHeight: 1.3, textShadow: '0 1px 4px rgba(0,0,0,0.5)' }}>
|
||||
{winner.name}
|
||||
</h3>
|
||||
<p style={{ fontFamily: FF, fontSize: 16, color: 'rgba(255,255,255,0.82)', margin: '4px 0 0', lineHeight: 1.3 }}>
|
||||
{winner.category}
|
||||
</p>
|
||||
|
||||
{/* Pill button */}
|
||||
<div style={{
|
||||
marginTop: 12,
|
||||
display: 'inline-flex', alignItems: 'center', gap: 7,
|
||||
background: NAVY, color: '#fff',
|
||||
padding: '9px 18px', borderRadius: 999,
|
||||
fontSize: 16, fontWeight: 700, fontFamily: FF,
|
||||
textTransform: 'uppercase', letterSpacing: '0.06em',
|
||||
opacity: hovered ? 1 : 0,
|
||||
transform: hovered ? 'translateY(0)' : 'translateY(6px)',
|
||||
transition: 'opacity 0.25s, transform 0.25s',
|
||||
}}>
|
||||
<Trophy size={14} /> Mehr erfahren
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// ── SelectControl ────────────────────────────────────────────────────────────
|
||||
|
||||
function SelectControl({ value, onChange, placeholder, options, width }: {
|
||||
value: string; onChange: (v: string) => void;
|
||||
placeholder: string; options: { label: string; value: string }[];
|
||||
width?: number;
|
||||
}) {
|
||||
const active = !!value;
|
||||
return (
|
||||
<div style={{ position: 'relative', width: width ?? '100%', flexShrink: width ? 0 : 1 }}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
style={{
|
||||
width: '100%', height: 44, padding: '0 36px 0 14px',
|
||||
fontFamily: FF, fontSize: 16,
|
||||
border: `1px solid ${active ? NAVY : BORDER}`,
|
||||
color: active ? NAVY : GRAY,
|
||||
background: '#fff', borderRadius: 0,
|
||||
appearance: 'none', cursor: 'pointer', outline: 'none',
|
||||
transition: 'border-color 0.15s',
|
||||
}}
|
||||
onFocus={e => (e.currentTarget.style.borderColor = GOLD)}
|
||||
onBlur={e => (e.currentTarget.style.borderColor = active ? NAVY : BORDER)}
|
||||
>
|
||||
<option value="">{placeholder}</option>
|
||||
{options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
|
||||
</select>
|
||||
<ChevronDown size={16} style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: '#101828' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── SearchControl ────────────────────────────────────────────────────────────
|
||||
|
||||
function SearchControl({ value, onChange }: { value: string; onChange: (v: string) => void }) {
|
||||
return (
|
||||
<div style={{ position: 'relative', flex: 1, minWidth: 200 }}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder="Stichwortsuche …"
|
||||
style={{
|
||||
width: '100%', height: 44, padding: '0 40px 0 14px',
|
||||
fontFamily: FF, fontSize: 16,
|
||||
border: `1px solid ${BORDER}`, borderRadius: 0,
|
||||
background: '#fff', outline: 'none', boxSizing: 'border-box',
|
||||
transition: 'border-color 0.15s', color: '#101828',
|
||||
}}
|
||||
onFocus={e => (e.currentTarget.style.borderColor = GOLD)}
|
||||
onBlur={e => (e.currentTarget.style.borderColor = BORDER)}
|
||||
/>
|
||||
<Search size={16} style={{ position: 'absolute', right: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: GRAY }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
164
src/spa/pages/PreistraegerDetail.tsx
Normal file
164
src/spa/pages/PreistraegerDetail.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams, Navigate } from '@/spa/router';
|
||||
import { Trophy, ArrowLeft, MapPin, Building2, Globe, ChevronRight } from 'lucide-react';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const BORDER = '#D0D5DD';
|
||||
const GRAY = '#666666';
|
||||
const BG_ALT = '#E4E2E3';
|
||||
|
||||
export default function PreistraegerDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const winner = WINNERS.find(w => w.slug === slug);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!winner) return <Navigate to="/preistraeger" replace />;
|
||||
|
||||
// Pick a second photo (offset from primary)
|
||||
const secondImg = winner.img.replace('w=800', 'w=900').replace('q=80', 'q=75');
|
||||
|
||||
return (
|
||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||
{/* Hero */}
|
||||
<div style={{ width: '100%', height: '60vh', overflow: 'hidden', position: 'relative' }}>
|
||||
<img
|
||||
src={winner.img}
|
||||
alt={winner.name}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, transparent 50%, rgba(3,9,58,0.82) 100%)' }} />
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div style={{ background: '#fff', borderBottom: `1px solid ${BORDER}`, padding: isMobile ? '16px 24px' : '16px 80px', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Link to="/preistraeger" style={{ fontFamily: FF, fontSize: 16, color: GOLD, textDecoration: 'none', fontWeight: 600 }}>
|
||||
Preisträger:innen
|
||||
</Link>
|
||||
<ChevronRight size={14} style={{ color: GRAY }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 16, color: GRAY }}>{winner.name}</span>
|
||||
</div>
|
||||
|
||||
{/* Header block */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 28px' : '48px 80px 40px', borderBottom: `1px solid ${BORDER}` }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16, flexWrap: 'wrap' }}>
|
||||
<span style={{
|
||||
fontFamily: FF, fontSize: 11, fontWeight: 700, color: '#fff',
|
||||
background: NAVY, padding: '4px 14px', borderRadius: 999,
|
||||
textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
}}>
|
||||
{winner.category}
|
||||
</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: GOLD }}>
|
||||
{winner.year}
|
||||
</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
||||
{winner.type}
|
||||
</span>
|
||||
</div>
|
||||
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 5vw, 3rem)' : 'clamp(2rem, 5vw, 3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: '0 0 12px' }}>
|
||||
{winner.name}
|
||||
</h1>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: GRAY, margin: 0, maxWidth: 640, lineHeight: 1.6 }}>
|
||||
{winner.shortDesc}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Content 2-col */}
|
||||
<div style={{ padding: isMobile ? '48px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 1fr', gap: isMobile ? 40 : 64 }}>
|
||||
{/* Left: Text */}
|
||||
<div>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
||||
Über das Unternehmen
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
||||
{winner.longDesc}
|
||||
</p>
|
||||
|
||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
||||
Warum ausgezeichnet?
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
||||
Die Jury des Bayerischen Mittelstandspreises überzeugte {winner.name} durch außergewöhnliche Leistungen im Bereich <strong>{winner.category}</strong>. Das Unternehmen steht exemplarisch für die Stärken des bayerischen Mittelstands: unternehmerischen Mut, wertebasierte Führung und nachhaltige Wirkung weit über den eigenen Betrieb hinaus.
|
||||
</p>
|
||||
|
||||
{/* Quote */}
|
||||
<div style={{ borderLeft: `4px solid ${GOLD}`, paddingLeft: 24, marginBottom: 40 }}>
|
||||
<p style={{ fontFamily: FB, fontStyle: 'italic', fontSize: 18, color: '#101828', lineHeight: 1.7, marginBottom: 12 }}>
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginRight: 4, fontFamily: '"IBM Plex Sans"' }}>"</span>
|
||||
{winner.quote}
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginLeft: 2, fontFamily: '"IBM Plex Sans"' }}>"</span>
|
||||
</p>
|
||||
<p style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: GRAY, margin: 0, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
||||
– {winner.quotePerson}, {winner.quoteRole}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Photo + Info box */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
{/* Photo */}
|
||||
<div style={{ borderRadius: 0, overflow: 'hidden', aspectRatio: '4/3' }}>
|
||||
<img src="/gala-preistraeger.jpg" alt="Gala Preisverleihung" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', filter: 'brightness(0.95)' }} />
|
||||
</div>
|
||||
|
||||
{/* Kenndaten */}
|
||||
<div style={{ background: BG_ALT, borderLeft: `3px solid ${GOLD}`, padding: '20px 24px' }}>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 20 }}>
|
||||
Auf einen Blick
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{[
|
||||
{ label: 'Kategorie', value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Auszeichnung', value: winner.type, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Jahr', value: String(winner.year), icon: null },
|
||||
{ label: 'Standort', value: winner.location, icon: <MapPin size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Branche', value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Website button */}
|
||||
<a
|
||||
href={`https://${winner.website}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
fontFamily: FF, fontSize: 16, fontWeight: 700,
|
||||
color: '#101828', border: `2px solid ${NAVY}`,
|
||||
padding: '12px 20px', textDecoration: 'none',
|
||||
textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||
transition: 'all 0.2s', borderRadius: 0, overflowWrap: 'anywhere',
|
||||
}}
|
||||
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = NAVY; el.style.color = '#fff'; }}
|
||||
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = 'transparent'; el.style.color = NAVY; }}
|
||||
>
|
||||
<Globe size={16} /> Website besuchen ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Back link */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 48px' : '32px 80px 64px', borderTop: `1px solid ${BORDER}` }}>
|
||||
<Link
|
||||
to="/preistraeger"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textDecoration: 'none', textTransform: 'uppercase', letterSpacing: '0.06em', transition: 'color 0.15s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = GOLD)}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = NAVY)}
|
||||
>
|
||||
<ArrowLeft size={16} /> Alle Preisträger
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
841
src/spa/pages/Press.tsx
Normal file
841
src/spa/pages/Press.tsx
Normal file
@@ -0,0 +1,841 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { Calendar, MapPin, Download, ArrowRight, FileText } from 'lucide-react';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
const CREAM = '#E4E2E3';
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
|
||||
const events = [
|
||||
{
|
||||
title: 'Preisverleihung 2026',
|
||||
date: '22. Okt 2026',
|
||||
location: 'München, Residenz',
|
||||
cat: 'Gala / Event',
|
||||
status: 'In Planung',
|
||||
img: '/images/buehne-moderatoren.jpg',
|
||||
desc: 'Der glanzvolle Höhepunkt des Jahres. Die Verleihung der Preise in der prachtvollen Kulisse der Residenz München.',
|
||||
slug: '/presse/events/preisverleihung-2026',
|
||||
},
|
||||
{
|
||||
title: 'Mittelstands-Gipfel',
|
||||
date: '15. Juni 2026',
|
||||
location: 'Nürnberg',
|
||||
cat: 'Workshop',
|
||||
status: 'Anmeldung offen',
|
||||
img: '/images/networking-innenhof.jpg',
|
||||
desc: 'Regionaler Austausch und Best-Practices für bayerische Unternehmen auf dem Weg zum Preis.',
|
||||
slug: '/presse/events/mittelstands-gipfel-2026',
|
||||
},
|
||||
{
|
||||
title: 'Nominierten-Auswahl',
|
||||
date: '04. Aug 2026',
|
||||
location: 'Regensburg',
|
||||
cat: 'Jury-Sitzung',
|
||||
status: 'Intern',
|
||||
img: '/images/saal-gedeckt.jpg',
|
||||
desc: 'Das Gremium sichtet die Ergebnisse der Audits und legt die Nominierten für die Hauptpreise fest.',
|
||||
slug: '/presse/events/nominierten-auswahl-2026',
|
||||
},
|
||||
];
|
||||
|
||||
const news = [
|
||||
{
|
||||
title: 'Wie Bayerns KMU die KI nutzen',
|
||||
excerpt:
|
||||
'Ein Deep-Dive in die Bewerbungsunterlagen 2024 zeigt: Der Mittelstand treibt die Digitalisierung aktiv voran.',
|
||||
cat: 'Innovation',
|
||||
img: '/images/gala-saal-overview.jpg',
|
||||
slug: '/presse/blog/ki-nutzung-bayerischer-kmu',
|
||||
},
|
||||
{
|
||||
title: 'Start der Ehrenamts-Initiative',
|
||||
excerpt:
|
||||
'Gemeinsam mit unseren Partnern fördern wir soziale Projekte mittelständischer Unternehmen.',
|
||||
cat: 'Engagement',
|
||||
img: '/images/networking-innenhof.jpg',
|
||||
slug: '/presse/blog/ehrenamts-initiative-2026',
|
||||
},
|
||||
{
|
||||
title: 'Bayerische Wirtschaft wächst',
|
||||
excerpt:
|
||||
'Neue Prognosen zeigen ein stabiles Wachstum für den Mittelstand – gute Aussichten für die Awards.',
|
||||
cat: 'Wirtschaft',
|
||||
img: '/images/gala-dinner.jpg',
|
||||
slug: '/presse/blog/bayerische-wirtschaft-waechst',
|
||||
},
|
||||
];
|
||||
|
||||
function NewsCard({ item, idx, isMobile }: { item: typeof news[0]; idx: number; isMobile: boolean }) {
|
||||
const [hovered, setHovered] = React.useState(false);
|
||||
return (
|
||||
<Link
|
||||
to={item.slug}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
cursor: 'pointer',
|
||||
borderLeft: isMobile ? 'none' : (idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none'),
|
||||
borderBottom: isMobile && idx < news.length - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
{/* image */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', height: 220 }}>
|
||||
<img
|
||||
src={item.img}
|
||||
alt={item.title}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
filter: hovered ? 'none' : 'grayscale(100%)',
|
||||
transition: 'filter 0.5s',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '50%',
|
||||
background: 'linear-gradient(to top, rgba(3,9,58,0.8), transparent)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* body */}
|
||||
<div style={{ padding: isMobile ? '24px 24px 32px' : '32px 36px 40px', display: 'flex', flexDirection: 'column', flex: 1 }}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 9,
|
||||
color: GOLD,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.22em',
|
||||
fontWeight: 700,
|
||||
marginBottom: 12,
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{item.cat}
|
||||
</span>
|
||||
<h3
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 'clamp(1rem, 1.5vw, 1.2rem)',
|
||||
fontWeight: 700,
|
||||
color: '#fff',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.01em',
|
||||
lineHeight: 1.25,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</h3>
|
||||
<div
|
||||
style={{
|
||||
width: hovered ? '100%' : 24,
|
||||
height: 1,
|
||||
background: 'rgba(239,191,4,0.5)',
|
||||
transition: 'width 0.4s ease',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(255,255,255,0.4)',
|
||||
lineHeight: 1.7,
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
{item.excerpt}
|
||||
</p>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em',
|
||||
color: GOLD,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
Weiterlesen <ArrowRight size={12} />
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const Press: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [hoveredEvent, setHoveredEvent] = useState<number | null>(null);
|
||||
const [dlHovered, setDlHovered] = useState(false);
|
||||
const [submitHovered, setSubmitHovered] = useState(false);
|
||||
|
||||
// Form state
|
||||
const [medium, setMedium] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (medium.trim() && name.trim() && email.trim()) {
|
||||
setSubmitted(true);
|
||||
}
|
||||
};
|
||||
|
||||
// DarkInput defined inline as required
|
||||
function DarkInput(props: React.InputHTMLAttributes<HTMLInputElement>) {
|
||||
const [f, setF] = React.useState(false);
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={(e) => {
|
||||
setF(true);
|
||||
props.onFocus?.(e);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
setF(false);
|
||||
props.onBlur?.(e);
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 44,
|
||||
padding: '0 14px',
|
||||
fontFamily: FF,
|
||||
fontSize: 18,
|
||||
color: '#fff',
|
||||
border: `1px solid ${f ? 'rgba(239,191,4,0.8)' : 'rgba(255,255,255,0.12)'}`,
|
||||
background: f ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.05)',
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
|
||||
{/* ── 1. HERO ─────────────────────────────────────────────────────── */}
|
||||
<section
|
||||
style={{
|
||||
position: 'relative',
|
||||
minHeight: '65vh',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
overflow: 'hidden',
|
||||
background: NAVY,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/gala-dinner.jpg"
|
||||
alt="Presse BMP"
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background: isMobile
|
||||
? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)'
|
||||
: 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 2,
|
||||
background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)',
|
||||
}}
|
||||
/>
|
||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 80px', maxWidth: 860 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 24 }}>
|
||||
<div style={{ width: 36, height: 2, background: GOLD }} />
|
||||
<span
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.32em',
|
||||
textTransform: 'uppercase',
|
||||
color: GOLD,
|
||||
}}
|
||||
>
|
||||
Presse & Newsroom
|
||||
</span>
|
||||
</div>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: isMobile ? 'clamp(2rem, 8vw, 2.8rem)' : 'clamp(2.8rem, 6vw, 5rem)',
|
||||
fontWeight: 900,
|
||||
color: '#fff',
|
||||
lineHeight: 0.95,
|
||||
letterSpacing: '-0.03em',
|
||||
textTransform: 'uppercase',
|
||||
margin: '0 0 24px',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
EVENTS &<br />BERICHTERSTATTUNG.
|
||||
</h1>
|
||||
<div style={{ width: 48, height: 2, background: GOLD, marginBottom: 24 }} />
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(255,255,255,0.6)',
|
||||
fontWeight: 300,
|
||||
maxWidth: 520,
|
||||
lineHeight: 1.7,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Alle Termine, Pressemitteilungen und Medienmaterial rund um den Bayerischen Mittelstandspreis.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 2. EVENTS ───────────────────────────────────────────────────── */}
|
||||
<section id="events" style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
{/* Section header */}
|
||||
<div
|
||||
style={{
|
||||
padding: isMobile ? '48px 24px 40px' : '80px 80px 56px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
|
||||
gap: isMobile ? 24 : 48,
|
||||
alignItems: 'flex-end',
|
||||
borderBottom: '1px solid rgba(3,9,58,0.1)',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.28em',
|
||||
color: '#4A8FC9',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
Termine 2026
|
||||
</div>
|
||||
<h2
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontWeight: 900,
|
||||
color: '#101828',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.025em',
|
||||
fontSize: 'clamp(2rem, 3.5vw, 3rem)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
EVENTS RUND UM DEN PREIS.
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(16,24,40,0.45)',
|
||||
lineHeight: 1.8,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event rows */}
|
||||
{events.map((event, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
onMouseEnter={() => setHoveredEvent(idx)}
|
||||
onMouseLeave={() => setHoveredEvent(null)}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr' : '320px 1fr',
|
||||
borderBottom: '1px solid rgba(3,9,58,0.08)',
|
||||
background: hoveredEvent === idx ? '#EAE7E6' : 'transparent',
|
||||
transition: 'background 0.15s',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{/* Left – image cell */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden', height: isMobile ? 200 : 240 }}>
|
||||
<img src={event.img} alt={event.title} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||||
<div style={{ position: 'absolute', top: 20, left: 20, padding: '5px 12px', fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase' as const, letterSpacing: '0.18em', background: event.status === 'Anmeldung offen' ? GOLD : 'rgba(3,9,58,0.85)', color: event.status === 'Anmeldung offen' ? NAVY : 'rgba(255,255,255,0.6)' }}>
|
||||
{event.status}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right – copy cell */}
|
||||
<div style={{ padding: isMobile ? '24px 24px 32px' : '40px 56px 40px 48px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
|
||||
<div style={{ fontFamily: FF, fontSize: 9, color: '#4A8FC9', textTransform: 'uppercase' as const, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>{event.cat}</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase' as const, letterSpacing: '-0.02em', margin: '0 0 14px' }}>{event.title}</h3>
|
||||
<div style={{ display: 'flex', gap: isMobile ? 16 : 28, alignItems: 'center', flexWrap: 'wrap', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)' }}>
|
||||
<Calendar size={14} color={GOLD} />{event.date}
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)' }}>
|
||||
<MapPin size={14} color={GOLD} />{event.location}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: 24, height: 1, background: 'rgba(239,191,4,0.4)', marginBottom: 14 }} />
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.7, flex: 1 }}>{event.desc}</p>
|
||||
<Link to={event.slug} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, marginTop: 16, fontFamily: FF, fontSize: 16, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: '#101828', textDecoration: 'none' }}>
|
||||
Details <ArrowRight size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
{/* ── 3. NEWSROOM ─────────────────────────────────────────────────── */}
|
||||
<section style={{ background: NAVY, overflow: 'hidden' }}>
|
||||
{/* Section header */}
|
||||
<div
|
||||
style={{
|
||||
padding: isMobile ? '48px 24px 40px' : '80px 80px 56px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
|
||||
gap: isMobile ? 24 : 48,
|
||||
alignItems: 'flex-end',
|
||||
borderBottom: '1px solid rgba(255,255,255,0.07)',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.28em',
|
||||
color: GOLD,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
Newsroom
|
||||
</div>
|
||||
<h2
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontWeight: 900,
|
||||
color: '#fff',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.025em',
|
||||
fontSize: 'clamp(2rem, 3.5vw, 3rem)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
NACHRICHTEN & EINBLICKE.
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(255,255,255,0.4)',
|
||||
lineHeight: 1.8,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3-column news grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)' }}>
|
||||
{news.map((item, idx) => (
|
||||
<NewsCard key={idx} item={item} idx={idx} isMobile={isMobile} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 4. PRESSE-MATERIAL & AKKREDITIERUNG ─────────────────────────── */}
|
||||
<section id="downloads" style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', minHeight: isMobile ? 'auto' : 600 }}>
|
||||
|
||||
{/* Left col */}
|
||||
<div
|
||||
style={{
|
||||
padding: isMobile ? '48px 24px' : '88px 80px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
borderRight: isMobile ? 'none' : '1px solid rgba(3,9,58,0.1)',
|
||||
borderBottom: isMobile ? '1px solid rgba(3,9,58,0.1)' : 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.28em',
|
||||
color: '#4A8FC9',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
Pressekontakt & Material
|
||||
</div>
|
||||
|
||||
<h2
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontWeight: 900,
|
||||
color: '#101828',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.025em',
|
||||
fontSize: 'clamp(1.8rem, 3vw, 2.6rem)',
|
||||
margin: '0 0 20px',
|
||||
}}
|
||||
>
|
||||
PRESSE-MATERIAL & KONTAKT.
|
||||
</h2>
|
||||
|
||||
{/* Gold divider */}
|
||||
<div style={{ width: 40, height: 2, background: GOLD, margin: '0 0 28px' }} />
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(16,24,40,0.5)',
|
||||
lineHeight: 1.8,
|
||||
maxWidth: 380,
|
||||
marginBottom: 48,
|
||||
}}
|
||||
>
|
||||
Laden Sie unser offizielles Material herunter oder kontaktieren Sie direkt unser Presseteam für individuelle Anfragen.
|
||||
</p>
|
||||
|
||||
{/* Download button */}
|
||||
<a
|
||||
href="/Print_BMP.zip"
|
||||
download="Print_BMP.zip"
|
||||
onMouseEnter={() => setDlHovered(true)}
|
||||
onMouseLeave={() => setDlHovered(false)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 20,
|
||||
padding: '24px 28px',
|
||||
border: `1px solid ${dlHovered ? GOLD : 'rgba(3,9,58,0.12)'}`,
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
marginBottom: 20,
|
||||
transition: 'border-color 0.15s',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
background: NAVY,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Download size={20} color={GOLD} />
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
color: '#101828',
|
||||
marginBottom: 3,
|
||||
}}
|
||||
>
|
||||
Download Presse-Kit
|
||||
</div>
|
||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.4)' }}>
|
||||
Print_BMP.zip – 1,5 MB
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Press contact block */}
|
||||
<div style={{ borderLeft: `3px solid ${GOLD}`, paddingLeft: 24, marginTop: 32 }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
color: '#4A8FC9',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.2em',
|
||||
fontWeight: 700,
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
Pressekontakt
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
color: '#101828',
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
Tanja Meier
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(16,24,40,0.55)',
|
||||
lineHeight: 1.8,
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
presse@bmp-bayern.de
|
||||
<br />
|
||||
+49 89 123 456 99
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right col */}
|
||||
<div
|
||||
style={{
|
||||
background: NAVY,
|
||||
padding: isMobile ? '48px 24px' : '72px 64px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{submitted ? (
|
||||
/* Success state */
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
background: GOLD,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 28,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<FileText size={24} color={NAVY} />
|
||||
</div>
|
||||
<h3
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 'clamp(1.4rem, 2.2vw, 1.8rem)',
|
||||
fontWeight: 900,
|
||||
color: '#fff',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.02em',
|
||||
margin: '0 0 16px',
|
||||
}}
|
||||
>
|
||||
Anfrage eingegangen
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(255,255,255,0.45)',
|
||||
lineHeight: 1.8,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Vielen Dank für Ihre Akkreditierungsanfrage. Unser Presseteam meldet sich zeitnah bei Ihnen.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
color: GOLD,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.32em',
|
||||
fontWeight: 700,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
Akkreditierung
|
||||
</div>
|
||||
|
||||
<h3
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 'clamp(1.4rem, 2.2vw, 1.8rem)',
|
||||
fontWeight: 900,
|
||||
color: '#fff',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '-0.02em',
|
||||
margin: '0 0 16px',
|
||||
}}
|
||||
>
|
||||
AKKREDITIERUNG ANFRAGEN.
|
||||
</h3>
|
||||
|
||||
{/* Gold divider */}
|
||||
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontFamily: FB,
|
||||
fontSize: 18,
|
||||
color: 'rgba(255,255,255,0.45)',
|
||||
lineHeight: 1.8,
|
||||
marginBottom: 36,
|
||||
}}
|
||||
>
|
||||
Melden Sie sich für unsere Presse-Verteiler an oder fordern Sie eine Akkreditierung für die Gala-Verleihung an.
|
||||
</p>
|
||||
|
||||
{/* Form fields */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.2em',
|
||||
color: 'rgba(255,255,255,0.35)',
|
||||
}}
|
||||
>
|
||||
Medium / Redaktion *
|
||||
</label>
|
||||
<DarkInput
|
||||
type="text"
|
||||
value={medium}
|
||||
onChange={(e) => setMedium(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.2em',
|
||||
color: 'rgba(255,255,255,0.35)',
|
||||
}}
|
||||
>
|
||||
Ihr Name *
|
||||
</label>
|
||||
<DarkInput
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label
|
||||
style={{
|
||||
fontFamily: FF,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.2em',
|
||||
color: 'rgba(255,255,255,0.35)',
|
||||
}}
|
||||
>
|
||||
E-Mail-Adresse *
|
||||
</label>
|
||||
<DarkInput
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit button */}
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
onMouseEnter={() => setSubmitHovered(true)}
|
||||
onMouseLeave={() => setSubmitHovered(false)}
|
||||
style={{
|
||||
background: submitHovered ? '#FFD130' : GOLD,
|
||||
boxShadow: submitHovered ? '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)' : 'none',
|
||||
color: '#101828',
|
||||
border: 'none',
|
||||
fontFamily: FF,
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em',
|
||||
cursor: 'pointer',
|
||||
padding: '14px 28px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
width: '100%',
|
||||
transition: 'background 0.15s, box-shadow 0.2s',
|
||||
}}
|
||||
>
|
||||
Akkreditierung anfragen <ArrowRight size={13} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Press;
|
||||
56
src/spa/router.tsx
Normal file
56
src/spa/router.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import NextLink from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import React, { forwardRef, useEffect, useMemo } from 'react'
|
||||
|
||||
type To = string | { pathname?: string; search?: string; hash?: string }
|
||||
|
||||
type LinkProps = Omit<React.ComponentProps<typeof NextLink>, 'href'> & {
|
||||
to: To
|
||||
}
|
||||
|
||||
const toHref = (to: To, pathname = '') => {
|
||||
if (typeof to === 'string') {
|
||||
if (to.startsWith('#')) return `${pathname || '/'}${to}`
|
||||
return to
|
||||
}
|
||||
return `${to.pathname || pathname || '/'}${to.search || ''}${to.hash || ''}`
|
||||
}
|
||||
|
||||
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link({ to, ...props }, ref) {
|
||||
const pathname = usePathname() || '/'
|
||||
return <NextLink ref={ref} href={toHref(to, pathname)} {...props} />
|
||||
})
|
||||
|
||||
export function useLocation() {
|
||||
const pathname = usePathname() || '/'
|
||||
const hash = typeof window === 'undefined' ? '' : window.location.hash
|
||||
const search = typeof window === 'undefined' ? '' : window.location.search
|
||||
|
||||
return useMemo(() => ({ pathname, hash, search }), [pathname, hash, search])
|
||||
}
|
||||
|
||||
export function useParams<T extends Record<string, string | undefined> = Record<string, string>>() {
|
||||
const pathname = usePathname() || '/'
|
||||
const parts = pathname.split('/').filter(Boolean)
|
||||
const params: Record<string, string | undefined> = {}
|
||||
|
||||
if (parts[0] === 'preistraeger' && parts[1]) params.slug = decodeURIComponent(parts[1])
|
||||
if (parts[0] === 'presse' && (parts[1] === 'events' || parts[1] === 'blog') && parts[2]) {
|
||||
params.slug = decodeURIComponent(parts[2])
|
||||
}
|
||||
|
||||
return params as T
|
||||
}
|
||||
|
||||
export function Navigate({ to, replace }: { to: string; replace?: boolean }) {
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (replace) router.replace(to)
|
||||
else router.push(to)
|
||||
}, [replace, router, to])
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user