1/** 2 * Google Reviews Slider - Swiper.js Initialization 3 * Optimized for iOS/Safari compatibility 4 */ 5document.addEventListener('DOMContentLoaded', function() { 6 // Initialize all sliders 7 document.querySelectorAll('.grs-swiper').forEach(function(el) { 8 var autoplay = el.dataset.autoplay !== 'false'; 9 var autoplaySpeed = parseInt(el.dataset.autoplaySpeed) || 4000; 10 var slidesDesktop = parseInt(el.dataset.slidesDesktop) || 3; 11 var slidesTablet = parseInt(el.dataset.slidesTablet) || 2; 12 var slidesMobile = parseInt(el.dataset.slidesMobile) || 1; 13 // Swiper 11 disables loop with a console warning when 14 // slides <= slidesPerView; skip it up front in that case. 15 var slideCount = el.querySelectorAll('.swiper-slide').length; 16 17 var swiper = new Swiper(el, { 18 // Core settings 19 slidesPerView: slidesMobile, 20 spaceBetween: 20, 21 loop: slideCount > slidesDesktop, 22 grabCursor: true, 23 24 // iOS/Safari critical settings 25 touchEventsTarget: 'container', 26 simulateTouch: true, 27 allowTouchMove: true, 28 touchRatio: 1, 29 touchAngle: 45, 30 shortSwipes: true, 31 longSwipes: true, 32 longSwipesRatio: 0.5, 33 longSwipesMs: 300, 34 followFinger: true, 35 threshold: 5, 36 touchMoveStopPropagation: false, 37 touchStartPreventDefault: false, 38 touchReleaseOnEdges: true, 39 passiveListeners: true, 40 resistance: true, 41 resistanceRatio: 0.85, 42 43 // Autoplay 44 autoplay: autoplay ? { 45 delay: autoplaySpeed, 46 disableOnInteraction: false, 47 pauseOnMouseEnter: true 48 } : false, 49 50 // Pagination 51 // dynamicBullets injects an inline width + translateX on the 52 // .swiper-pagination element, which fights the CSS centering and 53 // shoves the dots off to one side. Keep it off so the bullets stay 54 // centered via plain text-align:center. 55 pagination: { 56 el: el.querySelector('.swiper-pagination'), 57 clickable: true, 58 dynamicBullets: false 59 }, 60 61 // Nav buttons live outside the swiper element and are wired up 62 // manually below; passing them to Swiper's navigation module too 63 // would bind a second click handler and advance two slides per click. 64 65 // Responsive breakpoints 66 breakpoints: { 67 320: { 68 slidesPerView: slidesMobile, 69 spaceBetween: 15 70 }, 71 768: { 72 slidesPerView: slidesTablet, 73 spaceBetween: 20 74 }, 75 1024: { 76 slidesPerView: slidesDesktop, 77 spaceBetween: 25 78 } 79 }, 80 81 // Accessibility 82 a11y: { 83 prevSlideMessage: 'Previous review', 84 nextSlideMessage: 'Next review', 85 firstSlideMessage: 'First review', 86 lastSlideMessage: 'Last review' 87 } 88 }); 89 90 // iOS Safari: Restart autoplay after interaction 91 if (autoplay) { 92 el.addEventListener('touchend', function() { 93 setTimeout(function() { 94 if (swiper.autoplay && !swiper.autoplay.running) { 95 swiper.autoplay.start(); 96 } 97 }, 1000); 98 }); 99 } 100 101 // touchend calls preventDefault, which suppresses the synthetic click 102 // that follows a tap, so mobile taps advance exactly one slide. 103 var container = el.closest('.grs-direct-slider-container'); 104 var prevBtn = container ? container.querySelector('.grs-nav-prev') : null; 105 var nextBtn = container ? container.querySelector('.grs-nav-next') : null; 106 107 if (prevBtn) { 108 prevBtn.addEventListener('click', function(e) { 109 e.preventDefault(); 110 e.stopPropagation(); 111 swiper.slidePrev(); 112 }); 113 prevBtn.addEventListener('touchend', function(e) { 114 e.preventDefault(); 115 e.stopPropagation(); 116 swiper.slidePrev(); 117 }); 118 } 119 120 if (nextBtn) { 121 nextBtn.addEventListener('click', function(e) { 122 e.preventDefault(); 123 e.stopPropagation(); 124 swiper.slideNext(); 125 }); 126 nextBtn.addEventListener('touchend', function(e) { 127 e.preventDefault(); 128 e.stopPropagation(); 129 swiper.slideNext(); 130 }); 131 } 132 });
133 134 // Read more/less functionality 135 document.addEventListener('click', function(e) { 136 if (e.target.classList.contains('grs-direct-read-more')) { 137 e.preventDefault(); 138 var review = e.target.closest('.grs-direct-review'); 139 var text = review.querySelector('.grs-direct-text'); 140 var showLess = review.querySelector('.grs-direct-hide'); 141 var swiperEl = e.target.closest('.grs-swiper'); 142 143 // Expand the text 144 text.classList.remove('truncated'); 145 text.classList.add('expanded'); 146 e.target.style.display = 'none'; 147 if (showLess) showLess.style.display = 'inline'; 148 149 // Update Swiper to recalculate heights 150 if (swiperEl && swiperEl.swiper) { 151 setTimeout(function() { 152 swiperEl.swiper.update(); 153 }, 50); 154 } 155 } 156 157 if (e.target.classList.contains('grs-direct-hide')) { 158 e.preventDefault(); 159 var review = e.target.closest('.grs-direct-review'); 160 var text = review.querySelector('.grs-direct-text'); 161 var readMore = review.querySelector('.grs-direct-read-more'); 162 var swiperEl = e.target.closest('.grs-swiper'); 163 164 // Collapse the text 165 text.classList.remove('expanded'); 166 text.classList.add('truncated'); 167 e.target.style.display = 'none'; 168 if (readMore) readMore.style.display = 'inline'; 169 170 // Update Swiper to recalculate heights 171 if (swiperEl && swiperEl.swiper) { 172 setTimeout(function() { 173 swiperEl.swiper.update(); 174 }, 50); 175 } 176 } 177 }); 178});
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.