PageSourceSearch

https://cleartime.care/wp-content/themes/jupiterx-child/assets/js/custom-landing.js?ver=1767228519

js cleartime.care collected 2026-09-24 13:55:52 UTC 12,834 bytes, 381 lines download raw bytes

1/**
2 * clearTime Care - Modern Landing Page Scripts
3 * Handles scroll animations, tabs, accordion, and smooth interactions
4 */
5
6(function() {
7    'use strict';
8
9    // Wait for DOM to be ready
10    document.addEventListener('DOMContentLoaded', function() {
11        initScrollAnimations();
12        initTabs();
13        initAccordion();
14        initSmoothScroll();
15        initParallax();
16        initMobileMenu();
17        initHeaderScroll();
18        initDarkModeToggle();
19    });
20
21    /**
22     * Dark Mode Toggle - Theme preference with localStorage persistence
23     */
24    function initDarkModeToggle() {
25        var STORAGE_KEY = 'ct-theme-preference';
26        var html = document.documentElement;
27
28        function getPreferredTheme() {
29            var stored = localStorage.getItem(STORAGE_KEY);
30            if (stored) return stored;
31            return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
32        }
33
34        function setTheme(theme) {
35            html.setAttribute('data-ct-theme', theme);
36            localStorage.setItem(STORAGE_KEY, theme);
37            updateToggleButtons(theme);
38        }
39
40        function updateToggleButtons(theme) {
41            document.querySelectorAll('.ct-theme-toggle__btn').forEach(function(btn) {
42                btn.classList.toggle('active', btn.dataset.theme === theme);
43            });
44        }
45
46        // Apply theme (flash prevention script already set attribute)
47        var theme = getPreferredTheme();
48        setTheme(theme);
49
50        // Handle toggle button clicks
51        document.addEventListener('click', function(e) {
52            var btn = e.target.closest('.ct-theme-toggle__btn');
53            if (btn && btn.dataset.theme) {
54                setTheme(btn.dataset.theme);
55            }
56        });
57
58        // Listen for system preference changes
59        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
60            if (!localStorage.getItem(STORAGE_KEY)) {
61                setTheme(e.matches ? 'dark' : 'light');
62            }
63        });
64    }
65
66    /**
67     * Scroll Animations - Reveal elements on scroll with staggered delays
68     */
69    function initScrollAnimations() {
70        // Select elements to animate
71        const animatedSelectors = [
72            '.ct-section-header',
73            '.ct-card',
74            '.ct-comparison__column',
75            '.ct-pricing__card',
76            '.ct-accordion__item',
77            '.ct-blog-card',
78            '.ct-feature-row > *'
79        ];
80
81        const animatedElements = document.querySelectorAll(animatedSelectors.join(', '));
82
83        // Add animation classes
84        animatedElements.forEach(function(el, index) {
85            el.classList.add('ct-animate');
86            // Create staggered delay groups
87            const delayClass = 'ct-animate-delay-' + ((index % 4) + 1);
88            el.classList.add(delayClass);
89        });
90
91        // Intersection Observer for scroll reveal
92        const observerOptions = {
93            root: null,
94            rootMargin: '50px 0px -50px 0px',
95            threshold: 0.1
96        };
97
98        const observer = new IntersectionObserver(function(entries) {
99            entries.forEach(function(entry) {
100                if (entry.isIntersecting) {
101                    entry.target.classList.add('visible');
102                    observer.unobserve(entry.target);
103                }
104            });
105        }, observerOptions);
106
107        animatedElements.forEach(function(el) {
108            observer.observe(el);
109        });
110
111        // Immediately show elements already in viewport on load
112        setTimeout(function() {
113            animatedElements.forEach(function(el) {
114                const rect = el.getBoundingClientRect();
115                if (rect.top < window.innerHeight && rect.bottom > 0) {
116                    el.classList.add('visible');
117                }
118            });
119        }, 100);
120    }
121
122    /**
123     * Tabs Component - Modern pill-style tabs
124     */
125    function initTabs() {
126        const tabContainers = document.querySelectorAll('.ct-tabs');
127
128        tabContainers.forEach(function(container) {
129            const buttons = container.querySelectorAll('.ct-tabs__btn');
130            const panels = container.querySelectorAll('.ct-tabs__panel');
131
132            buttons.forEach(function(button) {
133                button.addEventListener('click', function() {
134                    const targetId = this.getAttribute('data-tab');
135
136                    // Remove active from all
137                    buttons.forEach(function(btn) {
138                        btn.classList.remove('active');
139                    });
140                    panels.forEach(function(panel) {
141                        panel.classList.remove('active');
142                    });
143
144                    // Activate clicked tab
145                    this.classList.add('active');
146                    const targetPanel = container.querySelector('[data-tab-panel="' + targetId + '"]');
147                    if (targetPanel) {
148                        targetPanel.classList.add('active');
149                    }
150                });
151            });
152
153            // Activate first tab by default
154            if (!container.querySelector('.ct-tabs__btn.active') && buttons.length > 0) {
155                buttons[0].click();
156            }
157        });
158    }
159
160    /**
161     * Accordion Component - Smooth expand/collapse
162     */
163    function initAccordion() {
164        const accordions = document.querySelectorAll('.ct-accordion');
165
166        accordions.forEach(function(accordion) {
167            const items = accordion.querySelectorAll('.ct-accordion__item');
168
169            items.forEach(function(item) {
170                const header = item.querySelector('.ct-accordion__header');
171
172                header.addEventListener('click', function() {
173                    const isActive = item.classList.contains('active');
174
175                    // Close all items (single-open mode)
176                    items.forEach(function(otherItem) {
177                        otherItem.classList.remove('active');
178                    });
179
180                    // Toggle clicked item
181                    if (!isActive) {
182                        item.classList.add('active');
183                    }
184                });
185            });
186        });
187    }
188
189    /**
190     * Smooth Scroll - Enhanced anchor scrolling
191     */
192    function initSmoothScroll() {
193        document.querySelectorAll('a[href^="#"]').forEach(function(anchor) {
194            anchor.addEventListener('click', function(e) {
195                const targetId = this.getAttribute('href');
196
197                if (targetId === '#' || targetId === '') return;
198
199                const targetElement = document.querySelector(targetId);
200
201                if (targetElement) {
202                    e.preventDefault();
203
204                    const headerOffset = 100;
205                    const elementPosition = targetElement.getBoundingClientRect().top;
206                    const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
207
208                    window.scrollTo({
209                        top: offsetPosition,
210                        behavior: 'smooth'
211                    });
212                }
213            });
214        });
215    }
216
217    /**
218     * Subtle Parallax Effect for hero blobs
219     */
220    function initParallax() {
221        const blobs = document.querySelectorAll('.ct-hero__blob');
222
223        if (blobs.length === 0) return;
224
225        let ticking = false;
226
227        window.addEventListener('scroll', function() {
228            if (!ticking) {
229                window.requestAnimationFrame(function() {
230                    const scrolled = window.pageYOffset;
231
232                    blobs.forEach(function(blob, index) {
233                        const speed = 0.1 + (index * 0.05);
234                        const yPos = scrolled * speed;
235                        blob.style.transform = 'translateY(' + yPos + 'px)';
236                    });
237
238                    ticking = false;
239                });
240
241                ticking = true;
242            }
243        });
244    }
245
246    /**
247     * Mobile Menu Toggle - Modern animated menu
248     * Uses data-ct-menu-open attribute for state management (synced with CSS)
249     */
250    function initMobileMenu() {
251        const toggle = document.querySelector('.ct-header__toggle');
252        const mobileNav = document.querySelector('.ct-header__mobile-nav');
253        const header = document.querySelector('.ct-header');
254
255        if (!toggle || !mobileNav) return;
256
257        // Save scroll position to restore after closing menu
258        let savedScrollY = 0;
259
260        // Helper to check if menu is open
261        function isMenuOpen() {
262            return toggle.getAttribute('data-ct-menu-open') === 'true';
263        }
264
265        // Toggle menu open/close
266        toggle.addEventListener('click', function(e) {
267            e.stopPropagation();
268            const isOpen = isMenuOpen();
269            const newState = !isOpen;
270
271            // Update state via data attribute (CSS uses this for styling)
272            this.setAttribute('aria-expanded', newState);
273            this.setAttribute('data-ct-menu-open', newState);
274            mobileNav.setAttribute('data-ct-menu-open', newState);
275
276            // Prevent body scroll when menu is open - preserve scroll position
277            if (newState) {
278                savedScrollY = window.scrollY;
279                document.body.style.overflow = 'hidden';
280                document.body.style.position = 'fixed';
281                document.body.style.top = '-' + savedScrollY + 'px';
282                document.body.style.width = '100%';
283                document.body.classList.add('ct-mobile-menu-open');
284            } else {
285                document.body.style.overflow = '';
286                document.body.style.position = '';
287                document.body.style.top = '';
288                document.body.style.width = '';
289                document.body.classList.remove('ct-mobile-menu-open');
290                window.scrollTo(0, savedScrollY);
291            }
292        });
293
294        // Close menu when clicking a link
295        mobileNav.querySelectorAll('a').forEach(function(link) {
296            link.addEventListener('click', function() {
297                closeMenu();
298            });
299        });
300
301        // Close menu when clicking the close button
302        var closeBtn = mobileNav.querySelector('.ct-header__mobile-close');
303        if (closeBtn) {
304            closeBtn.addEventListener('click', function(e) {
305                e.preventDefault();
306                e.stopPropagation();
307                closeMenu();
308            });
309        }
310
311        // Close menu when clicking outside
312        document.addEventListener('click', function(e) {
313            if (isMenuOpen() &&
314                !mobileNav.contains(e.target) &&
315                !toggle.contains(e.target)) {
316                closeMenu();
317            }
318        });
319
320        // Close menu on escape key
321        document.addEventListener('keydown', function(e) {
322            if (e.key === 'Escape' && isMenuOpen()) {
323                closeMenu();
324            }
325        });
326
327        function closeMenu() {
328            toggle.setAttribute('aria-expanded', 'false');
329            toggle.setAttribute('data-ct-menu-open', 'false');
330            mobileNav.setAttribute('data-ct-menu-open', 'false');
331            document.body.style.overflow = '';
332            document.body.style.position = '';
333            document.body.style.top = '';
334            document.body.style.width = '';
335            document.body.classList.remove('ct-mobile-menu-open');
336            window.scrollTo(0, savedScrollY);
337        }
338
339        // Handle window resize - close menu if viewport becomes larger than mobile
340        let resizeTimer;
341        window.addEventListener('resize', function() {
342            clearTimeout(resizeTimer);
343            resizeTimer = setTimeout(function() {
344                if (window.innerWidth > 768 && isMenuOpen()) {
345                    closeMenu();
346                }
347            }, 100);
348        });
349    }
350
351    /**
352     * Header Scroll Effect - Frosted glass background on scroll
353     */
354    function initHeaderScroll() {
355        const header = document.querySelector('.ct-header');
356        if (!header) return;
357
358        const scrollThreshold = 50;
359        let ticking = false;
360
361        function updateHeader() {
362            if (window.scrollY > scrollThreshold) {
363                header.classList.add('scrolled');
364            } else {
365                header.classList.remove('scrolled');
366            }
367            ticking = false;
368        }
369
370        window.addEventListener('scroll', function() {
371            if (!ticking) {
372                window.requestAnimationFrame(updateHeader);
373                ticking = true;
374            }
375        });
376
377        // Check initial state
378        updateHeader();
379    }
380
381})();

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.