import React from 'react'; import { Calendar, MapPin } from 'lucide-react'; import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute'; import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField'; import Image from '@/spa/components/ui/UnoptimizedImage' import { pressIndexContent } from '@/spa/pressIndexContent'; const NAVY = '#111D55'; const GOLD = '#EFBF04'; const CREAM = '#E4E2E3'; const FF = '"IBM Plex Sans", sans-serif'; const FB = '"Inter", sans-serif'; type PressIndexCms = Partial & { hero?: Partial & { image?: unknown } events?: Partial & { collectionFallbackImage?: unknown; fallbackItems?: PressEventItem[] } news?: Partial & { collectionFallbackImage?: unknown; fallbackItems?: PressNewsItem[] } downloads?: Partial & { kitFile?: unknown } } type PressEventItem = (typeof pressIndexContent.events.fallbackItems)[number] & { image?: unknown } type PressNewsItem = (typeof pressIndexContent.news.fallbackItems)[number] & { image?: unknown } const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback; const fallbackArray = (value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback; type StatusLabel = { value?: string | null label?: string | null } const statusLabelMap = (items: unknown) => fallbackArray(items, pressIndexContent.events.statusLabels).reduce>((labels, item) => { const value = fallbackText(item.value, '') const label = fallbackText(item.label, '') if (value && label) labels[value] = label return labels }, {}) const normalizeStatus = (status: unknown, labels: Record, defaultStatusLabel: string) => { const value = fallbackText(status, defaultStatusLabel) return labels[value] || value } const eventFromCms = ( event: CmsRouteDoc, fallbackImage: string, labels: Record, eventsSection: NonNullable, ): PressEventItem => ({ title: String(event.title || fallbackText(eventsSection.missingTitleLabel, pressIndexContent.events.missingTitleLabel)), date: String(event.date || fallbackText(eventsSection.missingDateLabel, pressIndexContent.events.missingDateLabel)), location: String(event.location || event.venue || fallbackText(eventsSection.missingLocationLabel, pressIndexContent.events.missingLocationLabel)), cat: String(event.category || fallbackText(eventsSection.missingCategoryLabel, pressIndexContent.events.missingCategoryLabel)), status: normalizeStatus(event.status, labels, fallbackText(eventsSection.defaultStatusLabel, pressIndexContent.events.defaultStatusLabel)), img: docImageUrl(event, fallbackImage), desc: String(event.description || event.meta?.description || ''), slug: String(event.spaPath || `/presse/events/${event.slug}`), }) const postFromCms = (post: CmsRouteDoc, fallbackImage: string, newsSection: NonNullable): PressNewsItem => ({ title: String(post.title || fallbackText(newsSection.missingTitleLabel, pressIndexContent.news.missingTitleLabel)), excerpt: String(post.excerpt || post.meta?.description || ''), cat: String(post.cat || fallbackText(newsSection.missingCategoryLabel, pressIndexContent.news.missingCategoryLabel)), img: docImageUrl(post, fallbackImage, 'heroImage'), slug: String(post.spaPath || `/presse/blog/${post.slug}`), }) function Lines({ text }: { text: string }) { return <>{text.split('\n').map((line, i) => {i > 0 &&
}{line}
)} } const fallbackCardImage = (item: PressEventItem | PressNewsItem) => mediaUrl(item.image, fallbackText(item.img, '')) function NewsCard({ item, idx, isMobile, total }: { item: PressNewsItem; idx: number; isMobile: boolean; total: number }) { const [hovered, setHovered] = React.useState(false); return (
setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ display: 'flex', flexDirection: 'column', borderLeft: isMobile ? 'none' : (idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none'), borderBottom: isMobile && idx < total - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none', }} > {/* image */}
{item.title}
{/* body */}
{item.cat}

{item.title}

{item.excerpt}

); } const Press: React.FC = () => { const isMobile = useIsMobile(); const cms = (useCmsRoute()?.doc?.pressIndex || {}) as PressIndexCms; const hero = React.useMemo(() => ({ ...pressIndexContent.hero, ...(cms.hero || {}) }), [cms.hero]); const eventsSection = React.useMemo(() => ({ ...pressIndexContent.events, ...(cms.events || {}) }), [cms.events]); const newsSection = React.useMemo(() => ({ ...pressIndexContent.news, ...(cms.news || {}) }), [cms.news]); const downloads = React.useMemo(() => ({ ...pressIndexContent.downloads, ...(cms.downloads || {}) }), [cms.downloads]); const contactLines = fallbackText(downloads.contactLines, pressIndexContent.downloads.contactLines).split('\n'); const cmsEvents = useCmsCollection('events'); const cmsPosts = useCmsCollection('posts'); const eventStatusLabels = React.useMemo(() => statusLabelMap(eventsSection.statusLabels), [eventsSection.statusLabels]); const eventCollectionFallbackImage = mediaUrl(eventsSection.collectionFallbackImage, `/images/${pressIndexContent.events.collectionFallbackImageFilename}`); const newsCollectionFallbackImage = mediaUrl(newsSection.collectionFallbackImage, `/images/${pressIndexContent.news.collectionFallbackImageFilename}`); const combinedEvents = React.useMemo(() => { if (cmsEvents.length) { return cmsEvents.map((event) => eventFromCms( event, eventCollectionFallbackImage, eventStatusLabels, eventsSection, ), ); } return fallbackArray(eventsSection.fallbackItems, pressIndexContent.events.fallbackItems); }, [cmsEvents, eventCollectionFallbackImage, eventStatusLabels, eventsSection]); const combinedNews = React.useMemo(() => { if (cmsPosts.length) return cmsPosts.map((post) => postFromCms(post, newsCollectionFallbackImage, newsSection)); return fallbackArray(newsSection.fallbackItems, pressIndexContent.news.fallbackItems); }, [cmsPosts, newsCollectionFallbackImage, newsSection]); return (
{/* ── 1. HERO ─────────────────────────────────────────────────────── */}
{mediaAlt(hero.image,
{fallbackText(hero.eyebrow, pressIndexContent.hero.eyebrow)}

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

{/* ── 2. EVENTS ───────────────────────────────────────────────────── */}
{/* Section header */}
{fallbackText(eventsSection.eyebrow, pressIndexContent.events.eyebrow)}

{fallbackText(eventsSection.description, pressIndexContent.events.description)}

{/* Event rows */} {combinedEvents.map((event, idx) => (
{/* Left – image cell */}
{event.title}
{event.status}
{/* Right – copy cell */}
{event.cat}

{event.title}

{event.date}
{event.location}

{event.desc}

))}
{/* ── 3. NEWSROOM ─────────────────────────────────────────────────── */}
{/* Section header */}
{fallbackText(newsSection.eyebrow, pressIndexContent.news.eyebrow)}

{fallbackText(newsSection.description, pressIndexContent.news.description)}

{/* 3-column news grid */}
{combinedNews.map((item, idx) => ( ))}
{/* ── 4. PRESSE-MATERIAL & AKKREDITIERUNG ─────────────────────────── */}
{/* Left col */}
{fallbackText(downloads.eyebrow, pressIndexContent.downloads.eyebrow)}

{/* Gold divider */}

{fallbackText(downloads.description, pressIndexContent.downloads.description)}

{/* Press contact block */}
{fallbackText(downloads.contactEyebrow, pressIndexContent.downloads.contactEyebrow)}
{fallbackText(downloads.contactName, pressIndexContent.downloads.contactName)}
{contactLines.map((line, index) => ( {line} {index < contactLines.length - 1 &&
}
))}
); }; export default Press;