- Created a new collection `JuryMitglieder` with fields for name, image, quote, role, organization, bio, website, LinkedIn, sort order, active status, and published date. - Implemented access control for creating, reading, updating, and deleting jury members. - Added migration script to create necessary database tables and columns for jury members, including versioning support. - Updated existing `preistraeger` table to include new columns for award group, winner rank, sort order, and visibility settings. - Introduced a temporary global switch to control the visibility of testimonials in the SPA.
555 lines
22 KiB
TypeScript
555 lines
22 KiB
TypeScript
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<typeof pressIndexContent> & {
|
||
hero?: Partial<typeof pressIndexContent.hero> & { image?: unknown }
|
||
events?: Partial<typeof pressIndexContent.events> & { collectionFallbackImage?: unknown; fallbackItems?: PressEventItem[] }
|
||
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown; fallbackItems?: PressNewsItem[] }
|
||
downloads?: Partial<typeof pressIndexContent.downloads> & { 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 = <T,>(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<StatusLabel>(items, pressIndexContent.events.statusLabels).reduce<Record<string, string>>((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<string, string>, defaultStatusLabel: string) => {
|
||
const value = fallbackText(status, defaultStatusLabel)
|
||
return labels[value] || value
|
||
}
|
||
|
||
const eventFromCms = (
|
||
event: CmsRouteDoc,
|
||
fallbackImage: string,
|
||
labels: Record<string, string>,
|
||
eventsSection: NonNullable<PressIndexCms['events']>,
|
||
): 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<PressIndexCms['news']>): 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) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>
|
||
}
|
||
|
||
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 (
|
||
<article
|
||
onMouseEnter={() => 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 */}
|
||
<div style={{ position: 'relative', overflow: 'hidden', height: 220 }}>
|
||
<Image unoptimized
|
||
src={fallbackCardImage(item)}
|
||
alt={item.title}
|
||
style={{
|
||
width: '100%',
|
||
height: '100%',
|
||
objectFit: 'cover',
|
||
filter: hovered ? 'none' : 'grayscale(100%)',
|
||
transition: 'filter 0.5s',
|
||
}}
|
||
/>
|
||
<div
|
||
style={{
|
||
position: 'absolute',
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: '50%',
|
||
background: 'linear-gradient(to top, rgba(3,9,58,0.8), transparent)',
|
||
}}
|
||
/>
|
||
</div>
|
||
{/* body */}
|
||
<div style={{ padding: isMobile ? '24px 24px 32px' : '32px 36px 40px', display: 'flex', flexDirection: 'column', flex: 1 }}>
|
||
<span
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 9,
|
||
color: GOLD,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '0.22em',
|
||
fontWeight: 700,
|
||
marginBottom: 12,
|
||
display: 'block',
|
||
}}
|
||
>
|
||
{item.cat}
|
||
</span>
|
||
<h3
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 'clamp(1rem, 1.5vw, 1.2rem)',
|
||
fontWeight: 700,
|
||
color: '#fff',
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '-0.01em',
|
||
lineHeight: 1.25,
|
||
marginBottom: 16,
|
||
}}
|
||
>
|
||
{item.title}
|
||
</h3>
|
||
<div
|
||
style={{
|
||
width: hovered ? '100%' : 24,
|
||
height: 1,
|
||
background: 'rgba(239,191,4,0.5)',
|
||
transition: 'width 0.4s ease',
|
||
marginBottom: 16,
|
||
}}
|
||
/>
|
||
<p
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(255,255,255,0.4)',
|
||
lineHeight: 1.7,
|
||
flex: 1,
|
||
marginBottom: 20,
|
||
}}
|
||
>
|
||
{item.excerpt}
|
||
</p>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|
||
|
||
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<PressEventItem>(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<PressNewsItem>(newsSection.fallbackItems, pressIndexContent.news.fallbackItems);
|
||
}, [cmsPosts, newsCollectionFallbackImage, newsSection]);
|
||
return (
|
||
<div className="animate-fade-in">
|
||
|
||
{/* ── 1. HERO ─────────────────────────────────────────────────────── */}
|
||
<section
|
||
style={{
|
||
position: 'relative',
|
||
minHeight: '65vh',
|
||
display: 'flex',
|
||
alignItems: 'flex-end',
|
||
overflow: 'hidden',
|
||
background: NAVY,
|
||
}}
|
||
>
|
||
<Image unoptimized
|
||
src={mediaUrl(hero.image, `/images/${pressIndexContent.hero.imageFilename}`)}
|
||
alt={mediaAlt(hero.image, fallbackText(hero.imageAlt, pressIndexContent.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',
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: 2,
|
||
background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)',
|
||
}}
|
||
/>
|
||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 80px', maxWidth: 860 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 24 }}>
|
||
<div style={{ width: 36, height: 2, background: GOLD }} />
|
||
<span
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 10,
|
||
fontWeight: 700,
|
||
letterSpacing: '0.32em',
|
||
textTransform: 'uppercase',
|
||
color: GOLD,
|
||
}}
|
||
>
|
||
{fallbackText(hero.eyebrow, pressIndexContent.hero.eyebrow)}
|
||
</span>
|
||
</div>
|
||
<h1
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: isMobile ? 'clamp(2rem, 8vw, 2.8rem)' : 'clamp(2.8rem, 6vw, 5rem)',
|
||
fontWeight: 900,
|
||
color: '#fff',
|
||
lineHeight: 0.95,
|
||
letterSpacing: '-0.03em',
|
||
textTransform: 'uppercase',
|
||
margin: '0 0 24px',
|
||
overflowWrap: 'anywhere',
|
||
}}
|
||
>
|
||
<Lines text={fallbackText(hero.heading, pressIndexContent.hero.heading)} />
|
||
</h1>
|
||
<div style={{ width: 48, height: 2, background: GOLD, marginBottom: 24 }} />
|
||
<p
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(255,255,255,0.6)',
|
||
fontWeight: 300,
|
||
maxWidth: 520,
|
||
lineHeight: 1.7,
|
||
margin: 0,
|
||
}}
|
||
>
|
||
{fallbackText(hero.description, pressIndexContent.hero.description)}
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── 2. EVENTS ───────────────────────────────────────────────────── */}
|
||
<section id="events" style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||
<MunichSkylineBg />
|
||
{/* Section header */}
|
||
<div
|
||
style={{
|
||
padding: isMobile ? '48px 24px 40px' : '80px 80px 56px',
|
||
display: 'grid',
|
||
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
|
||
gap: isMobile ? 24 : 48,
|
||
alignItems: 'flex-end',
|
||
borderBottom: '1px solid rgba(3,9,58,0.1)',
|
||
}}
|
||
>
|
||
<div>
|
||
<div
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 10,
|
||
fontWeight: 700,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '0.28em',
|
||
color: '#4A8FC9',
|
||
marginBottom: 16,
|
||
}}
|
||
>
|
||
{fallbackText(eventsSection.eyebrow, pressIndexContent.events.eyebrow)}
|
||
</div>
|
||
<h2
|
||
style={{
|
||
fontFamily: FF,
|
||
fontWeight: 900,
|
||
color: '#101828',
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '-0.025em',
|
||
fontSize: 'clamp(2rem, 3.5vw, 3rem)',
|
||
margin: 0,
|
||
}}
|
||
>
|
||
<Lines text={fallbackText(eventsSection.heading, pressIndexContent.events.heading)} />
|
||
</h2>
|
||
</div>
|
||
<div>
|
||
<p
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(16,24,40,0.45)',
|
||
lineHeight: 1.8,
|
||
margin: 0,
|
||
}}
|
||
>
|
||
{fallbackText(eventsSection.description, pressIndexContent.events.description)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Event rows */}
|
||
{combinedEvents.map((event, idx) => (
|
||
<div
|
||
key={idx}
|
||
style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: isMobile ? '1fr' : '320px 1fr',
|
||
borderBottom: '1px solid rgba(3,9,58,0.08)',
|
||
background: 'transparent',
|
||
}}
|
||
>
|
||
{/* Left – image cell */}
|
||
<div style={{ position: 'relative', overflow: 'hidden', height: isMobile ? 200 : 240 }}>
|
||
<Image unoptimized src={fallbackCardImage(event)} alt={event.title} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||
<div style={{ position: 'absolute', top: 20, left: 20, padding: '5px 12px', fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase' as const, letterSpacing: '0.18em', background: event.status === fallbackText(eventsSection.openStatusLabel, pressIndexContent.events.openStatusLabel) ? GOLD : 'rgba(3,9,58,0.85)', color: event.status === fallbackText(eventsSection.openStatusLabel, pressIndexContent.events.openStatusLabel) ? NAVY : 'rgba(255,255,255,0.6)' }}>
|
||
{event.status}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Right – copy cell */}
|
||
<div style={{ padding: isMobile ? '24px 24px 32px' : '40px 56px 40px 48px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
|
||
<div style={{ fontFamily: FF, fontSize: 9, color: '#4A8FC9', textTransform: 'uppercase' as const, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>{event.cat}</div>
|
||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase' as const, letterSpacing: '-0.02em', margin: '0 0 14px' }}>{event.title}</h3>
|
||
<div style={{ display: 'flex', gap: isMobile ? 16 : 28, alignItems: 'center', flexWrap: 'wrap', marginBottom: 16 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)' }}>
|
||
<Calendar size={14} color={GOLD} />{event.date}
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)' }}>
|
||
<MapPin size={14} color={GOLD} />{event.location}
|
||
</div>
|
||
</div>
|
||
<div style={{ width: 24, height: 1, background: 'rgba(239,191,4,0.4)', marginBottom: 14 }} />
|
||
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.5)', lineHeight: 1.7, flex: 1 }}>{event.desc}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</section>
|
||
|
||
{/* ── 3. NEWSROOM ─────────────────────────────────────────────────── */}
|
||
<section style={{ background: NAVY, overflow: 'hidden' }}>
|
||
{/* Section header */}
|
||
<div
|
||
style={{
|
||
padding: isMobile ? '48px 24px 40px' : '80px 80px 56px',
|
||
display: 'grid',
|
||
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
|
||
gap: isMobile ? 24 : 48,
|
||
alignItems: 'flex-end',
|
||
borderBottom: '1px solid rgba(255,255,255,0.07)',
|
||
}}
|
||
>
|
||
<div>
|
||
<div
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 10,
|
||
fontWeight: 700,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '0.28em',
|
||
color: GOLD,
|
||
marginBottom: 16,
|
||
}}
|
||
>
|
||
{fallbackText(newsSection.eyebrow, pressIndexContent.news.eyebrow)}
|
||
</div>
|
||
<h2
|
||
style={{
|
||
fontFamily: FF,
|
||
fontWeight: 900,
|
||
color: '#fff',
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '-0.025em',
|
||
fontSize: 'clamp(2rem, 3.5vw, 3rem)',
|
||
margin: 0,
|
||
}}
|
||
>
|
||
<Lines text={fallbackText(newsSection.heading, pressIndexContent.news.heading)} />
|
||
</h2>
|
||
</div>
|
||
<div>
|
||
<p
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(255,255,255,0.4)',
|
||
lineHeight: 1.8,
|
||
margin: 0,
|
||
}}
|
||
>
|
||
{fallbackText(newsSection.description, pressIndexContent.news.description)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 3-column news grid */}
|
||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)' }}>
|
||
{combinedNews.map((item, idx) => (
|
||
<NewsCard key={idx} item={item} idx={idx} isMobile={isMobile} total={combinedNews.length} />
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── 4. PRESSE-MATERIAL & AKKREDITIERUNG ─────────────────────────── */}
|
||
<section id="downloads" style={{ background: CREAM, overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||
<MunichSkylineBg />
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', minHeight: isMobile ? 'auto' : 600 }}>
|
||
|
||
{/* Left col */}
|
||
<div
|
||
style={{
|
||
background: '#fff',
|
||
padding: isMobile ? '48px 24px' : '88px 80px',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 10,
|
||
fontWeight: 700,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '0.28em',
|
||
color: '#4A8FC9',
|
||
marginBottom: 16,
|
||
}}
|
||
>
|
||
{fallbackText(downloads.eyebrow, pressIndexContent.downloads.eyebrow)}
|
||
</div>
|
||
|
||
<h2
|
||
style={{
|
||
fontFamily: FF,
|
||
fontWeight: 900,
|
||
color: '#101828',
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '-0.025em',
|
||
fontSize: 'clamp(1.8rem, 3vw, 2.6rem)',
|
||
margin: '0 0 20px',
|
||
}}
|
||
>
|
||
<Lines text={fallbackText(downloads.heading, pressIndexContent.downloads.heading)} />
|
||
</h2>
|
||
|
||
{/* Gold divider */}
|
||
<div style={{ width: 40, height: 2, background: GOLD, margin: '0 0 28px' }} />
|
||
|
||
<p
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(16,24,40,0.5)',
|
||
lineHeight: 1.8,
|
||
maxWidth: 380,
|
||
marginBottom: 0,
|
||
}}
|
||
>
|
||
{fallbackText(downloads.description, pressIndexContent.downloads.description)}
|
||
</p>
|
||
|
||
{/* Press contact block */}
|
||
<div style={{ borderLeft: `3px solid ${GOLD}`, paddingLeft: 24, marginTop: 32 }}>
|
||
<div
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 10,
|
||
color: '#4A8FC9',
|
||
textTransform: 'uppercase',
|
||
letterSpacing: '0.2em',
|
||
fontWeight: 700,
|
||
marginBottom: 12,
|
||
}}
|
||
>
|
||
{fallbackText(downloads.contactEyebrow, pressIndexContent.downloads.contactEyebrow)}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontFamily: FF,
|
||
fontSize: 18,
|
||
fontWeight: 700,
|
||
color: '#101828',
|
||
marginBottom: 4,
|
||
}}
|
||
>
|
||
{fallbackText(downloads.contactName, pressIndexContent.downloads.contactName)}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontFamily: FB,
|
||
fontSize: 18,
|
||
color: 'rgba(16,24,40,0.55)',
|
||
lineHeight: 1.8,
|
||
overflowWrap: 'anywhere',
|
||
}}
|
||
>
|
||
{contactLines.map((line, index) => (
|
||
<React.Fragment key={`${line}-${index}`}>
|
||
{line}
|
||
{index < contactLines.length - 1 && <br />}
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Press;
|