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; 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> | null; nextLabel?: string | null; previousLabel?: string | null; slideLabelPrefix?: string | null; yearPrefix?: string | null; }; type HomeCms = { application?: HomeSectionCms; benefits?: HomeSectionCms; form?: Partial; hero?: HomeHeroCms; intro?: HomeSectionCms; quickCheck?: HomeSectionCms; testimonials?: HomeTestimonialsCms; videoModal?: HomeVideoModalCms; winners?: HomeSectionCms; }; type NetzwerkPartnersCms = { defaultLogoColor?: string | null; }; type RenderedStatusPhase = Omit & { 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 = (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 = { '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(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) => {i > 0 &&
}{line}
)}; } 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: ( ), })); } function sortedPartners(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 ( setHovered(true)} onMouseLeave={() => setHovered(false)} > {title}
{winner?.year || staticWinner?.year} · {winner?.awardType || winner?.type || staticWinner?.type}

{title}

{winner?.category || staticWinner?.category}

{hoverLabel}
); } const Home: React.FC = () => { const [showVideo, setShowVideo] = useState(false); const videoRef = useRef(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 (
{/* Hero Section */}
{mediaAlt(hero.backgroundImage,
{fallbackText(hero.badge, homeContent.hero.badge)}

{fallbackText(hero.headingLine1, homeContent.hero.headingLine1)}
{fallbackText(hero.headingAccent, homeContent.hero.headingAccent)}

{fallbackText(hero.description, homeContent.hero.description)}

{ (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)}
{showHeroSecondaryCta && ( { (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} )}
{/* Partner Bar */}
{/* Left: label – stays put */} {fallbackText(hero.partnersLabel, homeContent.hero.partnersLabel)} {/* Right: ticker window */}
{/* Status Phase Slider */} {/* Schnell-Check Section */}
{/* Left (desktop) / Bottom (mobile) – text block (light bg, dark text) */}
{/* Eyebrow + heading – desktop only (on mobile these are overlaid on the image) */} {!isMobile && ( <> {fallbackText(quickCheck.eyebrow, homeContent.quickCheck.eyebrow)}

)}

{fallbackText(quickCheck.body, homeContent.quickCheck.body)}

{/* Criteria list */}
{quickCriteria.map((item, i) => (
{item.label}
{item.desc}
))}

{fallbackText(quickCheck.note, homeContent.quickCheck.note)}

{ (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)}
{/* Right (desktop) / Top (mobile) – image; on mobile the heading is overlaid here */}
{mediaAlt(quickCheck.image, {/* Overlay: mobile = navy shadow bottom-left (for the heading); desktop = leftward fade into text bg */}
{/* Heading overlay – mobile only */} {isMobile && (
{fallbackText(quickCheck.eyebrow, homeContent.quickCheck.eyebrow)}

)} {/* Gold accent line – bottom edge */}
{/* Intro Section – editorial split, no radius, no floating badge */}
{/* Left – full-bleed image with stats overlay (+ heading on mobile) */}
{mediaAlt(intro.image, {/* Dark overlay */}
{/* Heading overlay – mobile only (bottom-left) */} {isMobile && (
{fallbackText(intro.eyebrow, homeContent.intro.eyebrow)}

)} {/* Stats bottom-left – desktop only (mobile shows them below the image) */} {!isMobile && (
{introStats.map(({ value: v, label: l }) => (
{v}
{l}
))}
)}
{/* Right – editorial text */}
{!isMobile && ( <> {fallbackText(intro.eyebrow, homeContent.intro.eyebrow)}

)} {/* Stats row – mobile only, below the image */} {isMobile && (
{introStats.map(({ value: v, label: l }) => (
{v}
{l}
))}
)}
{fallbackText(intro.quote, homeContent.intro.quote)}

{fallbackText(intro.body, homeContent.intro.body)}

{ (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)}
{/* Winners Grid – neue Preisträger-Übersicht */}
{fallbackText(winnersSection.eyebrow, homeContent.winners.eyebrow)}

{fallbackText(winnersSection.cta?.label, homeContent.winners.cta.label)}
{homeWinners.map((w) => ( ))}
{/* Testimonials Section */} {/* Benefits Section – editorial split */}
{/* Left – text on white */}
{!isMobile && ( <> {fallbackText(benefits.eyebrow, homeContent.benefits.eyebrow)}

)} {/* Numbered rows – no cards, no shadows */}
{benefitItems.map((item, i) => (
(e.currentTarget.style.background = 'rgba(17,29,85,0.04)')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')} > {item.num}
{item.title}
{item.desc}
))}
{/* Right – full-bleed photo (+ heading on mobile) */}
{mediaAlt(benefits.image,
{/* Heading overlay – mobile only (bottom-left) */} {isMobile && (
{fallbackText(benefits.eyebrow, homeContent.benefits.eyebrow)}

)} {/* Bottom-left label – desktop only */} {!isMobile && (
{fallbackText(benefits.imageLabel, homeContent.benefits.imageLabel)}
)}
{/* CTA Section – with phase-aware embedded form */}
{/* Subtle radial glow behind left copy */}
{/* Left – pitch copy */}
{showApplicationForm ? fallbackText(application.eyebrow, homeContent.application.eyebrow) : newsletterCopy.eyebrow}

{showApplicationForm ? fallbackText(application.body, homeContent.application.body) : newsletterCopy.body}

{/* Benefit rows */}
{finalSectionBenefits.map(item => (
{item.num}
{item.label} {item.desc}
))}
{/* Divider */} {!isMobile &&
} {/* Right – gold premium panel */}
{/* Premium award crosshatch pattern */} {/* Inner vignette – edges slightly deeper gold */}
{/* Award ceremony photo */} {!isMobile && (
{mediaAlt(application.awardImage, {/* Blend photo into gold panel */}
{fallbackText(application.awardCaption, homeContent.application.awardCaption)}
)} {/* Form area */}
{showApplicationForm ? fallbackText(application.formEyebrow, homeContent.application.formEyebrow) : newsletterCopy.formEyebrow}

{showApplicationForm ? fallbackText(application.formTitle, homeContent.application.formTitle) : newsletterCopy.formTitle}

{showApplicationForm ? : }
{/* Footnote row */}
{showApplicationForm ? fallbackText(application.footnote, homeContent.application.footnote) : newsletterCopy.footnote} {showApplicationForm ? fallbackText(application.footnoteStrong, homeContent.application.footnoteStrong) : newsletterCopy.footnoteStrong}
{/* Video Modal */} {showVideo && (
{videoSrc ? (
)}
); }; // ─── 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 (
); } // ─── StatusSlideCard ────────────────────────────────────────────────────────── function StatusSlideCard({ phase, isMobile, noActionLabel, successApplicationsLabel, successWinnersLabel, }: { phase: RenderedStatusPhase; isMobile: boolean; noActionLabel: string; successApplicationsLabel: string; successWinnersLabel: string; }) { return (
{/* Left content */}
{/* Status badge + phase label */}
{phase.pulse && } {phase.tag}
{phase.phase}
{/* Headline + body + CTA */}

{phase.headline}

{phase.body}

{phase.cta ? ( 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} ) : (
{noActionLabel}
)}
{/* Meta */}
{phase.meta}
{/* Right visual panel */} {!isMobile &&
{phase.num !== '03' ? ( /* Concentric rings for active phases */
{([0, 20, 44] as const).map((inset, i) => (
))}
{phase.num}
) : ( /* Success stats */
{fallbackText(phase.successApplications, '')}
{successApplicationsLabel}
{fallbackText(phase.successWinners, '')}
{successWinnersLabel}
)} {/* Membership CTA – for active phases only */} {phase.num !== '03' && (
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}
)}
}
); } export default Home;