Skip to content

Commit 5304e33

Browse files
authored
Merge branch 'ep2025' into ep2025-issue-1126
2 parents 6a36e43 + 2f30f4c commit 5304e33

File tree

4 files changed

+325
-84
lines changed

4 files changed

+325
-84
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import type { CollectionEntry } from "astro:content";
4+
import { Image } from "astro:assets";
5+
6+
const allSpeakers = await getCollection("speakers");
7+
8+
const validSpeakers = allSpeakers.filter(speaker =>
9+
!!speaker.data?.name && !!speaker.data?.avatar
10+
);
11+
12+
function getRandomSpeakers(speakers: CollectionEntry<"speakers">[], count: number) {
13+
const shuffled = [...speakers].sort(() => 0.5 - Math.random());
14+
return shuffled.slice(0, Math.min(count, speakers.length));
15+
}
16+
17+
const featuredSpeakers = getRandomSpeakers(validSpeakers, 15);
18+
19+
const sectionTitle = "Featured Speakers";
20+
const sectionSubtitle = "Meet some of our amazing speakers";
21+
22+
---
23+
24+
<section class="py-16 px-4 bg-gray-50">
25+
<div class="container mx-auto">
26+
<div class="text-center mb-12">
27+
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">{sectionTitle}</h2>
28+
<p class="text-lg text-gray-600">{sectionSubtitle}</p>
29+
</div>
30+
31+
<div class="speakers-carousel-container relative overflow-hidden">
32+
<div class="speakers-track flex transition-transform duration-1000 ease-linear">
33+
{[...featuredSpeakers, ...featuredSpeakers].map((speaker, _index) => (
34+
<div class="speaker-slide w-full sm:w-1/2 md:w-1/3 lg:w-1/5 flex-shrink-0 px-3">
35+
<a href={`/speaker/${speaker.id}`} class="block group">
36+
<div class="bg-white rounded-lg shadow-md overflow-hidden transition-transform duration-300 group-hover:-translate-y-2">
37+
<div class="aspect-square overflow-hidden">
38+
<Image
39+
src={speaker.data.avatar}
40+
alt={`${speaker.data.name}'s profile picture`}
41+
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
42+
height={250}
43+
width={250}
44+
loading="eager"
45+
/>
46+
</div>
47+
<div class="p-4 text-center h-[88px] flex flex-col justify-center">
48+
<!-- Solution: Use text-ellipsis with 2 lines max and remove whitespace-nowrap -->
49+
<h3 class="font-bold text-lg text-gray-900 group-hover:text-primary transition-colors line-clamp-2">
50+
{speaker.data.name}
51+
</h3>
52+
</div>
53+
</div>
54+
</a>
55+
</div>
56+
))}
57+
</div>
58+
</div>
59+
60+
<div class="text-center mt-10">
61+
<a
62+
href="/speakers"
63+
class="inline-block px-6 py-3 bg-primary hover:bg-primary-dark text-white font-medium rounded-md transition-colors"
64+
>
65+
See all speakers
66+
</a>
67+
</div>
68+
</div>
69+
</section>
70+
71+
<script>
72+
document.addEventListener('DOMContentLoaded', () => {
73+
const track = document.querySelector('.speakers-track') as HTMLElement;
74+
const slides = document.querySelectorAll('.speaker-slide');
75+
const totalOriginalSlides = slides.length / 2;
76+
77+
if (!track || slides.length === 0) return;
78+
79+
let currentPosition = 0;
80+
const scrollSpeed = 4000;
81+
let slidingInterval: ReturnType<typeof setInterval> | null = null;
82+
83+
// Function to determine slides per view based on window width
84+
function getSlidesPerView() {
85+
const width = window.innerWidth;
86+
if (width <= 480) return 1;
87+
if (width <= 640) return 2;
88+
if (width <= 768) return 3;
89+
if (width <= 1024) return 4;
90+
return 5;
91+
}
92+
93+
function updateCarouselSettings() {
94+
const slidesPerView = getSlidesPerView();
95+
const slideWidth = 100 / slidesPerView;
96+
97+
// Reset position when breakpoint changes
98+
currentPosition = 0;
99+
track.style.transition = 'none';
100+
track.style.transform = `translateX(0)`;
101+
102+
// Clear and restart animation
103+
if (slidingInterval !== null) {
104+
clearInterval(slidingInterval);
105+
}
106+
startAnimation(slideWidth);
107+
}
108+
109+
function startAnimation(slideWidth: number) {
110+
// Initial setup
111+
moveCarousel();
112+
slidingInterval = setInterval(moveCarousel, scrollSpeed);
113+
114+
function moveCarousel() {
115+
currentPosition += slideWidth;
116+
117+
track.style.transition = 'transform 1000ms linear';
118+
track.style.transform = `translateX(-${currentPosition}%)`;
119+
120+
// Reset when we've scrolled through the original set of slides
121+
if (currentPosition >= slideWidth * totalOriginalSlides) {
122+
setTimeout(() => {
123+
track.style.transition = 'none';
124+
currentPosition = 0;
125+
track.style.transform = `translateX(0)`;
126+
127+
setTimeout(() => {
128+
track.style.transition = 'transform 1000ms linear';
129+
}, 20);
130+
}, 1000);
131+
}
132+
}
133+
}
134+
135+
// Start the carousel
136+
updateCarouselSettings();
137+
138+
// Update on window resize
139+
window.addEventListener('resize', () => {
140+
updateCarouselSettings();
141+
});
142+
143+
// Pause on hover
144+
const carouselContainer = document.querySelector('.speakers-carousel-container');
145+
carouselContainer?.addEventListener('mouseenter', () => {
146+
if (slidingInterval !== null) {
147+
clearInterval(slidingInterval);
148+
slidingInterval = null;
149+
}
150+
});
151+
152+
carouselContainer?.addEventListener('mouseleave', () => {
153+
updateCarouselSettings();
154+
});
155+
156+
// Pause when tab is not visible
157+
document.addEventListener('visibilitychange', () => {
158+
if (document.hidden) {
159+
if (slidingInterval !== null) {
160+
clearInterval(slidingInterval);
161+
slidingInterval = null;
162+
}
163+
} else {
164+
updateCarouselSettings();
165+
}
166+
});
167+
});
168+
</script>
169+
170+
<style>
171+
.speakers-carousel-container {
172+
position: relative;
173+
width: 100%;
174+
}
175+
176+
.speakers-track {
177+
will-change: transform;
178+
}
179+
180+
/* Line clamping for text overflow */
181+
.line-clamp-1 {
182+
display: -webkit-box;
183+
-webkit-line-clamp: 1;
184+
-webkit-box-orient: vertical;
185+
overflow: hidden;
186+
}
187+
188+
.line-clamp-2 {
189+
display: -webkit-box;
190+
-webkit-line-clamp: 2;
191+
-webkit-box-orient: vertical;
192+
overflow: hidden;
193+
}
194+
195+
/* These styles match the responsive classes used in the HTML */
196+
@media (max-width: 1024px) {
197+
.speaker-slide {
198+
width: 25%;
199+
}
200+
}
201+
202+
@media (max-width: 768px) {
203+
.speaker-slide {
204+
width: 33.333%;
205+
}
206+
}
207+
208+
@media (max-width: 640px) {
209+
.speaker-slide {
210+
width: 50%;
211+
}
212+
}
213+
214+
@media (max-width: 480px) {
215+
.speaker-slide {
216+
width: 100%;
217+
}
218+
}
219+
</style>

src/pages/index.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Updates from "@sections/updates.astro";
88
import Prague from "@sections/prague.astro";
99
import Sponsors from "@components/sponsors/sponsors.astro";
1010
import Subscribe from "@sections/subscribe.astro";
11+
import Speakers from "@sections/speakers.astro";
1112
1213
let deadlines = await getCollection("deadlines");
1314
deadlines = deadlines
@@ -23,6 +24,7 @@ deadlines = deadlines
2324
<Hero />
2425
<Updates />
2526
<Keynoters />
27+
<Speakers />
2628
<Prague />
2729
<Sponsors />
2830
<Subscribe />

src/pages/session/[slug].astro

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const speakers = await getEntries(entry.data.speakers);
2525
2626
// Resolve session codes to session data
2727
const resolveSessions = (codes: string[]) =>
28-
codes.map((code) => sessions.find((s) => s?.data?.code === code));
28+
codes.map(code => sessions.find(s => s.data.code === code)!);
2929
3030
// Filter out sessions with room "Exhibit Hall"
3131
const filteredSessions = sessions.filter(
@@ -74,7 +74,7 @@ const nextSessionsOrdered = sameRoomNextSession
7474
<dd class="capitalize">{entry.data.session_type}</dd>
7575
<dt class="font-bold">Level:</dt>
7676
<dd class="capitalize">{entry.data.level}</dd>
77-
<!-- {
77+
{
7878
entry.data.room && (
7979
<>
8080
<dt class="font-bold">Room:</dt>
@@ -95,7 +95,7 @@ const nextSessionsOrdered = sameRoomNextSession
9595
</dd>
9696
</>
9797
)
98-
} -->
98+
}
9999
<dt class="font-bold">Duration:</dt>
100100
<dd>{entry.data.duration} minutes</dd>
101101
</dl>
@@ -148,8 +148,8 @@ const nextSessionsOrdered = sameRoomNextSession
148148
The speaker{speakers.length > 1 ? "s" : ""}
149149
</h2>
150150
{speakers.map((speaker) => (
151-
<div class="md:grid grid-cols-[260px_1fr] md:gap-6">
152-
{speaker.data.avatar ? (
151+
<div class={speaker.data.avatar ? "md:grid grid-cols-[260px_1fr] md:gap-6" : ""}>
152+
{speaker.data.avatar && (
153153
<div class="flex items-start">
154154
<div class="border-4 border-white rounded-lg shadow-lg inline-block mb-10">
155155
<Picture
@@ -161,10 +161,6 @@ const nextSessionsOrdered = sameRoomNextSession
161161
/>
162162
</div>
163163
</div>
164-
) : (
165-
<div class="flex items-start mb-4 invisible">
166-
<img src="" alt="" class="w-full max-w-sm mb-12" />
167-
</div>
168164
)}
169165
<div>
170166
<p class="mb-4">
@@ -185,7 +181,7 @@ const nextSessionsOrdered = sameRoomNextSession
185181
)
186182
}
187183

188-
<!-- {
184+
{
189185
(parallelSessions?.length ?? 0) > 0 ||
190186
(nextSessionsInAllRooms?.length ?? 0) > 0 ? (
191187
<>
@@ -232,7 +228,7 @@ const nextSessionsOrdered = sameRoomNextSession
232228
</div>
233229
</>
234230
) : null
235-
} -->
231+
}
236232
</footer>
237233
</article>
238234
</Layout>

0 commit comments

Comments
 (0)