1document.addEventListener("DOMContentLoaded", () => { 2 const articleCarousels = document.querySelectorAll('.article-carousel'); 3 4 articleCarousels.forEach(articleCarousel => { 5 const track = articleCarousel.querySelector('.article-carousel__track'); 6 const controlPanel = articleCarousel.querySelector('.article-carousel__controls'); 7 const prevBtn = articleCarousel.querySelector('.article-carousel__prev'); 8 const nextBtn = articleCarousel.querySelector('.article-carousel__next'); 9 10 const figures = track.querySelectorAll('.article-carousel__figure'); 11 const hardcodedGap = 40; // must match your CSS gap 12 const cardStep = figures[0] ? figures[0].offsetWidth + hardcodedGap : 0; 13 14 // Slide counter elements 15 const currentIdxEl = articleCarousel.querySelector('#article-carousel__slide-counter__current-idx'); 16 const totalFiguresEl = articleCarousel.querySelector('#article-carousel__slide-counter__total-figures'); 17 if (totalFiguresEl) totalFiguresEl.textContent = figures.length; 18 19 20 // if (figures.length == 0) { 21 // // console.log("figures", figures.length, articleCarousel); 22 // articleCarousel.style.display = 'none'; 23 // } else 24 if (figures.length == 1 || figures.length == 0) { 25 controlPanel.style.display = 'none'; 26 } 27 28 // Scroll function for buttons 29 function scrollTrack(direction) { 30 if (!cardStep) return; 31 track.scrollBy({ 32 left: direction * cardStep, 33 behavior: 'smooth' 34 }); 35 } 36 37 38 39 // Update buttons and slide counter 40 function updateUI() { 41 const scrollLeft = track.scrollLeft; 42 const maxScrollLeft = track.scrollWidth - track.clientWidth; 43 const epsilon = 2; 44 45 // Buttons 46 if (prevBtn) prevBtn.classList.toggle('inactive', scrollLeft <= epsilon); 47 if (nextBtn) nextBtn.classList.toggle('inactive', scrollLeft >= maxScrollLeft - epsilon); 48 49 // Current slide index 50 const index = Math.round(scrollLeft / cardStep); 51 const currentFigure = figures[index]; 52 if (currentIdxEl) currentIdxEl.textContent = index + 1; 53 54 // Caption update 55 const captionEl = articleCarousel.querySelector('#article-carousel__caption'); 56 if (!captionEl) return; 57 58 const newCaption = currentFigure?.querySelector('img')?.dataset.caption || ''; 59 const oldCaption = captionEl.textContent.trim(); 60 61 if (newCaption === oldCaption) return; // no change, skip 62 63 // Animate out old caption 64 // animateOut(captionEl); 65 66 // After the out animation, swap text + animate in 67 setTimeout(() => { 68 captionEl.textContent = newCaption; 69 70 // Reinitialize SplitText lines for new caption 71 const split = new SplitText(captionEl, { 72 type: "lines", 73 linesClass: "line-reveal", 74 }); 75 const splitInner = new SplitText(split.lines, { 76 type: "lines", 77 linesClass: "line-reveal-inner", 78 }); 79 80 // Animate in new caption 81 console.log("captionEl", captionEl); 82 animateIn(captionEl); 83 }, 200); // matches your animateOut duration 84 } 85 86 87 // Recursive scroll stop function 88 function waitForScrollStop(track, callback, stableFrames = 20, snapDelay = 50) { 89 let lastScrollLeft = track.scrollLeft; 90 let stableCount = 0; 91 92 function check() { 93 const currentScrollLeft = track.scrollLeft; 94 if (currentScrollLeft === lastScrollLeft) { 95 stableCount++; 96 } else { 97 stableCount = 0; 98 lastScrollLeft = currentScrollLeft; 99 } 100 101 if (stableCount >= stableFrames) { 102 // Small delay to let scroll snap finish 103 setTimeout(callback, snapDelay); 104 } else { 105 requestAnimationFrame(check); 106 } 107 } 108 109 check(); 110 } 111 112 113 // Debounced scroll update (fires after user stops scrolling) 114 let isScrolling; 115 track.addEventListener('scroll', () => { 116 const captionEl = articleCarousel.querySelector('#article-carou
116sel__caption'); 117 if (!captionEl) return; 118 119 if (!captionEl.classList.contains('animating')) { 120 animateOut(captionEl); 121 captionEl.classList.add('animating'); 122 } 123 124 waitForScrollStop(track, () => { 125 updateUI(); // updates caption + animates in 126 captionEl.classList.remove('animating'); 127 }); 128 }); 129 130 // Button click events 131 // if (prevBtn) prevBtn.addEventListener('click', () => scrollTrack(-1)); 132 // if (nextBtn) nextBtn.addEventListener('click', () => scrollTrack(1)); 133 134 prevBtn.addEventListener('click', () => { 135 const captionEl = articleCarousel.querySelector('#article-carousel__caption'); 136 if (captionEl) { 137 animateOut(captionEl); 138 captionEl.classList.add('animating'); 139 } 140 scrollTrack(-1); 141 142 // Wait a minimal time for scroll to reach target, then animate in 143 setTimeout(() => { 144 updateUI(); 145 if (captionEl) captionEl.classList.remove('animating'); 146 }, 100); // smaller than animateOut duration 147 }); 148 149 nextBtn.addEventListener('click', () => { 150 const captionEl = articleCarousel.querySelector('#article-carousel__caption'); 151 if (captionEl) { 152 animateOut(captionEl); 153 captionEl.classList.add('animating'); 154 } 155 scrollTrack(1); 156 157 setTimeout(() => { 158 updateUI(); 159 if (captionEl) captionEl.classList.remove('animating'); 160 }, 100); 161 }); 162 163 164 // Initial update 165 updateUI(); 166 167 // Resize logic 168 let resizeTimeout; 169 170 window.addEventListener('resize', () => { 171 clearTimeout(resizeTimeout); 172 resizeTimeout = setTimeout(() => { 173 updateUI(); 174 }, 250); // Adjust delay as needed 175 }); 176 }); 177 178 179 180 181}); 182
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.