feat: add scrolling functionality for winners section with navigation buttons

This commit is contained in:
syntaxbullet
2026-07-13 13:52:02 +02:00
parent bd2e99ca91
commit 7668e1464b

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { Button } from '@/spa/components/ui/button';
import { Play, ChevronRight, X, Star } from 'lucide-react';
import { Play, ChevronLeft, 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';
@@ -225,6 +225,7 @@ function HomeWinnerCard({
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;
@@ -259,9 +260,17 @@ const Home: React.FC = () => {
const selectedWinnerDocs = selectedWinners
.map((selected) => resolveCmsRelationship(selected, cmsPreistraeger))
.filter(Boolean)
const eligibleWinners = (cmsPreistraeger.length ? cmsPreistraeger : WINNERS)
.filter((winner) => Number((winner as { year?: string | number }).year) === 2025)
const selectedWinnerIDs = new Set(
selectedWinnerDocs.map((winner) => String((winner as WinnerCardData).id || (winner as WinnerCardData).slug)),
)
const remainingWinners = selectedWinnerDocs.length >= 8
? eligibleWinners.filter((winner) => !selectedWinnerIDs.has(String((winner as WinnerCardData).id || (winner as WinnerCardData).slug)))
: []
const homeWinners = selectedWinnerDocs.length
? selectedWinnerDocs
: (cmsPreistraeger.length ? cmsPreistraeger : WINNERS).filter((w) => Number((w as { year?: string | number }).year) === 2025).slice(0, 8);
? [...selectedWinnerDocs, ...remainingWinners]
: eligibleWinners;
const scrollDesktopWinners = !isMobile && homeWinners.length > 8;
const scrollWinnerGrid = isMobile || scrollDesktopWinners;
const quickCriteria = fallbackArray(quickCheck.criteria, homeContent.quickCheck.criteria);
@@ -281,6 +290,16 @@ 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 */}
@@ -380,15 +399,37 @@ const Home: React.FC = () => {
<Lines text={fallbackText(winnersSection.heading, homeContent.winners.heading)} />
</h2>
</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 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 style={{
<div ref={winnersScrollRef} style={{
display: 'grid',
gridTemplateColumns: scrollWinnerGrid ? undefined : 'repeat(4, 1fr)',
gridAutoFlow: scrollWinnerGrid ? 'column' : undefined,
@@ -398,9 +439,10 @@ const Home: React.FC = () => {
scrollSnapType: scrollWinnerGrid ? 'x mandatory' : undefined,
overscrollBehaviorX: scrollWinnerGrid ? 'contain' : undefined,
gap: isMobile ? 8 : 0,
padding: isMobile ? '0 16px 8px' : 0,
padding: isMobile ? '0 16px 8px' : scrollDesktopWinners ? '0 0 12px' : 0,
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'none',
scrollbarWidth: scrollDesktopWinners ? 'thin' : 'none',
scrollbarColor: scrollDesktopWinners ? '#EFBF04 rgba(255,255,255,0.14)' : undefined,
}}>
{homeWinners.map((w) => (
<HomeWinnerCard
@@ -849,7 +891,7 @@ function StatusSlideCard({
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(2.4rem, 3.8vw, 3.8rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 0.94, margin: '0 0 24px' }}>
{phase.headline}
</h2>
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: 17, color: 'rgba(255,255,255,0.68)', lineHeight: 1.8, marginBottom: 32, maxWidth: 520 }}>
<p style={{ fontFamily: '"Inter", sans-serif', fontSize: 17, color: '#C7CAD1', lineHeight: 1.8, marginBottom: 32, maxWidth: isMobile ? 520 : 760 }}>
{phase.body}
</p>
{phase.cta ? (