PageSourceSearch

http://verticalurbanism.org/wp-content/themes/mds-multiple-theme/assets/js/article-carousel.js

js verticalurbanism.org collected 2026-09-25 03:55:21 UTC 6,597 bytes, 182 lines download raw bytes

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.