@@ -13,3 +13,216 @@ listing:
13
13
:::
14
14
15
15
[ See all news &rarr ; ] ( news/ )
16
+
17
+ ``` {=html}
18
+ <style>
19
+ /* Scoped styles for the news carousel to avoid conflicts with global themes */
20
+ #news-carousel-container {
21
+ overflow: hidden;
22
+ position: relative;
23
+ width: 100%;
24
+ }
25
+
26
+ #news-carousel-container #news-carousel-track {
27
+ display: flex;
28
+ align-items: stretch;
29
+ }
30
+
31
+ #news-carousel-container .news-carousel-slide {
32
+ flex-shrink: 0;
33
+ width: 33.3333%;
34
+ padding: 0.5rem;
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ @media (max-width: 1024px) {
39
+ #news-carousel-container .news-carousel-slide {
40
+ width: 50%;
41
+ }
42
+ }
43
+
44
+ @media (max-width: 768px) {
45
+ #news-carousel-container .news-carousel-slide {
46
+ width: 100%;
47
+ }
48
+ }
49
+
50
+ #news-carousel-container .news-carousel-card {
51
+ overflow: hidden;
52
+ display: flex;
53
+ flex-direction: column;
54
+ height: 100%;
55
+ text-decoration: none;
56
+ border-radius: 0.5rem;
57
+ transition: background-color 0.3s ease, border-color 0.3s ease;
58
+ background-color: #f8f9fa;
59
+ border: 1px solid #e9ecef;
60
+ color: #212529;
61
+ }
62
+
63
+ #news-carousel-container .news-carousel-card:hover {
64
+ background-color: #e9ecef;
65
+ border-color: #dee2e6;
66
+ }
67
+
68
+ #news-carousel-container .news-carousel-card-body {
69
+ flex-grow: 1;
70
+ display: flex;
71
+ flex-direction: column;
72
+ padding: 1rem;
73
+ }
74
+
75
+ #news-carousel-container .news-carousel-title {
76
+ white-space: nowrap;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ margin-bottom: 0.25rem;
80
+ color: #212529;
81
+ font-weight: 700;
82
+ }
83
+
84
+ #news-carousel-container .news-carousel-reading-time,
85
+ #news-carousel-container .news-carousel-description,
86
+ #news-carousel-container .news-carousel-attribution {
87
+ color: #6c757d;
88
+ }
89
+
90
+ #news-carousel-container .news-carousel-reading-time {
91
+ font-size: 0.9em;
92
+ margin-bottom: 0.75rem;
93
+ }
94
+
95
+ #news-carousel-container .news-carousel-description {
96
+ flex-grow: 1;
97
+ display: -webkit-box;
98
+ -webkit-box-orient: vertical;
99
+ -webkit-line-clamp: 2;
100
+ line-clamp: 2;
101
+ overflow: hidden;
102
+ text-overflow: ellipsis;
103
+ margin-bottom: 1rem;
104
+ }
105
+
106
+ #news-carousel-container .news-carousel-attribution {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: flex-end;
110
+ gap: 1em;
111
+ font-size: 0.85em;
112
+ margin-top: auto;
113
+ }
114
+
115
+ #news-carousel-container .news-carousel-author {
116
+ white-space: nowrap;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ min-width: 0;
120
+ }
121
+
122
+ #news-carousel-container .news-carousel-date {
123
+ white-space: nowrap;
124
+ flex-shrink: 0;
125
+ }
126
+ </style>
127
+
128
+ <script>
129
+ document.addEventListener('DOMContentLoaded', function () {
130
+ const carouselContainer = document.getElementById('news-carousel-container');
131
+ const carouselTrack = document.getElementById('news-carousel-track');
132
+
133
+ if (!carouselContainer || !carouselTrack || !carouselTrack.children.length) {
134
+ return;
135
+ }
136
+
137
+ const slides = Array.from(carouselTrack.children);
138
+ const displayDuration = 2500;
139
+ let currentTranslate = 0;
140
+ let currentIndex = 0;
141
+ let intervalId;
142
+ let wheelTimeout;
143
+ let isWheeling = false;
144
+
145
+ const getItemsPerView = () => {
146
+ const width = window.innerWidth;
147
+ if (width <= 768) return 1;
148
+ if (width > 768 && width <= 1024) return 2;
149
+ return 3;
150
+ }
151
+
152
+ const startAutoplay = () => {
153
+ stopAutoplay();
154
+ intervalId = setInterval(autoplayNext, displayDuration);
155
+ }
156
+
157
+ const stopAutoplay = () => {
158
+ clearInterval(intervalId);
159
+ }
160
+
161
+ const setSliderPosition = () => {
162
+ carouselTrack.style.transform = `translateX(${currentTranslate}px)`;
163
+ }
164
+
165
+ const setPositionByIndex = () => {
166
+ if (slides.length === 0) return;
167
+
168
+ const itemsPerView = getItemsPerView();
169
+ const maxIndex = slides.length > itemsPerView ? slides.length - itemsPerView : 0;
170
+
171
+ if (currentIndex > maxIndex) currentIndex = maxIndex;
172
+ if (currentIndex < 0) currentIndex = 0;
173
+
174
+ const slideWidth = slides[0].getBoundingClientRect().width;
175
+ currentTranslate = currentIndex * -slideWidth;
176
+
177
+ carouselTrack.style.transition = 'transform 0.4s ease-out';
178
+ setSliderPosition();
179
+ }
180
+
181
+ const autoplayNext = () => {
182
+ if (document.hidden) return;
183
+ const itemsPerView = getItemsPerView();
184
+ const maxIndex = slides.length > itemsPerView ? slides.length - itemsPerView : 0;
185
+
186
+ currentIndex++;
187
+ if (currentIndex > maxIndex) {
188
+ currentIndex = 0;
189
+ }
190
+ setPositionByIndex();
191
+ }
192
+
193
+ const handleWheel = (event) => {
194
+ event.preventDefault();
195
+
196
+ if (isWheeling) return;
197
+ isWheeling = true;
198
+
199
+ stopAutoplay();
200
+
201
+ const itemsPerView = getItemsPerView();
202
+ const maxIndex = slides.length > itemsPerView ? slides.length - itemsPerView : 0;
203
+ const delta = event.deltaY;
204
+
205
+ if (delta > 0) {
206
+ if (currentIndex < maxIndex) currentIndex++;
207
+ } else if (delta < 0) {
208
+ if (currentIndex > 0) currentIndex--;
209
+ }
210
+ setPositionByIndex();
211
+
212
+ clearTimeout(wheelTimeout);
213
+ wheelTimeout = setTimeout(startAutoplay, 300);
214
+
215
+ setTimeout(() => { isWheeling = false; }, 100);
216
+ }
217
+
218
+ carouselContainer.addEventListener('wheel', handleWheel, { passive: false });
219
+ carouselContainer.addEventListener('mouseenter', stopAutoplay);
220
+ carouselContainer.addEventListener('mouseleave', startAutoplay);
221
+ document.addEventListener('visibilitychange', () => document.hidden ? stopAutoplay() : startAutoplay());
222
+ window.addEventListener('resize', setPositionByIndex);
223
+
224
+ setPositionByIndex();
225
+ startAutoplay();
226
+ });
227
+ </script>
228
+ ```
0 commit comments