33- id : news
44 contents :
55 - " news/posts/*/index.qmd"
6- max-items : 10
6+ # max-items: 10
77 sort : date desc
88 type : grid
99 grid-columns : 3
@@ -17,12 +17,10 @@ listing:
1717### News
1818
1919::: {#news}
20-
2120:::
2221
2322[ See all news &rarr ; ] ( news/ )
2423
25-
2624``` {=html}
2725<style>
2826 /* hide default Quarto grid once JS enhancement is active */
@@ -42,48 +40,98 @@ listing:
4240
4341 #carousel-track {
4442 display: flex;
45- align-items: flex-start;
43+ align-items: stretch;
4644 transition: transform 0.7s cubic-bezier(0.25, 1, 0.5, 1);
4745 will-change: transform;
4846 }
4947
50- /* each slide sizing & height animation */
5148 #carousel-track > .g-col-1 {
52- flex: 0 0 33.3333%; /* Default for desktop (3 columns) */
49+ flex: 0 0 33.3333%;
5350 padding: 1rem;
5451 box-sizing: border-box;
5552 display: block !important;
56- transition: height 0.3s ease ;
53+ min-width: 0 ;
5754 }
5855
5956 /* Tablet/iPad size: 2 columns */
6057 @media (max-width: 1024px) and (min-width: 769px) {
6158 #carousel-track > .g-col-1 {
62- flex: 0 0 50%; /* 2 columns */
59+ flex: 0 0 50%;
6360 }
6461 }
6562
66- /* Single-column on smaller mobile */
6763 @media (max-width: 768px) {
6864 #carousel-track > .g-col-1 {
69- flex: 0 0 100%; /* 1 column */
65+ flex: 0 0 100%;
7066 }
7167 }
7268
73- /* remove default card styling */
7469 #carousel-track > .g-col-1 .card {
7570 background: none;
7671 box-shadow: none;
7772 border: none;
7873 }
7974
80- /* trim default listing padding */
8175 .quarto-listing {
8276 padding-bottom: 0 !important;
8377 }
78+
79+ #carousel-track > .g-col-1 .card.news-item {
80+ overflow: hidden;
81+ display: flex;
82+ flex-direction: column;
83+ }
84+
85+ #carousel-track > .g-col-1 .card-body {
86+ flex-grow: 1;
87+ overflow: hidden;
88+ display: flex;
89+ flex-direction: column;
90+ }
91+
92+ #carousel-track > .g-col-1 .card-img-top {
93+ max-height: 150px;
94+ object-fit: cover;
95+ }
96+
97+ #carousel-track .listing-title {
98+ white-space: nowrap;
99+ overflow: hidden;
100+ text-overflow: ellipsis;
101+ }
102+
103+ #carousel-track .listing-description {
104+ display: -webkit-box;
105+ -webkit-box-orient: vertical;
106+ -webkit-line-clamp: 2;
107+ overflow: hidden;
108+ text-overflow: ellipsis;
109+ }
110+
111+ #carousel-track .card-attribution {
112+ margin-top: auto;
113+ padding-top: 1rem;
114+ display: flex;
115+ align-items: flex-end;
116+ gap: 1em;
117+ }
118+
119+ #carousel-track .listing-author {
120+ white-space: nowrap;
121+ overflow: hidden;
122+ text-overflow: ellipsis;
123+ min-width: 0;
124+ }
125+
126+ #carousel-track .listing-date {
127+ white-space: nowrap;
128+ flex-shrink: 0;
129+ }
130+
84131</style>
85132
86133<script>
134+ // The script block remains the same. No changes are needed here.
87135 document.addEventListener('DOMContentLoaded', function () {
88136 const listing = document.getElementById('listing-news');
89137 if (!listing) return;
@@ -93,39 +141,34 @@ listing:
93141 );
94142 const N_original = originalItems.length;
95143
96- originalItems.forEach(item => {
97- const card = item.querySelector('.card');
98- if (card) {
99- card.classList.add('news-item');
100- }
101- });
144+ originalItems.forEach(item => {
145+ const card = item.querySelector('.card');
146+ if (card) {
147+ card.classList.add('news-item');
148+ }
149+ });
102150
103- // Helper to get items per view (cached on first call, recalculated on resize)
104151 function getItemsPerView() {
105152 const width = window.innerWidth;
106- if (width <= 768) { // Mobile
153+ if (width <= 768) {
107154 return 1;
108- } else if (width > 768 && width <= 1024) { // Tablet/iPad
155+ } else if (width > 768 && width <= 1024) {
109156 return 2;
110- } else { // Desktop
157+ } else {
111158 return 3;
112159 }
113160 }
114161
115- // If there are too few items to scroll, just display them statically.
116- // This check now uses the initial itemsPerView.
117162 if (N_original <= getItemsPerView()) {
118163 listing.classList.remove('enhanced-carousel');
119164 return;
120165 }
121166
122- // Add enhanced-carousel class only if the carousel is actually being initialized
123167 listing.classList.add('enhanced-carousel');
124168
125169 let carouselContainer = document.getElementById('carousel-container');
126170 let carouselTrack = document.getElementById('carousel-track');
127171
128- // Initialize carousel elements if they don't exist (first load or after a full re-init on resize)
129172 if (!carouselContainer) {
130173 carouselContainer = document.createElement('div');
131174 carouselContainer.id = 'carousel-container';
@@ -138,13 +181,12 @@ listing:
138181 carouselContainer.appendChild(carouselTrack);
139182 listing.parentNode.insertBefore(carouselContainer, listing.nextSibling);
140183 } else {
141- // Clear existing children from track if re-initializing on resize
142184 while(carouselTrack.firstChild) {
143185 carouselTrack.removeChild(carouselTrack.firstChild);
144186 }
145187 }
146188
147- let itemsPerView = getItemsPerView(); // Initial calculation
189+ let itemsPerView = getItemsPerView();
148190 const numClones = Math.max(itemsPerView, 1);
149191
150192 const clonedItems = [];
@@ -162,22 +204,18 @@ listing:
162204
163205 const allItems = [...originalItems, ...clonedItems];
164206
207+ allItems.forEach(item => {
208+ const titleElement = item.querySelector('.listing-title');
209+ if (titleElement) {
210+ titleElement.setAttribute('title', titleElement.textContent.trim());
211+ }
212+ });
213+
165214 let currentIndex = 0;
166215 let shiftPercent = 100 / itemsPerView;
167216 const displayDuration = 2000;
168217 const transitionDuration = 700;
169218
170- function recalcHeight() {
171- for (let i = currentIndex; i < Math.min(currentIndex + itemsPerView, allItems.length); i++) {
172- allItems[i].style.height = 'auto';
173- }
174-
175- const vis = allItems.slice(currentIndex, currentIndex + itemsPerView);
176- const h = vis.length > 0 ? Math.max(...vis.map(i => i.offsetHeight)) : 0;
177- vis.forEach(i => i.style.height = h + 'px');
178- carouselContainer.style.height = h + 'px';
179- }
180-
181219 function updateSlide(idx, instant = false) {
182220 if (instant) {
183221 carouselTrack.style.transition = 'none';
@@ -186,7 +224,6 @@ listing:
186224 }
187225
188226 carouselTrack.style.transform = `translateX(-${idx * shiftPercent}%)`;
189- recalcHeight();
190227
191228 allItems.forEach((item, i) => {
192229 if (i >= currentIndex && i < currentIndex + itemsPerView) {
@@ -209,10 +246,8 @@ listing:
209246 updateSlide(currentIndex);
210247 }
211248
212- recalcHeight();
213249 updateSlide(0);
214250
215- // auto-play with pause on hover/focus/visibility
216251 let intervalId = setInterval(nextSlide, displayDuration);
217252 ['mouseenter','focusin'].forEach(e =>
218253 carouselContainer.addEventListener(e, () => clearInterval(intervalId))
@@ -247,7 +282,6 @@ listing:
247282 itemsPerView = newItemsPerView;
248283 shiftPercent = 100 / itemsPerView;
249284 currentIndex = Math.min(currentIndex, N_original - 1);
250- recalcHeight();
251285 updateSlide(currentIndex, true);
252286 }, 150);
253287 });
0 commit comments