PageSourceSearch

https://motivewave.com/js/pricing-scroll.js

js motivewave.com collected 2026-10-01 13:04:36 UTC 10,674 bytes, 222 lines download raw bytes

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.