1document.addEventListener('DOMContentLoaded', () => { 2 const scrollContainer = document.getElementById('pricing-grid'); 3 const scrollRow = document.getElementById('pricing-grid'); 4 5 if (!scrollContainer || !scrollRow) { 6 return; 7 } 8 9 // --- Scroll Configuration --- 10 const MAX_SCROLL_SPEED = 12; // Maximum manual scroll speed 11 const ACCELERATION_FACTOR = 0.8; // Manual scroll acceleration rate 12 const FRICTION_FACTOR = 0.0; // Friction applied when stopping 13 const MIN_VELOCITY_TO_STOP = 0.1; // Minimum velocity to halt scrolling 14 const EDGE_ZONE_PERCENTAGE = 0.17; // Edge zone width as a percentage of container width 15 16 const AUTO_SCROLL_EASING_FACTOR = 0.009; // Easing factor for smooth auto-scroll 17 const AUTO_SCROLL_DELAY = 450; // Delay before auto-scroll starts 18 const AUTO_SCROLL_PAUSE_DURATION = 2500; // Pause duration at scroll boundaries 19 // --- End Configuration --- 20 21 // Manual scroll state 22 let scrollDirectionIntensity = 0; 23 let currentScrollVelocity = 0; 24 let manualScrollFrameId = null; 25 26 // Auto-scroll state 27 let hasUserInteracted = false; 28 let isAutoScrollingActive = false; 29 let autoScrollDirection = 1; // 1 for right, -1 for left 30 let autoScrollFrameId = null; 31 let isAutoScrollPaused = false; 32 let pauseTimeoutId = null; 33 34 function canScroll() { 35 if (!scrollRow || !scrollContainer) return false; 36 return scrollRow.scrollWidth > scrollContainer.clientWidth + 1; 37 } 38 39 // --- Initial Auto-Scroll Logic with Easing --- 40 function performInitialAutoScroll() { 41 if (!isAutoScrollingActive || hasUserInteracted || !canScroll()) { 42 stopInitialAutoScroll(); 43 return; 44 } 45 46 if (isAutoScrollPaused) { 47 // Currently paused, waiting for setTimeout to finish. 48 return; 49 } 50 51 // We use preciseAutoScrollLeft for calculations, and sync it with actual scrollLeft on start/snap. 52 // On the first run of a scroll segment, ensure preciseAutoScrollLeft matches actual scrollLeft. 53 // This is important if actual scrollLeft was changed by something else, or after a snap. 54 // However, since we snap preciseAutoScrollLeft, it should stay in sync. 55 56 const maxScrollLeft = scrollRow.scrollWidth - scrollContainer.clientWidth; 57 58 let targetScrollValue; // This is the integer target we are aiming for 59 if (autoScrollDirection === 1) { // Moving right 60 targetScrollValue = maxScrollLeft; 61 } else { // Moving left (autoScrollDirection === -1) 62 targetScrollValue = 0; 63 } 64 65 // Calculate distance using the precise internal value 66 const distanceToTarget = targetScrollValue - preciseAutoScrollLeft; 67 68 69 let scrollStep = distanceToTarget * AUTO_SCROLL_EASING_FACTOR; 70 71 // Update the precise internal scroll value 72 preciseAutoScrollLeft += scrollStep; 73 74 // Apply the rounded value to the actual scrollLeft property 75 scrollContainer.scrollLeft = Math.round(preciseAutoScrollLeft); 76 77 let boundaryReached = false; 78 // Check if the *precise* value is very close to the target (threshold can be smaller now) 79 if (Math.abs(targetScrollValue - preciseAutoScrollLeft) < 0.5) { 80 scrollContainer.scrollLeft = targetScrollValue; // Snap to the integer target 81 preciseAutoScrollLeft = targetValue; // Crucially, sync the precise value to the snap target 82 boundaryReached = true; 83 } 84 85 if (boundaryReached) { 86 isAutoScrollPaused = true; 87 if (pauseTimeoutId) clearTimeout(pauseTimeoutId); 88 pauseTimeoutId = setTimeout(() => { 89 isAutoScrollPaused = false; 90 pauseTimeoutId = null; 91 92 if (hasUserInteracted || !isAutoScrollingActive || !canScroll()) { 93 stopInitialAutoScroll(); 94 return; 95 } 96 autoScrollDirection *= -1; // Reverse direction for next movement 97 98 // preciseAutoScrollLeft is already at an endpoint (0 or maxScrollLeft) 99 autoScrollFrameId = requestAnimationFrame(performInitialAutoScroll); 100 }, AUTO_SCROLL_PAUSE_DURATION); 101 } else if (isAutoScrollingActive) { 102 autoScrollFrameId = requestAnimationFrame(performInitialAutoScroll); 103 } else { 104 stopInitialAutoScroll(); 105 } 106 } 107 108 function startInitialAutoScroll() { 109 if (hasUserInteracted || !canScroll() || isAutoScrollingActive) return; 110 isAutoScrollingActive = true; 111 isAutoScrollPaused = false; 112 if (pauseTimeoutId) clearTimeout(pauseTimeoutId); 113 114 // Sync preciseAutoScrollLeft with the actual scrollLeft when starting a new scroll sequence
115 preciseAutoScrollLeft = scrollContainer.scrollLeft; 116 117 setTimeout(() => { 118 if (isAutoScrollingActive && !autoScrollFrameId && !hasUserInteracted && !isAutoScrollPaused) { 119 autoScrollFrameId = requestAnimationFrame(performInitialAutoScroll); 120 } 121 }, AUTO_SCROLL_DELAY); 122 } 123 124 // In tab change handler, after scrollContainer.scrollLeft = 0; 125 // add: 126 // preciseAutoScrollLeft = 0; 127 128 // In resize handler, after scrollContainer.scrollLeft = 0; (if it happens) 129 // add: 130 // preciseAutoScrollLeft = 0; 131 132 function stopInitialAutoScroll() { 133 isAutoScrollingActive = false; 134 isAutoScrollPaused = false; 135 if (autoScrollFrameId) { 136 cancelAnimationFrame(autoScrollFrameId); 137 autoScrollFrameId = null; 138 } 139 if (pauseTimeoutId) { 140 clearTimeout(pauseTimeoutId); 141 pauseTimeoutId = null; 142 } 143 } 144 145 function handleFirstUserInteraction() { 146 if (!hasUserInteracted) { 147 hasUserInteracted = true; 148 stopInitialAutoScroll(); 149 } 150 } 151 152 scrollContainer.addEventListener('mouseenter', handleFirstUserInteraction, { once: true }); 153 scrollContainer.addEventListener('mousedown', handleFirstUserInteraction, { once: true }); 154 scrollContainer.addEventListener('touchstart', handleFirstUserInteraction, { once: true }); 155 scrollContainer.addEventListener('wheel', handleFirstUserInteraction, { once: true }); 156 157 // --- Manual Hover-Scroll Logic (remains the same) --- 158 scrollContainer.addEventListener('mousemove', (e) => { 159 if (hasUserInteracted) { 160 if (!canScroll()) { stopManualScrolling(); scrollDirectionIntensity = 0; return; } 161 const containerRect = scrollContainer.getBoundingClientRect(); 162 const mouseX = e.clientX - containerRect.left; 163 const containerWidth = containerRect.width; 164 const edgeWidth = containerWidth * EDGE_ZONE_PERCENTAGE; 165 let newIntensity = 0; 166 if (mouseX < edgeWidth && mouseX > 0) { newIntensity = -1 * ((edgeWidth - mouseX) / edgeWidth); } 167 else if (mouseX > containerWidth - edgeWidth && mouseX < containerWidth) { newIntensity = 1 * ((mouseX - (containerWidth - edgeWidth)) / edgeWidth); } 168 scrollDirectionIntensity = Math.max(-1, Math.min(1, newIntensity)); 169 if (scrollDirectionIntensity !== 0 && manualScrollFrameId === null) { startManualScrolling(); } 170 } 171 }); 172 scrollContainer.addEventListener('mouseleave', () => { if (hasUserInteracted) { scrollDirectionIntensity = 0; } }); 173 function startManualScrolling() { if (manualScrollFrameId === null) { manualScrollFrameId = requestAnimationFrame(performManualScroll); } } 174 function stopManualScrolling() { if (manualScrollFrameId !== null) { cancelAnimationFrame(manualScrollFrameId); manualScrollFrameId = null; } } 175 function performManualScroll() { 176 if (!hasUserInteracted) { stopManualScrolling(); return; } 177 let targetVelocity = scrollDirectionIntensity * MAX_SCROLL_SPEED; 178 currentScrollVelocity += (targetVelocity - currentScrollVelocity) * ACCELERATION_FACTOR; 179 if (scrollDirectionIntensity === 0) { currentScrollVelocity *= FRICTION_FACTOR; } 180 if (Math.abs(currentScrollVelocity) < MIN_VELOCITY_TO_STOP && scrollDirectionIntensity === 0) { currentScrollVelocity = 0; } 181 if (currentScrollVelocity === 0 && scrollDirectionIntensity === 0) { stopManualScrolling(); return; } 182 if (!canScroll()) { currentScrollVelocity = 0; stopManualScrolling(); return; } 183 if (Math.abs(currentScrollVelocity) > 0) { 184 const currentScrollLeft = scrollContainer.scrollLeft; 185 const maxScrollLeft = scrollRow.scrollWidth - scrollContainer.clientWidth; 186 let newScrollLeft = currentScrollLeft + currentScrollVelocity; 187 if (newScrollLeft <= 0) { newScrollLeft = 0; currentScrollVelocity *= -0.5; } 188 else if (newScrollLeft >= maxScrollLeft) { newScrollLeft = maxScrollLeft; currentScrollVelocity *= -0.5; } 189 scrollContainer.scrollLeft = newScrollLeft; 190 } 191 if (manualScrollFrameId !== null) { manualScrollFrameId = requestAnimationFrame(performManualScroll); } 192 } 193 // --- End Manual Hover-Scroll Logic --- 194 195 // --- Event Handlers for Resize and Tabs (remains the same) --- 196 let resizeTimeout; 197 window.addEventListener('resize', () => { 198 clearTimeout(resizeTimeout); 199 resizeTimeout = setTimeout(() => { 200 const isNowScrollable = canScroll(); 201 if (!isNowScrollable) { 202 stopInitialAutoScroll(); scrollContainer.scrollLeft = 0; currentScrollVelocity = 0; 203 scrollDirectionIntensity = 0; stopManualScrolling(); 204 } else if (!hasUserInteracted && !isAutoScrollingActive) { 205 scrollContainer.scrollLeft = 0; autoScrollDirection = 1; startInitialAutoScroll(); 206 } 207 }, 250); 208 });
209 const tabInputs = document.querySelectorAll('input[name="pricing-tab"]'); 210 tabInputs.forEach(input => { 211 input.addEventListener('change', () => { 212 stopInitialAutoScroll(); currentScrollVelocity = 0; scrollDirectionIntensity = 0; 213 stopManualScrolling(); scrollContainer.scrollLeft = 0; 214 setTimeout(() => { 215 if (canScroll() && !hasUserInteracted) { autoScrollDirection = 1; startInitialAutoScroll(); } 216 }, 150); 217 }); 218 }); 219 // --- End Event Handlers --- 220 221 if (canScroll()) { startInitialAutoScroll(); } 222});
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.