1/** 2 * Taxonomy Responsive Slider - Slick Style Navigation 3 * Works with existing category grid structure, moves 1 item at a time 4 * Activates only below 992px with Slick-style dots navigation 5 */ 6 7document.addEventListener("DOMContentLoaded", function () { 8 // Only run on screens smaller than 992px 9 function initTaxonomySliders() { 10 if (window.innerWidth >= 992) { 11 return; // Don't initialize on desktop 12 } 13 14 const wrappers = document.querySelectorAll('.taxonomy-slider-wrapper:not(.no-mobile-carousel)'); 15 16 wrappers.forEach(function(wrapper) { 17 // Skip if wrapper is hidden by Elementor responsive settings 18 if (wrapper.offsetParent === null || wrapper.offsetWidth === 0 || wrapper.offsetHeight === 0) { 19 return; 20 } 21 22 const slider = wrapper.querySelector('.taxonomy-responsive-slider'); 23 const controlsContainer = wrapper.querySelector('.taxonomy-slider-controls-container'); 24 25 if (!slider || !controlsContainer) { 26 return; 27 } 28 29 const prevButton = controlsContainer.querySelector('.taxonomy-slide-prev'); 30 const nextButton = controlsContainer.querySelector('.taxonomy-slide-next'); 31 const dotsContainer = controlsContainer.querySelector('.taxonomy-dots'); 32 const items = slider.querySelectorAll('.category-small-box, .category-small-box-alt'); 33 34 if (!prevButton || !nextButton || !dotsContainer || items.length === 0) { 35 return; 36 } 37 38 let currentPosition = 0; 39 40 // Calculate actual item width dynamically (responsive width + gap) 41 function getItemWidth() { 42 if (items.length > 0) { 43 const itemRect = items[0].getBoundingClientRect(); 44 const itemStyle = window.getComputedStyle(items[0]); 45 const marginRight = parseFloat(itemStyle.marginRight) || 0; 46 return itemRect.width + marginRight + 20; // Adding gap 47 } 48 // Fallback calculation based on screen width 49 if (window.innerWidth <= 480) { 50 return (window.innerWidth / 2 - 10) + 20; // 2 items per screen + gap 51 } 52 return (window.innerWidth / 3 - 20) + 20; // 3 items per screen + gap 53 } 54 55 // Calculate maximum allowed movement 56 function getMaxTranslateX() { 57 const itemWidth = getItemWidth(); 58 const totalContentWidth = items.length * itemWidth; 59 const visibleWidth = wrapper.clientWidth; 60 61 if (totalContentWidth <= visibleWidth) { 62 return 0; 63 } 64 65 return Math.max(0, totalContentWidth - visibleWidth - 20); 66 } 67 68 // Calculate visible items 69 function calculateVisibleItems() { 70 const containerWidth = wrapper.clientWidth; 71 const itemWidth = getItemWidth(); 72 return Math.floor(containerWidth / itemWidth); 73 } 74 75 // Calculate total number of slides (pages) 76 function getTotalSlides() { 77 const visibleItems = calculateVisibleItems(); 78 return Math.ceil(items.length / visibleItems); 79 } 80 81 // Generate dots 82 function generateDots() { 83 const totalSlides = getTotalSlides(); 84 dotsContainer.innerHTML = ''; 85 86 for (let i = 0; i < totalSlides; i++) { 87 const li = document.createElement('li'); 88 li.setAttribute('role', 'presentation'); 89 li.setAttribute('aria-selected', i === 0 ? 'true' : 'false'); 90 li.setAttribute('aria-controls', `navigation${i}`); 91 li.setAttribute('id', `slick-slide${i}`); 92 if (i === 0) { 93 li.classList.add('active'); 94 } 95 96 const button = document.createElement('button'); 97 button.type = 'button'; 98 button.setAttribute('data-role', 'none'); 99 button.setAttribute('role', 'button'); 100 button.setAttribute('tabindex', '0'); 101 button.textContent = (i + 1).toString(); 102 103 li.appendChild(button); 104 dotsContainer.appendChild(li); 105 106 // Add click event to dot 107 li.addEventListener('click', function() { 108 goToSlide(i); 109 }); 110 } 111 } 112 113 // Go to specific slide 114 function goToSlide(slideIndex) { 115 const visibleItems = calculateVisibleItems(); 116 const targetPosition = slideIndex * visibleItems; 117 118 // Ensure we don't go beyond the last item 119 currentPosition = Math.min(targetPosition, items.length - visibleItems); 120 currentPosition = Math.max(0, currentPosition); 121 122 updateSliderPosition(); 123 updateDots(); 124 } 125 126 // Update dots active state 127 function updateDots() { 128 const dots = dotsContainer.querySelectorAll('li'); 129 const visibleItems = calculateVisibleItems(); 130 const currentSlide = Math.floor(currentPosition / visibleItems); 131
132 dots.forEach((dot, index) => { 133 dot.classList.remove('active'); 134 dot.setAttribute('aria-selected', 'false'); 135 if (index === currentSlide) { 136 dot.classList.add('active'); 137 dot.setAttribute('aria-selected', 'true'); 138 } 139 }); 140 } 141 142 function updateSliderPosition() { 143 const itemWidth = getItemWidth(); 144 const requestedTranslateX = currentPosition * itemWidth; 145 const maxTranslateX = getMaxTranslateX(); 146 const actualTranslateX = Math.min(requestedTranslateX, maxTranslateX); 147 148 slider.style.transform = `translateX(-${actualTranslateX}px)`; 149 updateNavigationButtons(); 150 updateDots(); 151 } 152 153 function updateNavigationButtons() { 154 // Hide prev button if at the beginning 155 if (currentPosition <= 0) { 156 prevButton.classList.add('slick-disabled'); 157 } else { 158 prevButton.classList.remove('slick-disabled'); 159 } 160 161 // Hide next button when the NEXT move would exceed the max translation 162 const itemWidth = getItemWidth(); 163 const nextPositionTranslateX = (currentPosition + 1) * itemWidth; 164 const maxTranslateX = getMaxTranslateX(); 165 166 if (nextPositionTranslateX > maxTranslateX) { 167 nextButton.classList.add('slick-disabled'); 168 } else { 169 nextButton.classList.remove('slick-disabled'); 170 } 171 } 172 173 // Previous button click - move by 1 item 174 prevButton.addEventListener("click", function (e) { 175 e.preventDefault(); 176 e.stopPropagation(); 177 178 if (currentPosition > 0) { 179 currentPosition--; 180 updateSliderPosition(); 181 } 182 }); 183 184 // Next button click - simple increment with boundary check 185 nextButton.addEventListener("click", function (e) { 186 e.preventDefault(); 187 e.stopPropagation(); 188 189 // Check if we can move more (not at max translation) 190 const itemWidth = getItemWidth(); 191 const requestedTranslateX = (currentPosition + 1) * itemWidth; 192 const maxTranslateX = getMaxTranslateX(); 193 194 if (requestedTranslateX <= maxTranslateX) { 195 currentPosition++; 196 updateSliderPosition(); 197 } 198 }); 199 200 // Handle window resize 201 function handleResize() { 202 if (window.innerWidth >= 992) { 203 slider.style.transform = 'none'; 204 return; 205 } 206 207 const visibleItems = calculateVisibleItems(); 208 currentPosition = Math.min( 209 currentPosition, 210 Math.max(0, items.length - visibleItems) 211 ); 212 213 generateDots(); // Regenerate dots on resize 214 updateSliderPosition(); 215 } 216 217 window.addEventListener("resize", handleResize); 218 219 // Touch support 220 let startX = 0; 221 let endX = 0; 222 let touchStartTime = 0; 223 224 slider.addEventListener("touchstart", function (e) { 225 startX = e.touches[0].clientX; 226 touchStartTime = Date.now(); 227 }); 228 229 slider.addEventListener("touchmove", function (e) { 230 endX = e.touches[0].clientX; 231 }); 232 233 slider.addEventListener("touchend", function (e) { 234 const deltaX = endX - startX; 235 const touchDuration = Date.now() - touchStartTime; 236 237 // Only treat as swipe if movement is significant and quick 238 if (Math.abs(deltaX) > 50 && touchDuration < 300) { 239 if (deltaX > 0) { 240 // Swipe right - go to previous 241 if (currentPosition > 0) { 242 currentPosition--; 243 updateSliderPosition(); 244 } 245 } else {
246 // Swipe left - go to next 247 const itemWidth = getItemWidth(); 248 const requestedTranslateX = (currentPosition + 1) * itemWidth; 249 const maxTranslateX = getMaxTranslateX(); 250 251 if (requestedTranslateX <= maxTranslateX) { 252 currentPosition++; 253 updateSliderPosition(); 254 } 255 } 256 } 257 }); 258 259 // Initialize slider 260 generateDots(); 261 updateSliderPosition(); 262 }); 263 } 264 265 // Initialize sliders 266 initTaxonomySliders(); 267 268 // Re-initialize on window resize (in case screen orientation changes) 269 let resizeTimeout; 270 window.addEventListener("resize", function() { 271 clearTimeout(resizeTimeout); 272 resizeTimeout = setTimeout(function() { 273 // Re-initialize if we're now on mobile 274 if (window.innerWidth < 992) { 275 initTaxonomySliders(); 276 } 277 }, 250); 278 }); 279});
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.