1/** 2 * PRICING CAROUSEL NAVIGATION 3 * Handles carousel navigation buttons and dots for mobile pricing cards 4 */ 5 6(function () { 7 'use strict'; 8 9 // Initialize carousel on DOM ready 10 if (document.readyState === 'loading') { 11 document.addEventListener('DOMContentLoaded', initPricingCarousel); 12 } else { 13 initPricingCarousel(); 14 } 15 16 function initPricingCarousel() { 17 // Only run on mobile/tablet 18 if (window.innerWidth >= 992) { 19 console.log('Pricing carousel: Desktop view detected, skipping initialization'); 20 return; 21 } 22 23 const pricingSections = document.querySelectorAll('.pricing-page'); 24 console.log(`Pricing carousel: Found ${pricingSections.length} pricing sections`); 25 26 pricingSections.forEach((section, sectionIndex) => { 27 // Try multiple selectors to find the row 28 let container = section.querySelector('.row.justify-content-center'); 29 if (!container) { 30 container = section.querySelector('.row[class*="justify-content"]'); 31 } 32 if (!container) { 33 console.warn(`Pricing carousel: No container found in section ${sectionIndex}`); 34 return; 35 } 36 37 const cards = container.querySelectorAll(':scope > [class*="col-"]'); 38 console.log(`Pricing carousel: Found ${cards.length} cards in section ${sectionIndex}`); 39 40 // Force flex-start to prevent centering issues with scroll 41 container.style.setProperty('justify-content', 'flex-start', 'important'); 42 43 if (cards.length <= 1) { 44 console.log(`Pricing carousel: Only ${cards.length} card(s), skipping carousel`); 45 return; // No need for carousel with 1 card 46 } 47 48 // Create navigation buttons 49 createNavigationButtons(section, container, cards);
50 51 // Create dots indicator 52 createDotsIndicator(section, container, cards); 53 54 // Update button states on scroll 55 container.addEventListener('scroll', () => updateNavigationState(container, cards), { passive: true }); 56 57 // Initial state 58 updateNavigationState(container, cards); 59 60 console.log(`Pricing carousel: Successfully initialized section ${sectionIndex}`); 61 }); 62 } 63 64 function createNavigationButtons(section, container, cards) { 65 // Check if buttons already exist 66 if (section.querySelector('.pricing-carousel-nav')) return; 67 68 // Previous button 69 const prevBtn = document.createElement('button'); 70 prevBtn.className = 'pricing-carousel-nav prev'; 71 prevBtn.setAttribute('aria-label', 'Previous pricing plan'); 72 prevBtn.innerHTML = ` 73 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> 74 <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/> 75 </svg> 76 `; 77 prevBtn.addEventListener('click', () => scrollCarousel(container, cards, 'prev')); 78 79 // Next button 80 const nextBtn = document.createElement('button'); 81 nextBtn.className = 'pricing-carousel-nav next'; 82 nextBtn.setAttribute('aria-label', 'Next pricing plan'); 83 nextBtn.innerHTML = ` 84 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> 85 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 86 </svg> 87 `; 88 nextBtn.addEventListener('click', () => scrollCarousel(container, cards, 'next')); 89 90 // Insert buttons 91 const pricingContainer = section.querySelector('.container-fluid.container-md'); 92 if (pricingContainer) { 93 pricingContainer.appendChild(prevBtn); 94 pricingContainer.appendChild(nextBtn); 95 } 96 } 97 98 function createDotsIndicator(section, container, cards) { 99 // Check if dots already exist 100 if (section.querySelector('.pricing-carousel-dots')) return; 101 102 const dotsContainer = document.createElement('div'); 103 dotsContainer.className = 'pricing-carousel-dots'; 104 105 cards.forEach((card, index) => { 106 const dot = document.createElement('button'); 107 dot.className = 'pricing-carousel-dot'; 108 dot.setAttribute('aria-label', `Go to plan ${index + 1}`); 109 if (index === 0) dot.classList.add('active'); 110 111 dot.addEventListener('click', () => { 112 card.scrollIntoView({ 113 behavior: 'smooth', 114 block: 'nearest', 115 inline: 'center' 116 }); 117 }); 118 119 dotsContainer.appendChild(dot); 120 }); 121 122 // Insert dots after the pricing row 123 const pricingRow = section.querySelector('.row.justify-content-center'); 124 if (pricingRow && pricingRow.parentNode) { 125 pricingRow.parentNode.insertBefore(dotsContainer, pricingRow.nextSibling); 126 } 127 } 128 129 function scrollCarousel(container, cards, direction) { 130 const cardWidth = cards[0].offsetWidth; 131 const scrollAmount = direction === 'next' ? cardWidth : -cardWidth; 132 133 container.scrollBy({ 134 left: scrollAmount, 135 behavior: 'smooth' 136 }); 137 } 138 139 function updateNavigationState(container, cards) { 140 const section = container.closest('.pricing-page'); 141 if (!section) return; 142 143 const prevBtn = section.querySelector('.pricing-carousel-nav.prev'); 144 const nextBtn = section.querySelector('.pricing-carousel-nav.next'); 145 const dots = section.querySelectorAll('.pricing-carousel-dot'); 146 147 if (!prevBtn || !nextBtn) return; 148 149 // Calculate scroll position 150 const scrollLeft = container.scrollLeft; 151 const maxScroll = container.scrollWidth - container.clientWidth; 152 153 // Update button states 154 if (scrollLeft <= 10) { 155 prevBtn.classList.add('disabled'); 156 } else { 157 prevBtn.classList.remove('disabled'); 158 } 159 160 if (scrollLeft >= maxScroll - 10) { 161 nextBtn.classList.add('disabled'); 162 } else { 163 nextBtn.classList.remove('disabled'); 164 } 165 166 // Update dots 167 if (dots.length > 0) { 168 const cardWidth = cards[0].offsetWidth; 169 const currentIndex = Math.round(scrollLeft / cardWidth); 170
171 dots.forEach((dot, index) => { 172 if (index === currentIndex) { 173 dot.classList.add('active'); 174 } else { 175 dot.classList.remove('active'); 176 } 177 }); 178 } 179 } 180 181 // Re-initialize on window resize 182 let resizeTimer; 183 window.addEventListener('resize', () => { 184 clearTimeout(resizeTimer); 185 resizeTimer = setTimeout(() => { 186 // Remove existing navigation 187 document.querySelectorAll('.pricing-carousel-nav, .pricing-carousel-dots').forEach(el => el.remove()); 188 // Re-initialize 189 initPricingCarousel(); 190 }, 250); 191 }); 192 193})();
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.