Skip to content

Commit e792d47

Browse files
authored
Merge branch 'ep2025' into fix-commit-hash-preview
2 parents f27ad3c + 7917897 commit e792d47

File tree

6 files changed

+352
-98
lines changed

6 files changed

+352
-98
lines changed

src/components/keynoters/keynoter.astro

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import type { ImageMetadata } from "astro";
3+
import { getCollection } from "astro:content";
34
import { Image } from "astro:assets";
45
import Person1 from "../placeholders/person-1.astro";
56
import Person2 from "../placeholders/person-2.astro";
@@ -19,6 +20,7 @@ export interface Props {
1920
2021
const {
2122
name,
23+
slug,
2224
tagline,
2325
image,
2426
placeholder,
@@ -30,10 +32,12 @@ const placeholders = [Person1, Person2, Person3, Person4, Person5];
3032
const Placeholder =
3133
placeholders[Math.floor(Math.random() * placeholders.length)];
3234
35+
const entries = await getCollection("speakers");
36+
const isSpeaker = entries.some((entry) => entry.id === slug);
3337
---
3438

3539
<li class:list={["list-none rounded-2xl overflow-clip flex group", className]}>
36-
<a class="block w-full h-full relative " >
40+
<div class="block w-full h-full relative">
3741
<div class="relative aspect-[9/10] overflow-clip">
3842
{
3943
image ? (
@@ -51,21 +55,30 @@ const Placeholder =
5155
}
5256
</div>
5357

54-
<div
55-
class="px-5 py-2 pb-4 bg-keynoter-info rounded-t-none h-full "
56-
>
58+
<div class="px-5 py-2 pb-4 bg-keynoter-info rounded-t-none h-full">
5759
{
5860
placeholder ? (
59-
<p class="text-body-inverted ">More keynoters coming soon</p>
61+
<p class="text-body-inverted">More keynoters coming soon</p>
6062
) : (
6163
<>
62-
<p class="text-[#17223A] font-bold">{name}</p>
63-
{tagline ? (
64+
{isSpeaker ? (
65+
<p class="text-[#17223A] font-bold">
66+
<a
67+
href={`/speaker/${slug}`}
68+
class="text-[#17223A] font-bold hover:underline"
69+
>
70+
{name}
71+
</a>
72+
</p>
73+
) : (
74+
<p class="text-[#17223A] font-bold">{name}</p>
75+
)}
76+
{tagline && (
6477
<p class="text-secondary-hover text-lg italic">{tagline}</p>
65-
) : null}
78+
)}
6679
</>
6780
)
6881
}
6982
</div>
70-
</a>
83+
</div>
7184
</li>
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/components/sessions/filter.astro

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { Select } from "../form/select";
1212

1313
<form class="mb-12 filter-sessions">
1414
<div class="w-full mb-6">
15-
<Label htmlFor="search">Search</Label>
15+
<Label htmlFor="sessionSearch">Search</Label>
1616
<input
1717
type="text"
18-
id="search"
18+
id="sessionSearch"
1919
name="search"
2020
class="block w-full bg-transparent text-lg h-16 py-2 pr-16 pl-4 border-[3px] border-primary appearance-none focus:outline-none focus:border-black focus-visible:bg-white"
2121
placeholder="Search sessions..."
@@ -101,7 +101,7 @@ import { Select } from "../form/select";
101101
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
102102
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
103103
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
104-
const searchInput = form.querySelector<HTMLInputElement>("#search");
104+
const searchInput = form.querySelector<HTMLInputElement>("#sessionSearch");
105105

106106
if (trackSelect) trackSelect.value = track;
107107
if (typeSelect) typeSelect.value = type;
@@ -130,7 +130,7 @@ import { Select } from "../form/select";
130130
const trackSelect = form.querySelector<HTMLSelectElement>("#track");
131131
const typeSelect = form.querySelector<HTMLSelectElement>("#type");
132132
const levelSelect = form.querySelector<HTMLSelectElement>("#level");
133-
const searchInput = form.querySelector<HTMLInputElement>("#search");
133+
const searchInput = form.querySelector<HTMLInputElement>("#sessionSearch");
134134

135135
const track = trackSelect?.value || "";
136136
const type = typeSelect?.value || "";
@@ -190,7 +190,7 @@ import { Select } from "../form/select";
190190
select.addEventListener("change", filterSessions);
191191
});
192192

193-
const searchInput = form.querySelector<HTMLInputElement>("#search");
193+
const searchInput = form.querySelector<HTMLInputElement>("#sessionSearch");
194194
if (searchInput) {
195195
searchInput.addEventListener("input", filterSessions);
196196
}

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 />

0 commit comments

Comments
 (0)