Files
bmp-website-2026/src/spa/pages/Home.tsx
syntaxbullet 2842caa936 feat: update Preistraeger page to include a simple winner grid for 2023 and remove unused components
fix: change featured status for participation content and remove deprecated award card

feat: add migration for new participation phase form fields and backfill existing data

feat: create AwardsGridSection component for displaying awards with improved layout

feat: implement NewsletterInterestForm component for newsletter sign-up with validation
2026-07-01 18:02:12 +02:00

969 lines
59 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef, useState } from 'react';
import { Button } from '@/spa/components/ui/button';
import { Play, ChevronRight, 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 { NewsletterInterestForm, type NewsletterBenefit, type NewsletterInterestCms, type NewsletterInterestCopy } from '@/spa/components/forms/NewsletterInterestForm';
import TestimonialsSection from '@/spa/components/TestimonialsSection';
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
import { useIsMobile } from '@/spa/hooks/useIsMobile';
import Image from '@/spa/components/ui/UnoptimizedImage'
import type { SpaApplicationPhaseData, SpaApplicationPhaseItem } from '@/spa/applicationPhase'
import { useApplicationPhase, useCmsCollection, useCmsRoute } from '@/spa/cmsRoute'
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField'
import { homeContent } from '@/spa/homeContent'
import { netzwerkContent } from '@/spa/netzwerkContent'
import { PartnerLogoMark, type PartnerLogoData } from '@/spa/components/ui/partner-logo'
const FF = '"IBM Plex Sans", sans-serif';
type CmsRecord = Record<string, unknown>;
type CmsLink = { label?: string | null; to?: string | null; url?: string | null };
type HomeHeroCms = {
backgroundImage?: unknown;
badge?: string | null;
description?: string | null;
headingAccent?: string | null;
headingLine1?: string | null;
imageAlt?: string | null;
partners?: unknown;
partnersLabel?: string | null;
primaryCta?: CmsLink | null;
secondaryCta?: CmsLink | null;
videoLabel?: string | null;
};
type HomeSectionCms = CmsRecord & {
awardCaption?: string | null;
awardImage?: unknown;
awardImageAlt?: string | null;
benefits?: unknown;
body?: string | null;
cardFallbackTitle?: string | null;
criteria?: unknown;
cta?: CmsLink | null;
eyebrow?: string | null;
fallbackImage?: unknown;
featured?: unknown;
footnote?: string | null;
footnoteStrong?: string | null;
formEyebrow?: string | null;
formTitle?: string | null;
heading?: string | null;
hoverLabel?: string | null;
image?: unknown;
imageAlt?: string | null;
imageLabel?: string | null;
items?: unknown;
newsletterPhaseCompleted?: NewsletterInterestCms | null;
newsletterPhaseEvaluation?: NewsletterInterestCms | null;
note?: string | null;
quote?: string | null;
stats?: unknown;
};
type HomeVideoModalCms = {
closeLabel?: string | null;
description?: string | null;
title?: string | null;
video?: unknown;
};
type HomeTestimonialsCms = {
desktopHeading?: string | null;
eyebrow?: string | null;
heading?: string | null;
items?: Array<Record<string, unknown>> | null;
nextLabel?: string | null;
previousLabel?: string | null;
slideLabelPrefix?: string | null;
yearPrefix?: string | null;
};
type HomeCms = {
application?: HomeSectionCms;
benefits?: HomeSectionCms;
form?: Partial<typeof homeContent.form>;
hero?: HomeHeroCms;
intro?: HomeSectionCms;
quickCheck?: HomeSectionCms;
testimonials?: HomeTestimonialsCms;
videoModal?: HomeVideoModalCms;
winners?: HomeSectionCms;
};
type NetzwerkPartnersCms = {
defaultLogoColor?: string | null;
};
type RenderedStatusPhase = Omit<SpaApplicationPhaseItem, 'accent' | 'bg' | 'cta' | 'membershipCta'> & {
accent: string;
bg: string;
cta?: { label: string; to: string } | null;
membershipCta?: { label: string; to: string } | null;
};
type WinnerCardData = CmsRecord & {
id?: string | number;
slug?: string;
title?: string;
name?: string;
img?: string;
image?: unknown;
spaPath?: string;
year?: string | number;
awardType?: string;
type?: string;
category?: string;
};
const fallbackText = (value: unknown, fallback: string) =>
typeof value === 'string' && value.length > 0 ? value : fallback;
const fallbackArray = <T,>(value: unknown, fallback: readonly T[]) =>
Array.isArray(value) && value.length > 0 ? (value as T[]) : fallback;
const HIDE_MEMBERSHIP_HOME_HERO_CTA = true;
const APPLICATION_PHASE_OPEN = '0';
const newsletterInterestCopy: Record<string, NewsletterInterestCopy> = {
'1': homeContent.application.newsletterPhaseEvaluation,
'2': homeContent.application.newsletterPhaseCompleted,
};
const mergeNewsletterInterestCopy = (
value: NewsletterInterestCms | null | undefined,
fallback: NewsletterInterestCopy,
): NewsletterInterestCopy => ({
eyebrow: fallbackText(value?.eyebrow, fallback.eyebrow),
heading: fallbackText(value?.heading, fallback.heading),
body: fallbackText(value?.body, fallback.body),
benefits: fallbackArray<NewsletterBenefit>(value?.benefits, fallback.benefits),
formEyebrow: fallbackText(value?.formEyebrow, fallback.formEyebrow),
formTitle: fallbackText(value?.formTitle, fallback.formTitle),
formBody: fallbackText(value?.formBody, fallback.formBody),
emailLabel: fallbackText(value?.emailLabel, fallback.emailLabel),
emailPlaceholder: fallbackText(value?.emailPlaceholder, fallback.emailPlaceholder),
submitLabel: fallbackText(value?.submitLabel, fallback.submitLabel),
privacy: fallbackText(value?.privacy, fallback.privacy),
validationRequired: fallbackText(value?.validationRequired, fallback.validationRequired),
validationInvalid: fallbackText(value?.validationInvalid, fallback.validationInvalid),
successHeading: fallbackText(value?.successHeading, fallback.successHeading),
successBody: fallbackText(value?.successBody, fallback.successBody),
footnote: fallbackText(value?.footnote, fallback.footnote),
footnoteStrong: fallbackText(value?.footnoteStrong, fallback.footnoteStrong),
});
const getNewsletterInterestCopy = (phase: string | undefined, application: HomeSectionCms) => {
const fallback = newsletterInterestCopy[phase || ''] || newsletterInterestCopy['2'];
const cmsCopy = phase === '1' ? application.newsletterPhaseEvaluation : application.newsletterPhaseCompleted;
return mergeNewsletterInterestCopy(cmsCopy, fallback);
};
const isMembershipCta = (cta?: { label?: string; url?: string; to?: string } | null) =>
cta?.url === '/mitglied-werden' ||
cta?.to === '/mitglied-werden' ||
cta?.label?.trim().toLowerCase() === 'mitglied werden';
function Lines({ text }: { text: string }) {
return <>{text.split('\n').map((line, i) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>;
}
const isNetzwerkPage = (page: CmsRecord) =>
page.spaPath === '/netzwerk' || page.slug === 'netzwerk' || page.slug === 'network'
function partnerLogos(partners: PartnerLogoData[], defaultLogoColor: string) {
return partners.map((partner, index) => ({
id: String(partner.stableId || partner.id || partner.name || index),
label: (
<span
aria-label={fallbackText(partner.name, 'Partner')}
style={{
width: 224,
height: 54,
padding: '9px 18px',
background: 'rgba(255,255,255,0.92)',
border: '1px solid rgba(255,255,255,0.18)',
borderRadius: 6,
boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<PartnerLogoMark partner={partner} defaultLogoColor={defaultLogoColor} />
</span>
),
}));
}
function sortedPartners<T extends PartnerLogoData>(partners: T[]) {
return partners
.map((partner, index) => ({ partner, index }))
.sort((a, b) => Number(a.partner.sortOrder ?? a.index) - Number(b.partner.sortOrder ?? b.index))
.map(({ partner }) => partner);
}
function HomeWinnerCard({
winner,
fallbackTitle,
fallbackImage,
hoverLabel,
}: {
winner: WinnerCardData;
fallbackTitle: string;
fallbackImage: string;
hoverLabel: string;
}) {
const [hovered, setHovered] = useState(false);
const isMobile = useIsMobile();
const staticWinner = WINNERS.find(
(item) =>
String(item.id) === String(winner?.id) ||
item.slug === winner?.slug ||
item.name === winner?.title ||
item.name === winner?.name,
);
const title = fallbackText(winner?.title, fallbackText(winner?.name, staticWinner?.name || fallbackTitle));
const imageSrc = mediaUrl(winner?.image, winner?.img || staticWinner?.img || fallbackImage);
const href = winner?.spaPath || (winner?.slug ? `/preistraeger/${winner.slug}` : staticWinner ? `/preistraeger/${staticWinner.slug}` : '/preistraeger');
return (
<Link
to={href}
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 220 : 280, overflow: 'hidden', scrollSnapAlign: isMobile ? 'start' : undefined }}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<Image unoptimized src={imageSrc} alt={title} 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 || staticWinner?.year} · {winner?.awardType || winner?.type || staticWinner?.type}</span>
</div>
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight: 700, color: '#fff', margin: '0 0 3px', lineHeight: 1.3 }}>{title}</h3>
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(255,255,255,0.75)', margin: 0 }}>{winner?.category || staticWinner?.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' }}>
{hoverLabel}
</div>
</div>
</Link>
);
}
const Home: React.FC = () => {
const [showVideo, setShowVideo] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const isMobile = useIsMobile();
const home = (useCmsRoute()?.doc?.home || {}) as HomeCms;
const netzwerkPage = useCmsCollection('pages').find((page) => isNetzwerkPage(page as CmsRecord)) as
| ({ netzwerk?: { partners?: NetzwerkPartnersCms } } & CmsRecord)
| undefined;
const partnerSection = netzwerkPage?.netzwerk?.partners;
const defaultLogoColor = fallbackText(partnerSection?.defaultLogoColor, netzwerkContent.partners.defaultLogoColor);
const cmsPartners = useCmsCollection('partners') as PartnerLogoData[];
const partnerSource = cmsPartners.length ? cmsPartners : (netzwerkContent.partners.items as PartnerLogoData[]);
const networkPartners = sortedPartners(partnerSource)
.filter((partner) => partner.active !== false && partner.showOnHomepage !== false)
.map((partner) => ({
...partner,
id: partner.stableId || partner.id,
logoColor: fallbackText(partner.logoColor, defaultLogoColor),
}));
const hero = home.hero || {};
const quickCheck = home.quickCheck || {};
const intro = home.intro || {};
const winnersSection = home.winners || {};
const benefits = home.benefits || {};
const application = home.application || {};
const form = home.form || {};
const videoModal = home.videoModal || {};
const applicationPhase = useApplicationPhase();
const activeApplicationPhase = applicationPhase?.activePhase || APPLICATION_PHASE_OPEN;
const showApplicationForm = activeApplicationPhase === APPLICATION_PHASE_OPEN;
const newsletterCopy = getNewsletterInterestCopy(activeApplicationPhase, application);
const cmsPreistraeger = useCmsCollection('preistraeger');
const selectedWinners = fallbackArray(winnersSection.featured, []);
const heroSecondaryCta = {
label: fallbackText(hero.secondaryCta?.label, homeContent.hero.secondaryCta.label),
url: fallbackText(hero.secondaryCta?.url, homeContent.hero.secondaryCta.url),
};
const showHeroSecondaryCta = !HIDE_MEMBERSHIP_HOME_HERO_CTA || !isMembershipCta(heroSecondaryCta);
const selectedWinnerDocs = selectedWinners
.map((selected) => {
const selectedRecord = selected as { id?: string | number }
const selectedID = typeof selected === 'object' && selected !== null ? selectedRecord.id : selected
const listDoc = cmsPreistraeger.find((doc) => String((doc as { id?: string | number }).id) === String(selectedID))
return listDoc || (typeof selected === 'object' ? selected : undefined)
})
.filter(Boolean)
const homeWinners = selectedWinnerDocs.length
? selectedWinnerDocs
: (cmsPreistraeger.length ? cmsPreistraeger : WINNERS).filter((w) => Number((w as { year?: string | number }).year) === 2025).slice(0, 8);
const quickCriteria = fallbackArray(quickCheck.criteria, homeContent.quickCheck.criteria);
const introStats = fallbackArray(intro.stats, homeContent.intro.stats);
const benefitItems = fallbackArray(benefits.items, homeContent.benefits.items);
const applicationBenefits = fallbackArray(application.benefits, homeContent.application.benefits);
const finalSectionBenefits = showApplicationForm ? applicationBenefits : newsletterCopy.benefits;
const winnerFallbackImage = mediaUrl(winnersSection.fallbackImage, `/images/${homeContent.winners.fallbackImageFilename}`);
const videoTitle = fallbackText(videoModal.title, homeContent.videoModal.title);
const videoSrc = mediaUrl(videoModal.video, '');
useEffect(() => {
if (!showVideo || !videoSrc) return;
videoRef.current?.play().catch(() => {
// Browser autoplay rules can still require the native control click.
});
}, [showVideo, videoSrc]);
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">
<Image unoptimized
src={mediaUrl(hero.backgroundImage, `/images/${homeContent.hero.backgroundImageFilename}`)}
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, homeContent.hero.imageAlt))}
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">{fallbackText(hero.badge, homeContent.hero.badge)}</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)' }}>
{fallbackText(hero.headingLine1, homeContent.hero.headingLine1)} <br />
<span className="text-accent">{fallbackText(hero.headingAccent, homeContent.hero.headingAccent)}</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 }}>
{fallbackText(hero.description, homeContent.hero.description)}
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<Link
to={fallbackText(hero.primaryCta?.url, homeContent.hero.primaryCta.url)}
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'; }}
>
{fallbackText(hero.primaryCta?.label, homeContent.hero.primaryCta.label)} <ChevronRight size={16} />
</Link>
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 16, alignItems: isMobile ? 'stretch' : 'center' }}>
{showHeroSecondaryCta && (
<Link
to={heroSecondaryCta.url}
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)'; }}
>
{heroSecondaryCta.label}
</Link>
)}
<button
type="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">{fallbackText(hero.videoLabel, homeContent.hero.videoLabel)}</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 }}>{fallbackText(hero.partnersLabel, homeContent.hero.partnersLabel)}</span>
{/* Right: ticker window */}
<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: 'min(820px, calc(100vw - 260px))', overflow: 'hidden', flexShrink: 0 }}>
<PartnerTicker logos={partnerLogos(networkPartners, defaultLogoColor)} />
</div>
</div>
</div>
</div>
</section>
{/* Status Phase Slider */}
<StatusSlider data={applicationPhase} />
{/* 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: '#fff', 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 }}>{fallbackText(quickCheck.eyebrow, homeContent.quickCheck.eyebrow)}</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' }}>
<Lines text={fallbackText(quickCheck.heading, homeContent.quickCheck.heading)} />
</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 }}>
{fallbackText(quickCheck.body, homeContent.quickCheck.body)}
</p>
{/* Criteria list */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, marginBottom: 36, borderTop: '1px solid #D0D5DD' }}>
{quickCriteria.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 }}>
{fallbackText(quickCheck.note, homeContent.quickCheck.note)}
</p>
<Link
to={fallbackText(quickCheck.cta?.url, homeContent.quickCheck.cta.url)}
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'; }}
>
{fallbackText(quickCheck.cta?.label, homeContent.quickCheck.cta.label)} <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 }}>
<Image unoptimized
src={mediaUrl(quickCheck.image, `/images/${homeContent.quickCheck.imageFilename}`)}
alt={mediaAlt(quickCheck.image, fallbackText(quickCheck.imageAlt, homeContent.quickCheck.imageAlt))}
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%, #fff 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 }}>{fallbackText(quickCheck.eyebrow, homeContent.quickCheck.eyebrow)}</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)' }}>
<Lines text={fallbackText(quickCheck.heading, homeContent.quickCheck.heading)} />
</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' }}>
<Image unoptimized
src={mediaUrl(intro.image, `/images/${homeContent.intro.imageFilename}`)}
alt={mediaAlt(intro.image, fallbackText(intro.imageAlt, homeContent.intro.imageAlt))}
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 }}>{fallbackText(intro.eyebrow, homeContent.intro.eyebrow)}</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)' }}>
<Lines text={fallbackText(intro.heading, homeContent.intro.heading)} />
</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 }}>
{introStats.map(({ value: v, label: 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 }}>{fallbackText(intro.eyebrow, homeContent.intro.eyebrow)}</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' }}>
<Lines text={fallbackText(intro.heading, homeContent.intro.heading)} />
</h2>
</>
)}
{/* Stats row mobile only, below the image */}
{isMobile && (
<div style={{ display: 'flex', gap: 16, marginBottom: 28, paddingBottom: 24, borderBottom: '1px solid #D0D5DD' }}>
{introStats.map(({ value: v, label: 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' }}>
{fallbackText(intro.quote, homeContent.intro.quote)}
</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' }}>
{fallbackText(intro.body, homeContent.intro.body)}
</p>
<Link
to={fallbackText(intro.cta?.url, homeContent.intro.cta.url)}
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'; }}
>
{fallbackText(intro.cta?.label, homeContent.intro.cta.label)} <ChevronRight size={14} />
</Link>
</div>
</div>
</section>
{/* 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 }}>{fallbackText(winnersSection.eyebrow, homeContent.winners.eyebrow)}</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 }}>
<Lines text={fallbackText(winnersSection.heading, homeContent.winners.heading)} />
</h2>
</div>
<Link
to={fallbackText(winnersSection.cta?.url, homeContent.winners.cta.url)}
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' }}
>
{fallbackText(winnersSection.cta?.label, homeContent.winners.cta.label)} <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',
}}>
{homeWinners.map((w) => (
<HomeWinnerCard
key={(w as WinnerCardData).id || (w as WinnerCardData).slug}
winner={w as WinnerCardData}
fallbackTitle={fallbackText(winnersSection.cardFallbackTitle, homeContent.winners.cardFallbackTitle)}
fallbackImage={winnerFallbackImage}
hoverLabel={fallbackText(winnersSection.hoverLabel, homeContent.winners.hoverLabel)}
/>
))}
</div>
</section>
{/* Testimonials Section */}
<TestimonialsSection data={home.testimonials} />
{/* 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 white */}
<div style={{ background: '#fff', 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 }}>{fallbackText(benefits.eyebrow, homeContent.benefits.eyebrow)}</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' }}>
<Lines text={fallbackText(benefits.heading, homeContent.benefits.heading)} />
</h2>
</>
)}
{/* Numbered rows no cards, no shadows */}
<div style={{ borderTop: '1px solid #D0D5DD' }}>
{benefitItems.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 = 'rgba(17,29,85,0.04)')}
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 }}>
<Image unoptimized
src={mediaUrl(benefits.image, `/images/${homeContent.benefits.imageFilename}`)}
alt={mediaAlt(benefits.image, fallbackText(benefits.imageAlt, homeContent.benefits.imageAlt))}
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, #fff 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 }}>{fallbackText(benefits.eyebrow, homeContent.benefits.eyebrow)}</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)' }}>
<Lines text={fallbackText(benefits.heading, homeContent.benefits.heading)} />
</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 }}>{fallbackText(benefits.imageLabel, homeContent.benefits.imageLabel)}</div>
<div style={{ width: 32, height: 2, background: '#EFBF04' }} />
</div>
)}
</div>
</div>
</section>
{/* CTA Section with phase-aware embedded form */}
<section style={{ background: '#24366A', position: 'relative', overflow: 'hidden', height: isMobile ? 'auto' : showApplicationForm ? 'calc(100vh - 60px)' : 'auto', minHeight: isMobile ? undefined : showApplicationForm ? undefined : 560, 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(255,255,255,0.08) 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 }}>{showApplicationForm ? fallbackText(application.eyebrow, homeContent.application.eyebrow) : newsletterCopy.eyebrow}</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 }}>
<Lines text={showApplicationForm ? fallbackText(application.heading, homeContent.application.heading) : newsletterCopy.heading} />
</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 }}>
{showApplicationForm ? fallbackText(application.body, homeContent.application.body) : newsletterCopy.body}
</p>
{/* Benefit rows */}
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}>
{finalSectionBenefits.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 }}>
<Image unoptimized
src={mediaUrl(application.awardImage, `/images/${homeContent.application.awardImageFilename}`)}
alt={mediaAlt(application.awardImage, fallbackText(application.awardImageAlt, homeContent.application.awardImageAlt))}
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' }}>{fallbackText(application.awardCaption, homeContent.application.awardCaption)}</span>
</div>
</div>
)}
{/* Form area */}
<div style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile && showApplicationForm ? '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 }}>{showApplicationForm ? fallbackText(application.formEyebrow, homeContent.application.formEyebrow) : newsletterCopy.formEyebrow}</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 }}>{showApplicationForm ? fallbackText(application.formTitle, homeContent.application.formTitle) : newsletterCopy.formTitle}</h3>
{showApplicationForm ? <BewerbungsForm theme="gold" content={form} /> : <NewsletterInterestForm copy={newsletterCopy} />}
</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 }}>{showApplicationForm ? fallbackText(application.footnote, homeContent.application.footnote) : newsletterCopy.footnote}</span>
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: 'rgba(239,191,4,0.8)' }}>{showApplicationForm ? fallbackText(application.footnoteStrong, homeContent.application.footnoteStrong) : newsletterCopy.footnoteStrong}</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
type="button"
onClick={() => setShowVideo(false)}
className="absolute top-8 right-8 text-white/60 hover:text-white transition-colors"
aria-label={fallbackText(videoModal.closeLabel, homeContent.videoModal.closeLabel)}
>
<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">
{videoSrc ? (
<video
ref={videoRef}
src={videoSrc}
className="h-full w-full bg-black object-contain"
controls
autoPlay
playsInline
aria-label={videoTitle}
/>
) : (
<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">{videoTitle}</p>
<p className="text-white/60 mt-4 text-xs italic font-body">{fallbackText(videoModal.description, homeContent.videoModal.description)}</p>
<Button type="button" onClick={() => setShowVideo(false)} className="mt-8" variant="outline">{fallbackText(videoModal.closeLabel, homeContent.videoModal.closeLabel)}</Button>
</div>
)}
</div>
</div>
)}
</div>
);
};
// ─── StatusSlider ─────────────────────────────────────────────────────────────
function StatusSlider({ data }: { data?: SpaApplicationPhaseData }) {
const isMobile = useIsMobile();
if (!data?.phases?.length) return null;
const phases = data.phases.map((phase): RenderedStatusPhase => {
const cta = phase.cta?.label
? { label: phase.cta.label, to: phase.cta.to || phase.cta.url || '/' }
: null
const membershipCta = phase.membershipCta?.label
? { label: phase.membershipCta.label, to: phase.membershipCta.to || phase.membershipCta.url || '/mitglied-werden' }
: undefined
return {
...phase,
accent: phase.accent || '#EFBF04',
bg: phase.bg || '#020A1E',
cta,
membershipCta,
}
});
const configuredActive = Number(data.activePhase);
const active = Math.max(0, Math.min(phases.length - 1, Number.isFinite(configuredActive) ? configuredActive : 0));
const phase = phases[active];
return (
<section
style={{
position: 'relative',
overflow: 'hidden',
height: isMobile ? 'auto' : 480,
minHeight: isMobile ? 360 : 'auto',
}}
>
<StatusSlideCard
phase={phase}
isMobile={isMobile}
noActionLabel={data.noActionLabel}
successApplicationsLabel={data.successApplicationsLabel}
successWinnersLabel={data.successWinnersLabel}
/>
</section>
);
}
// ─── StatusSlideCard ──────────────────────────────────────────────────────────
function StatusSlideCard({
phase,
isMobile,
noActionLabel,
successApplicationsLabel,
successWinnersLabel,
}: {
phase: RenderedStatusPhase;
isMobile: boolean;
noActionLabel: string;
successApplicationsLabel: string;
successWinnersLabel: string;
}) {
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' }}>{noActionLabel}</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 }}>{fallbackText(phase.successApplications, '')}</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 }}>{successApplicationsLabel}</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 }}>{fallbackText(phase.successWinners, '')}</div>
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4 }}>{successWinnersLabel}</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={phase.membershipCta?.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`;
}}
>
{phase.membershipCta?.label} <ChevronRight size={13} />
</Link>
</div>
)}
</div>}
</div>
);
}
export default Home;