feat: add participation and network support pages, update about fields and home winners section
- Added support for '/teilnahme' and '/netzwerk' in findSupportPages function. - Updated the description in aboutFields to clarify the relationship with the homepage Preisträger grid. - Enhanced the About page by integrating HomeWinnersSection for better modularity. - Created HomeWinnersSection component to encapsulate winners display logic and improve code organization. - Removed unused WinnerCardData type and related logic from Home component.
This commit is contained in:
@@ -1,40 +1,32 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { ChevronRight, Star } from 'lucide-react'
|
||||
import React, { useState } from 'react'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
|
||||
import AwardsGridSection, { type AwardsGridContent } from '@/spa/components/AwardsGridSection'
|
||||
import HomeWinnersSection, { type HomeWinnersContent } from '@/spa/components/HomeWinnersSection'
|
||||
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 { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute'
|
||||
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 WHITE = '#fff'
|
||||
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)
|
||||
@@ -69,6 +61,12 @@ const isParticipationPage = (page: CmsRouteDoc) => {
|
||||
return page.spaPath === '/teilnahme' || page.slug === 'teilnahme' || page.slug === 'participation' || title === 'teilnahme' || title === 'participation'
|
||||
}
|
||||
|
||||
const isHomePage = (page: CmsRouteDoc) => {
|
||||
const title = String(page.title || '').toLowerCase()
|
||||
|
||||
return page.spaPath === '/' || page.slug === 'startseite' || page.slug === 'home' || title === 'startseite' || title === 'home'
|
||||
}
|
||||
|
||||
function HighlightedText({ text, highlight }: { text: string; highlight: string }) {
|
||||
const index = text.indexOf(highlight)
|
||||
if (!highlight || index < 0) return <>{text}</>
|
||||
@@ -82,72 +80,23 @@ function HighlightedText({ text, highlight }: { text: string; highlight: string
|
||||
)
|
||||
}
|
||||
|
||||
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 homePage = cmsPages.find(isHomePage)
|
||||
const participationAwardsGrid = (participationPage?.participation as { awardsGrid?: AwardsGridContent } | undefined)?.awardsGrid
|
||||
const homeWinners = (homePage?.home as { winners?: HomeWinnersContent } | undefined)?.winners
|
||||
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">
|
||||
@@ -213,7 +162,7 @@ const About: React.FC = () => {
|
||||
<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 }}>
|
||||
<div style={{ background: WHITE, 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>
|
||||
@@ -247,7 +196,7 @@ const About: React.FC = () => {
|
||||
/>
|
||||
<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%)` }} />
|
||||
: `linear-gradient(to right, ${WHITE} 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>
|
||||
@@ -312,26 +261,7 @@ const About: React.FC = () => {
|
||||
</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>
|
||||
<HomeWinnersSection content={homeWinners} />
|
||||
|
||||
<TestimonialsSection data={testimonials} />
|
||||
|
||||
@@ -385,7 +315,7 @@ const About: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="vorteile" style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
|
||||
<section id="vorteile" style={{ background: WHITE, 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>
|
||||
@@ -404,7 +334,7 @@ const About: React.FC = () => {
|
||||
))}
|
||||
</section>
|
||||
|
||||
<AwardsGridSection content={participationAwardsGrid} />
|
||||
<AwardsGridSection content={participationAwardsGrid} featuredGoldeneBavaria />
|
||||
|
||||
<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 }} />
|
||||
@@ -449,37 +379,6 @@ const About: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button } from '@/spa/components/ui/button';
|
||||
import { Play, ChevronLeft, ChevronRight, X, Star } from 'lucide-react';
|
||||
import { Play, ChevronRight, X } from 'lucide-react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { PartnerTicker } from '@/spa/components/ui/partner-ticker';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import { ApplicationFormSection } from '@/spa/components/forms/ApplicationFormSection';
|
||||
import HomeWinnersSection from '@/spa/components/HomeWinnersSection';
|
||||
import TestimonialsSection from '@/spa/components/TestimonialsSection';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
@@ -13,9 +13,7 @@ import type { SpaApplicationPhaseData, SpaApplicationPhaseItem } from '@/spa/app
|
||||
import { useApplicationPhase, useCmsCollection, useCmsRoute } from '@/spa/cmsRoute'
|
||||
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField'
|
||||
import { homeContent } from '@/spa/homeContent'
|
||||
import { resolveHomeWinners } from '@/spa/homeWinnerSelection'
|
||||
import { netzwerkContent } from '@/spa/netzwerkContent'
|
||||
import { usePreistraegerPlaceholderImage } from '@/spa/preistraegerPlaceholder'
|
||||
import { PartnerLogoMark, type PartnerLogoData } from '@/spa/components/ui/partner-logo'
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
@@ -93,20 +91,6 @@ type RenderedStatusPhase = Omit<SpaApplicationPhaseItem, 'accent' | 'bg' | 'cta'
|
||||
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;
|
||||
|
||||
@@ -168,63 +152,10 @@ function sortedPartners<T extends PartnerLogoData>(partners: T[]) {
|
||||
.map(({ partner }) => partner);
|
||||
}
|
||||
|
||||
function HomeWinnerCard({
|
||||
winner,
|
||||
fallbackTitle,
|
||||
fallbackImage,
|
||||
hoverLabel,
|
||||
horizontalScroll = false,
|
||||
}: {
|
||||
winner: WinnerCardData;
|
||||
fallbackTitle: string;
|
||||
fallbackImage: string;
|
||||
hoverLabel: string;
|
||||
horizontalScroll?: boolean;
|
||||
}) {
|
||||
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 = winner?.img || mediaUrl(winner?.image, 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 || horizontalScroll ? '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 winnersScrollRef = useRef<HTMLDivElement>(null);
|
||||
const isMobile = useIsMobile();
|
||||
const placeholderImage = usePreistraegerPlaceholderImage();
|
||||
const home = (useCmsRoute()?.doc?.home || {}) as HomeCms;
|
||||
const cmsPartners = useCmsCollection('partners') as PartnerLogoData[];
|
||||
const networkPartners = sortedPartners(cmsPartners)
|
||||
@@ -240,19 +171,14 @@ const Home: React.FC = () => {
|
||||
const benefits = home.benefits || {};
|
||||
const videoModal = home.videoModal || {};
|
||||
const applicationPhase = useApplicationPhase();
|
||||
const cmsPreistraeger = useCmsCollection('preistraeger');
|
||||
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 homeWinners = resolveHomeWinners(winnersSection.featured, cmsPreistraeger, WINNERS);
|
||||
const scrollDesktopWinners = !isMobile && homeWinners.length > 8;
|
||||
const scrollWinnerGrid = isMobile || scrollDesktopWinners;
|
||||
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 winnerFallbackImage = placeholderImage;
|
||||
const videoTitle = fallbackText(videoModal.title, homeContent.videoModal.title);
|
||||
const videoSrc = mediaUrl(videoModal.video, '');
|
||||
|
||||
@@ -264,16 +190,6 @@ const Home: React.FC = () => {
|
||||
});
|
||||
}, [showVideo, videoSrc]);
|
||||
|
||||
const scrollWinners = (direction: -1 | 1) => {
|
||||
const container = winnersScrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
container.scrollBy({
|
||||
left: direction * Math.max(container.clientWidth * 0.8, 320),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
{/* Hero Section */}
|
||||
@@ -364,72 +280,7 @@ const Home: React.FC = () => {
|
||||
{/* Status Phase Slider */}
|
||||
<StatusSlider data={applicationPhase} />
|
||||
|
||||
{/* 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>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
|
||||
{scrollDesktopWinners && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Vorherige Preisträger anzeigen"
|
||||
onClick={() => scrollWinners(-1)}
|
||||
style={{ width: 42, height: 42, border: '1px solid rgba(255,255,255,0.28)', background: 'transparent', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Weitere Preisträger anzeigen"
|
||||
onClick={() => scrollWinners(1)}
|
||||
style={{ width: 42, height: 42, border: '1px solid rgba(255,255,255,0.28)', background: 'transparent', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<ChevronRight size={18} />
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<div ref={winnersScrollRef} style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: scrollWinnerGrid ? undefined : 'repeat(4, 1fr)',
|
||||
gridAutoFlow: scrollWinnerGrid ? 'column' : undefined,
|
||||
gridTemplateRows: scrollWinnerGrid ? `repeat(2, ${isMobile ? '1fr' : '280px'})` : undefined,
|
||||
gridAutoColumns: isMobile ? '66vw' : scrollDesktopWinners ? '25vw' : undefined,
|
||||
overflowX: scrollWinnerGrid ? 'auto' : undefined,
|
||||
scrollSnapType: scrollWinnerGrid ? 'x mandatory' : undefined,
|
||||
overscrollBehaviorX: scrollWinnerGrid ? 'contain' : undefined,
|
||||
gap: isMobile ? 8 : 0,
|
||||
padding: isMobile ? '0 16px 8px' : scrollDesktopWinners ? '0 0 12px' : 0,
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: scrollDesktopWinners ? 'thin' : 'none',
|
||||
scrollbarColor: scrollDesktopWinners ? '#EFBF04 rgba(255,255,255,0.14)' : undefined,
|
||||
}}>
|
||||
{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)}
|
||||
horizontalScroll={scrollDesktopWinners}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<HomeWinnersSection content={winnersSection} />
|
||||
|
||||
{/* Schnell-Check Section */}
|
||||
<section style={{ overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
|
||||
Reference in New Issue
Block a user