@@ -18,6 +18,15 @@ const featuredSpeakers = getRandomSpeakers(validSpeakers, 15);
1818
1919const sectionTitle = " Featured Speakers" ;
2020const sectionSubtitle = " Meet some of our amazing speakers" ;
21+
22+ // Helper function to truncate names if needed
23+ function formatSpeakerName(name : string ) {
24+ // Option: You could truncate very long names
25+ // if (name.length > 20) {
26+ // return name.substring(0, 18) + '...';
27+ // }
28+ return name ;
29+ }
2130---
2231
2332<section class =" py-16 px-4 bg-gray-50" >
@@ -43,10 +52,18 @@ const sectionSubtitle = "Meet some of our amazing speakers";
4352 loading = " eager"
4453 />
4554 </div >
46- <div class = " p-4 text-center" >
47- <h3 class = " font-bold text-lg text-gray-900 group-hover:text-primary transition-colors" >
48- { speaker .data .name }
55+ <div class = " p-4 text-center h-[88px] flex flex-col justify-center" >
56+ <!-- Solution: Use text-ellipsis with 2 lines max and remove whitespace-nowrap -->
57+ <h3 class = " font-bold text-lg text-gray-900 group-hover:text-primary transition-colors line-clamp-2" >
58+ { formatSpeakerName (speaker .data .name )}
4959 </h3 >
60+
61+ <!-- Optional: Add role/title with ellipsis if available -->
62+ { speaker .data .title && (
63+ <p class = " text-sm text-gray-600 mt-1 line-clamp-1" >
64+ { speaker .data .title }
65+ </p >
66+ )}
5067 </div >
5168 </div >
5269 </a >
@@ -70,30 +87,52 @@ const sectionSubtitle = "Meet some of our amazing speakers";
7087 document.addEventListener('DOMContentLoaded', () => {
7188 const track = document.querySelector('.speakers-track') as HTMLElement;
7289 const slides = document.querySelectorAll('.speaker-slide');
90+ const totalOriginalSlides = slides.length / 2;
7391
7492 if (!track || slides.length === 0) return;
7593
76- const slideWidth = 100 / 5;
77- const totalOriginalSlides = slides.length / 2;
7894 let currentPosition = 0;
7995 const scrollSpeed = 4000;
96+ let slidingInterval: ReturnType<typeof setInterval> | null = null;
97+
98+ // Function to determine slides per view based on window width
99+ function getSlidesPerView() {
100+ const width = window.innerWidth;
101+ if (width <= 480) return 1;
102+ if (width <= 640) return 2;
103+ if (width <= 768) return 3;
104+ if (width <= 1024) return 4;
105+ return 5;
106+ }
80107
81- slides.forEach((slide) => {
82- (slide as HTMLElement).style.flexBasis = `${slideWidth}%` ;
83- }) ;
108+ function updateCarouselSettings() {
109+ const slidesPerView = getSlidesPerView() ;
110+ const slideWidth = 100 / slidesPerView ;
84111
85- startAnimation();
112+ // Reset position when breakpoint changes
113+ currentPosition = 0;
114+ track.style.transition = 'none';
115+ track.style.transform = `translateX(0)`;
86116
87- function startAnimation() {
117+ // Clear and restart animation
118+ if (slidingInterval !== null) {
119+ clearInterval(slidingInterval);
120+ }
121+ startAnimation(slideWidth);
122+ }
123+
124+ function startAnimation(slideWidth: number) {
125+ // Initial setup
88126 moveCarousel();
89- let slidingInterval = setInterval(moveCarousel, scrollSpeed);
127+ slidingInterval = setInterval(moveCarousel, scrollSpeed);
90128
91129 function moveCarousel() {
92130 currentPosition += slideWidth;
93131
94132 track.style.transition = 'transform 1000ms linear';
95133 track.style.transform = `translateX(-${currentPosition}%)`;
96134
135+ // Reset when we've scrolled through the original set of slides
97136 if (currentPosition >= slideWidth * totalOriginalSlides) {
98137 setTimeout(() => {
99138 track.style.transition = 'none';
@@ -103,27 +142,43 @@ const sectionSubtitle = "Meet some of our amazing speakers";
103142 setTimeout(() => {
104143 track.style.transition = 'transform 1000ms linear';
105144 }, 20);
106- }, 4000 );
145+ }, 1000 );
107146 }
108147 }
148+ }
149+
150+ // Start the carousel
151+ updateCarouselSettings();
109152
110- const carouselContainer = document.querySelector('.speakers-carousel-container');
111- carouselContainer?.addEventListener('mouseenter', () => {
153+ // Update on window resize
154+ window.addEventListener('resize', () => {
155+ updateCarouselSettings();
156+ });
157+
158+ // Pause on hover
159+ const carouselContainer = document.querySelector('.speakers-carousel-container');
160+ carouselContainer?.addEventListener('mouseenter', () => {
161+ if (slidingInterval !== null) {
112162 clearInterval(slidingInterval);
113- });
163+ slidingInterval = null;
164+ }
165+ });
114166
115- carouselContainer?.addEventListener('mouseleave', () => {
116- slidingInterval = setInterval(moveCarousel, scrollSpeed );
117- });
167+ carouselContainer?.addEventListener('mouseleave', () => {
168+ updateCarouselSettings( );
169+ });
118170
119- document.addEventListener('visibilitychange', () => {
120- if (document.hidden) {
171+ // Pause when tab is not visible
172+ document.addEventListener('visibilitychange', () => {
173+ if (document.hidden) {
174+ if (slidingInterval !== null) {
121175 clearInterval(slidingInterval);
122- } else {
123- slidingInterval = setInterval(moveCarousel, scrollSpeed);
176+ slidingInterval = null;
124177 }
125- });
126- }
178+ } else {
179+ updateCarouselSettings();
180+ }
181+ });
127182 });
128183</script >
129184
@@ -137,6 +192,22 @@ const sectionSubtitle = "Meet some of our amazing speakers";
137192 will-change: transform;
138193 }
139194
195+ /* Line clamping for text overflow */
196+ .line-clamp-1 {
197+ display: -webkit-box;
198+ -webkit-line-clamp: 1;
199+ -webkit-box-orient: vertical;
200+ overflow: hidden;
201+ }
202+
203+ .line-clamp-2 {
204+ display: -webkit-box;
205+ -webkit-line-clamp: 2;
206+ -webkit-box-orient: vertical;
207+ overflow: hidden;
208+ }
209+
210+ /* These styles match the responsive classes used in the HTML */
140211 @media (max-width: 1024px) {
141212 .speaker-slide {
142213 width: 25%;
0 commit comments