503 lines
35 KiB
TypeScript
503 lines
35 KiB
TypeScript
import React, { useMemo, useState } from 'react'
|
|
import { ChevronRight, Star } from 'lucide-react'
|
|
|
|
import AwardsGridSection, { type AwardsGridContent } from '@/spa/components/AwardsGridSection'
|
|
import TestimonialsSection from '@/spa/components/TestimonialsSection'
|
|
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'
|
|
import Image from '@/spa/components/ui/UnoptimizedImage'
|
|
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField'
|
|
import { resolveCmsRelationship, useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute'
|
|
import { WINNERS, type Winner } from '@/spa/data/winners'
|
|
import { useIsMobile } from '@/spa/hooks/useIsMobile'
|
|
import { Link } from '@/spa/router'
|
|
import { aboutContent } from '@/spa/aboutContent'
|
|
import { usePreistraegerPlaceholderImage } from '@/spa/preistraegerPlaceholder'
|
|
|
|
const FF = '"IBM Plex Sans", sans-serif'
|
|
const FB = '"Inter", sans-serif'
|
|
const NAVY = '#111D55'
|
|
const GOLD = '#EFBF04'
|
|
const CREAM = '#E4E2E3'
|
|
const HIDE_MEMBERSHIP_ABOUT_CTA = true
|
|
|
|
type AboutCms = Partial<typeof aboutContent> & {
|
|
hero?: Partial<typeof aboutContent.hero> & { backgroundImage?: unknown }
|
|
prize?: Partial<typeof aboutContent.prize> & { image?: unknown }
|
|
mittelstand?: Partial<typeof aboutContent.mittelstand> & { image?: unknown }
|
|
highlights?: Partial<typeof aboutContent.highlights> & {
|
|
fallbackImage?: unknown
|
|
fallbackWinner?: Partial<typeof aboutContent.highlights.fallbackWinner>
|
|
selectedWinners?: unknown[] | null
|
|
}
|
|
}
|
|
|
|
type TextRow = { text?: string | null }
|
|
|
|
const FALLBACK_HIGHLIGHTS = WINNERS.filter((w) => w.year === aboutContent.highlights.year).slice(0, 8)
|
|
|
|
const fallbackText = (value: unknown, fallback: string) => (typeof value === 'string' && value.length > 0 ? value : fallback)
|
|
|
|
const fallbackArray = <T,>(value: unknown, fallback: T[]) => (Array.isArray(value) && value.length ? (value as T[]) : fallback)
|
|
|
|
const isMembershipCta = (cta?: { label?: string | null; url?: string | null } | null) => {
|
|
const normalizedLabel = cta?.label?.trim().toLowerCase()
|
|
|
|
return cta?.url === '/mitglied-werden' || normalizedLabel === 'mitglied werden' || normalizedLabel === 'vereinsmitglied 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 paragraphs = (value: unknown, fallback: string[]) =>
|
|
Array.isArray(value) && value.length
|
|
? (value as Array<string | TextRow>).map((item) => (typeof item === 'string' ? item : fallbackText(item.text, ''))).filter(Boolean)
|
|
: fallback
|
|
|
|
const isParticipationPage = (page: CmsRouteDoc) => {
|
|
const title = String(page.title || '').toLowerCase()
|
|
|
|
return page.spaPath === '/teilnahme' || page.slug === 'teilnahme' || page.slug === 'participation' || title === 'teilnahme' || title === 'participation'
|
|
}
|
|
|
|
function HighlightedText({ text, highlight }: { text: string; highlight: string }) {
|
|
const index = text.indexOf(highlight)
|
|
if (!highlight || index < 0) return <>{text}</>
|
|
|
|
return (
|
|
<>
|
|
{text.slice(0, index)}
|
|
<span style={{ color: GOLD }}>{highlight}</span>
|
|
{text.slice(index + highlight.length)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const normalizeWinner = (
|
|
doc: CmsRouteDoc | undefined,
|
|
placeholderImage: string,
|
|
fallbackWinner: typeof aboutContent.highlights.fallbackWinner,
|
|
): Winner | undefined => {
|
|
if (!doc) return undefined
|
|
return {
|
|
id: String(doc.id),
|
|
slug: String(doc.slug || ''),
|
|
name: String(doc.title || fallbackWinner.name),
|
|
category: String(doc.category || fallbackWinner.category),
|
|
year: Number(doc.year || aboutContent.highlights.year),
|
|
type: String(doc.awardType || fallbackWinner.awardType),
|
|
img: mediaUrl(doc.image, placeholderImage),
|
|
shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''),
|
|
longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''),
|
|
quote: String(doc.quote || ''),
|
|
quotePerson: String(doc.quotePerson || doc.title || ''),
|
|
quoteRole: String(doc.quoteRole || ''),
|
|
location: String(doc.location || ''),
|
|
industry: String(doc.industry || ''),
|
|
website: String(doc.website || ''),
|
|
hasMedia: Boolean(doc.hasMedia),
|
|
}
|
|
}
|
|
|
|
const About: React.FC = () => {
|
|
const isMobile = useIsMobile()
|
|
const placeholderImage = usePreistraegerPlaceholderImage()
|
|
const cms = (useCmsRoute()?.doc?.about || {}) as AboutCms
|
|
const cmsWinners = useCmsCollection('preistraeger')
|
|
const cmsPages = useCmsCollection('pages')
|
|
const hero = { ...aboutContent.hero, ...(cms.hero || {}) }
|
|
const prize = { ...aboutContent.prize, ...(cms.prize || {}) }
|
|
const mittelstand = { ...aboutContent.mittelstand, ...(cms.mittelstand || {}) }
|
|
const highlights = useMemo(() => ({ ...aboutContent.highlights, ...(cms.highlights || {}) }), [cms.highlights])
|
|
const testimonials = { ...aboutContent.testimonials, ...(cms.testimonials || {}) }
|
|
const history = { ...aboutContent.history, ...(cms.history || {}) }
|
|
const goals = { ...aboutContent.goals, ...(cms.goals || {}) }
|
|
const values = { ...aboutContent.values, ...(cms.values || {}) }
|
|
const cta = { ...aboutContent.cta, ...(cms.cta || {}) }
|
|
const participationPage = cmsPages.find(isParticipationPage)
|
|
const participationAwardsGrid = (participationPage?.participation as { awardsGrid?: AwardsGridContent } | undefined)?.awardsGrid
|
|
const showSecondaryCta = !HIDE_MEMBERSHIP_ABOUT_CTA || !isMembershipCta(cta.secondaryCta)
|
|
const highlightFallbackWinner = useMemo(
|
|
() => ({ ...aboutContent.highlights.fallbackWinner, ...(highlights.fallbackWinner || {}) }),
|
|
[highlights],
|
|
)
|
|
|
|
const highlightWinners = useMemo(() => {
|
|
const selected = fallbackArray<unknown>(highlights.selectedWinners, [])
|
|
.map((entry) => normalizeWinner(resolveCmsRelationship(entry, cmsWinners), placeholderImage, highlightFallbackWinner))
|
|
.filter((winner): winner is Winner => Boolean(winner))
|
|
|
|
if (selected.length) return selected.slice(0, 8)
|
|
|
|
const isWinnerFromYear = (winner: Winner | undefined): winner is Winner =>
|
|
Boolean(winner) && winner?.year === Number(highlights.year || aboutContent.highlights.year)
|
|
|
|
const cmsByYear = cmsWinners
|
|
.map((winner) => normalizeWinner(winner, placeholderImage, highlightFallbackWinner))
|
|
.filter(isWinnerFromYear)
|
|
.slice(0, 8)
|
|
|
|
return cmsByYear.length ? cmsByYear : FALLBACK_HIGHLIGHTS
|
|
}, [cmsWinners, highlightFallbackWinner, highlights, placeholderImage])
|
|
|
|
return (
|
|
<div className="animate-fade-in">
|
|
<section style={{ position: 'relative', minHeight: '72vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: '#060C14' }}>
|
|
<Image
|
|
unoptimized
|
|
src={mediaUrl(hero.backgroundImage, `/images/${aboutContent.hero.backgroundImageFilename}`)}
|
|
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, aboutContent.hero.imageAlt))}
|
|
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', 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 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' }}>
|
|
{fallbackText(hero.eyebrow, aboutContent.hero.eyebrow)}
|
|
</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' }}>
|
|
{fallbackText(hero.heading, aboutContent.hero.heading).split('\n').map((line, index) => (
|
|
<React.Fragment key={`${line}-${index}`}>
|
|
{index > 0 && <br />}
|
|
<span style={{ color: line === fallbackText(hero.highlight, aboutContent.hero.highlight) ? GOLD : '#fff' }}>{line}</span>
|
|
</React.Fragment>
|
|
))}
|
|
</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 }}>
|
|
{fallbackText(hero.description, aboutContent.hero.description)}
|
|
</p>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
|
|
<Link
|
|
to={fallbackText(hero.primaryCta?.url, aboutContent.hero.primaryCta.url)}
|
|
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.style.background = GOLD; e.currentTarget.style.color = '#101828' }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.background = '#111D55'; e.currentTarget.style.color = '#fff' }}
|
|
>
|
|
{fallbackText(hero.primaryCta?.label, aboutContent.hero.primaryCta.label)} <ChevronRight size={16} />
|
|
</Link>
|
|
<Link
|
|
to={fallbackText(hero.secondaryCta?.url, aboutContent.hero.secondaryCta.url)}
|
|
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.style.background = '#FFD130'; e.currentTarget.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)' }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.background = GOLD; e.currentTarget.style.boxShadow = 'none' }}
|
|
>
|
|
{fallbackText(hero.secondaryCta?.label, aboutContent.hero.secondaryCta.label)} <ChevronRight size={16} />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<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)' }}>
|
|
{fallbackArray(hero.stats, aboutContent.hero.stats).map((item, i) => (
|
|
<div key={`${item.value}-${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 }}>{fallbackText(item.value, '')}</div>
|
|
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginTop: 2 }}>{fallbackText(item.label, '')}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<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 }}>
|
|
<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 }}>{fallbackText(prize.eyebrow, aboutContent.prize.eyebrow)}</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' }}>
|
|
<Lines text={fallbackText(prize.heading, aboutContent.prize.heading)} />
|
|
</h2>
|
|
</>
|
|
)}
|
|
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
|
{paragraphs(prize.body, aboutContent.prize.body).map((text, index, arr) => (
|
|
<p key={index} style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.8)', lineHeight: 1.8, maxWidth: 440, marginBottom: index === arr.length - 1 ? (isMobile ? 0 : 44) : 16 }}>
|
|
{text}
|
|
</p>
|
|
))}
|
|
<div style={{ display: 'flex', order: isMobile ? -1 : 0, borderTop: isMobile ? 'none' : '1px solid #D0D5DD', borderBottom: isMobile ? '1px solid #D0D5DD' : 'none', marginBottom: isMobile ? 26 : 0 }}>
|
|
{fallbackArray(prize.facts, aboutContent.prize.facts).map((f, i, arr) => (
|
|
<div key={`${f.value}-${i}`} style={{ flex: 1, padding: '20px 0', borderRight: i < arr.length - 1 ? '1px solid #D0D5DD' : 'none', paddingLeft: 0, paddingRight: i < arr.length - 1 ? 20 : 0 }}>
|
|
<div style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2vw, 1.8rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.03em', lineHeight: 1 }}>{fallbackText(f.value, '')}</div>
|
|
<div style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.18em', marginTop: 6 }}>{fallbackText(f.label, '')}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined, order: isMobile ? 1 : 0 }}>
|
|
<Image
|
|
unoptimized
|
|
src={mediaUrl(prize.image, `/images/${aboutContent.prize.imageFilename}`)}
|
|
alt={mediaAlt(prize.image, fallbackText(prize.imageAlt, aboutContent.prize.imageAlt))}
|
|
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%)` }} />
|
|
{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 }}>{fallbackText(prize.eyebrow, aboutContent.prize.eyebrow)}</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)' }}>
|
|
<Lines text={fallbackText(prize.heading, aboutContent.prize.heading)} />
|
|
</h2>
|
|
</div>
|
|
)}
|
|
{!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 }}>{fallbackText(prize.imageLabel, aboutContent.prize.imageLabel)}</div>
|
|
<div style={{ width: 28, height: 2, background: GOLD }} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="mittelstand" style={{ overflow: 'hidden' }}>
|
|
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '45% 55%', minHeight: isMobile ? 'auto' : 580 }}>
|
|
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined }}>
|
|
<Image
|
|
unoptimized
|
|
src={mediaUrl(mittelstand.image, `/images/${aboutContent.mittelstand.imageFilename}`)}
|
|
alt={mediaAlt(mittelstand.image, fallbackText(mittelstand.imageAlt, aboutContent.mittelstand.imageAlt))}
|
|
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>
|
|
)}
|
|
{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 }}>{fallbackText(mittelstand.eyebrow, aboutContent.mittelstand.eyebrow)}</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)' }}>
|
|
<Lines text={fallbackText(mittelstand.heading, aboutContent.mittelstand.heading)} />
|
|
</h2>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<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 }}>{fallbackText(mittelstand.eyebrow, aboutContent.mittelstand.eyebrow)}</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' }}>
|
|
<Lines text={fallbackText(mittelstand.heading, aboutContent.mittelstand.heading)} />
|
|
</h2>
|
|
</>
|
|
)}
|
|
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 28 }} />
|
|
{paragraphs(mittelstand.body, aboutContent.mittelstand.body).map((text, index, arr) => (
|
|
<p key={index} style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.8, maxWidth: 440, marginBottom: index === arr.length - 1 ? 40 : 16 }}>
|
|
{text}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<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', 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 }}>{fallbackText(highlights.eyebrow, aboutContent.highlights.eyebrow)}</span>
|
|
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: 0, lineHeight: 1 }}>
|
|
<Lines text={fallbackText(highlights.heading, aboutContent.highlights.heading)} />
|
|
</h2>
|
|
</div>
|
|
<Link
|
|
to={fallbackText(highlights.cta?.url, aboutContent.highlights.cta.url)}
|
|
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' }}
|
|
>
|
|
{fallbackText(highlights.cta?.label, aboutContent.highlights.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' }}>
|
|
{highlightWinners.map((w) => <HighlightCard key={w.id} winner={w} hoverLabel={fallbackText(highlights.hoverLabel, aboutContent.highlights.hoverLabel)} />)}
|
|
</div>
|
|
</section>
|
|
|
|
<TestimonialsSection data={testimonials} />
|
|
|
|
<section id="geschichte" style={{ overflow: 'hidden' }}>
|
|
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', minHeight: isMobile ? 'auto' : 700 }}>
|
|
<div style={{ background: NAVY, padding: isMobile ? '56px 24px' : '88px 80px', position: 'relative', overflow: 'hidden' }}>
|
|
{!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' }}>{fallbackText(history.watermark, aboutContent.history.watermark)}</div>}
|
|
|
|
<span style={{ fontFamily: FF, fontSize: 10, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(history.eyebrow, aboutContent.history.eyebrow)}</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' }}>
|
|
{fallbackText(history.heading, aboutContent.history.heading).split('\n').map((line, index) => (
|
|
<React.Fragment key={`${line}-${index}`}>
|
|
{index > 0 && <br />}
|
|
<HighlightedText text={line} highlight={fallbackText(history.highlight, aboutContent.history.highlight)} />
|
|
</React.Fragment>
|
|
))}
|
|
</h2>
|
|
|
|
<div style={{ position: 'relative', paddingLeft: 28 }}>
|
|
<div style={{ position: 'absolute', left: 0, top: 6, bottom: 6, width: 2, background: `linear-gradient(to bottom, ${GOLD}, rgba(239,191,4,0.15))` }} />
|
|
|
|
{fallbackArray(history.items, aboutContent.history.items).map((item, i, arr) => (
|
|
<div key={`${item.year}-${i}`} style={{ position: 'relative', marginBottom: i < arr.length - 1 ? 32 : 0 }}>
|
|
<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 }}>{fallbackText(item.year, '')}</div>
|
|
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: '#fff', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 5 }}>{fallbackText(item.title, '')}</div>
|
|
<div style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.38)', lineHeight: 1.65 }}>{fallbackText(item.desc, '')}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ background: '#fff', 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 }}>{fallbackText(goals.eyebrow, aboutContent.goals.eyebrow)}</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' }}>
|
|
<Lines text={fallbackText(goals.heading, aboutContent.goals.heading)} />
|
|
</h2>
|
|
|
|
<div>
|
|
{fallbackArray(goals.items, aboutContent.goals.items).map((item, i, arr) => (
|
|
<div key={`${item.num}-${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 }}>{fallbackText(item.num, '')}</span>
|
|
<div>
|
|
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 800, color: NAVY, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 7 }}>{fallbackText(item.title, '')}</div>
|
|
<div style={{ fontFamily: FB, fontSize: 16, color: 'rgba(17,29,85,0.5)', lineHeight: 1.75 }}>{fallbackText(item.desc, '')}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="vorteile" style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
|
|
<MunichSkylineBg />
|
|
<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 }}>{fallbackText(values.eyebrow, aboutContent.values.eyebrow)}</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 }}>
|
|
<Lines text={fallbackText(values.heading, aboutContent.values.heading)} />
|
|
</h2>
|
|
</div>
|
|
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.8, margin: 0, maxWidth: 440 }}>
|
|
{fallbackText(values.description, aboutContent.values.description)}
|
|
</p>
|
|
</div>
|
|
|
|
{fallbackArray(values.items, aboutContent.values.items).map((item, i, arr) => (
|
|
<ValueRow key={`${item.num}-${i}`} item={item} last={i === arr.length - 1} />
|
|
))}
|
|
</section>
|
|
|
|
<AwardsGridSection content={participationAwardsGrid} />
|
|
|
|
<section style={{ background: NAVY, padding: isMobile ? '48px 24px' : '100px 80px', position: 'relative', overflow: 'hidden' }}>
|
|
<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' }}>{fallbackText(cta.eyebrow, aboutContent.cta.eyebrow)}</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' }}>
|
|
<Lines text={fallbackText(cta.heading, aboutContent.cta.heading)} />
|
|
</h2>
|
|
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.55)', lineHeight: 1.8, marginBottom: 48, fontWeight: 300 }}>
|
|
{fallbackText(cta.description, aboutContent.cta.description)}
|
|
</p>
|
|
<Link
|
|
to={fallbackText(cta.primaryCta?.url, aboutContent.cta.primaryCta.url)}
|
|
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.style.background = '#FFD130'; e.currentTarget.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)' }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.background = GOLD; e.currentTarget.style.boxShadow = 'none' }}
|
|
>
|
|
{fallbackText(cta.primaryCta?.label, aboutContent.cta.primaryCta.label)} <ChevronRight size={16} />
|
|
</Link>
|
|
{showSecondaryCta && (
|
|
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'center' }}>
|
|
<span style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.3)', marginRight: 8 }}>{fallbackText(cta.secondaryPrefix, aboutContent.cta.secondaryPrefix)}</span>
|
|
<Link
|
|
to={fallbackText(cta.secondaryCta?.url, aboutContent.cta.secondaryCta.url)}
|
|
style={{ fontFamily: FF, 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.style.color = '#EFBF04'; e.currentTarget.style.borderBottomColor = '#EFBF04' }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.color = 'rgba(239,191,4,0.7)'; e.currentTarget.style.borderBottomColor = 'rgba(239,191,4,0.3)' }}
|
|
>
|
|
{fallbackText(cta.secondaryCta?.label, aboutContent.cta.secondaryCta.label)} <ChevronRight size={10} />
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function HighlightCard({ winner, hoverLabel }: { winner: Winner; hoverLabel: string }) {
|
|
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)}
|
|
>
|
|
<Image unoptimized 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>
|
|
<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' }}>
|
|
{hoverLabel}
|
|
</div>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
function ValueRow({ item, last }: { item: { num?: string | null; title?: string | null }; 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)}
|
|
>
|
|
<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' }}>{fallbackText(item.num, '')}</div>
|
|
</div>
|
|
<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 }}>{fallbackText(item.title, '')}</h3>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default About
|