PageSourceSearch

https://moneypex.com/js/pricing-carousel.js?v=69TIntQihaEC8mDbvISE…69TIntQihaEC8mDbvISEeZc0kR9lVwo-l2qOPshDHk0

js moneypex.com collected 2026-10-04 00:19:55 UTC 7,308 bytes, 193 lines download raw bytes

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.