PageSourceSearch

https://headrandom.com/themes/flexiwhite/js/main.js?v=1790893013

js headrandom.com collected 2026-10-02 05:17:15 UTC 10,929 bytes, 280 lines download raw bytes

1/**
2 * PolyCMS Corporate Theme JavaScript
3 *
4 * Professional business theme functionality
5 */
6
7(function() {
8    'use strict';
9
10    // Mobile Menu Toggle
11    const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
12    const mobileMenu = document.getElementById('mobile-menu');
13
14    if (mobileMenuToggle && mobileMenu) {
15        // Update ARIA attributes
16        mobileMenuToggle.setAttribute('aria-expanded', 'false');
17        mobileMenu.setAttribute('aria-hidden', 'true');
18
19        mobileMenuToggle.addEventListener('click', function() {
20            const isActive = mobileMenu.classList.toggle('active');
21
22            // Update ARIA attributes
23            mobileMenuToggle.setAttribute('aria-expanded', isActive ? 'true' : 'false');
24            mobileMenu.setAttribute('aria-hidden', isActive ? 'false' : 'true');
25            
26            // Toggle body scroll
27            document.body.style.overflow = isActive ? 'hidden' : '';
28
29            // Update icon
30            const icon = mobileMenuToggle.querySelector('svg');
31            if (icon) {
32                if (isActive) {
33                    icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />';
34                    icon.setAttribute('aria-label', 'Close menu');
35                } else {
36                    icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />';
37                    icon.setAttribute('aria-label', 'Open menu');
38                }
39            }
40        });
41
42        // Close menu on escape key
43        document.addEventListener('keydown', function(e) {
44            if (e.key === 'Escape' && mobileMenu.classList.contains('active')) {
45                mobileMenu.classList.remove('active');
46                mobileMenuToggle.setAttribute('aria-expanded', 'false');
47                mobileMenu.setAttribute('aria-hidden', 'true');
48                document.body.style.overflow = '';
49                mobileMenuToggle.focus();
50            }
51        });
52        
53        // Mobile Submenu Toggle
54        document.querySelectorAll('.submenu-toggle').forEach(function(toggle) {
55            toggle.addEventListener('click', function(e) {
56                e.preventDefault();
57                e.stopPropagation();
58                this.classList.toggle('active');
59                var submenu = this.nextElementSibling;
60                if (submenu && submenu.classList.contains('mobile-submenu')) {
61                    submenu.classList.toggle('active');
62                }
63            });
64        });
65    }
66
67    // Dropdown edge detection
68    document.querySelectorAll('.nav-item.has-dropdown').forEach(function(item) {
69        item.addEventListener('mouseenter', function() {
70            var dropdown = this.querySelector('.nav-dropdown');
71            if (dropdown) {
72                var rect = dropdown.getBoundingClientRect();
73                if (rect.right > window.innerWidth) {
74                    dropdown.classList.add('dropdown-reverse');
75                }
76            }
77        });
78        item.addEventListener('mouseleave', function() {
79            var dropdown = this.querySelector('.nav-dropdown');
80            if (dropdown) {
81                dropdown.classList.remove('dropdown-reverse');
82            }
83        });
84    });
85
86    // Header scroll effect
87    const header = document.getElementById('main-header');
88    if (header) {
89        let lastScroll = 0;
90        window.addEventListener('scroll', function() {
91            const currentScroll = window.pageYOffset;
92
93            if (currentScroll > 50) {
94                header.classList.add('scrolled');
95            } else {
96                header.classList.remove('scrolled');
97            }
98
99            lastScroll = currentScroll;
100        });
101    }
102
103    // Smooth scroll for anchor links
104    // Respect prefers-reduced-motion
105    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
106
107    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
108        anchor.addEventListener('click', function (e) {
109            const href = this.getAttribute('href');
110            if (href !== '#' && href.length > 1) {
111                const target = document.querySelector(href);
112                if (target) {
113                    e.preventDefault();
114                    const headerOffset = 80;
115                    const elementPosition = target.getBoundingClientRect().top;
116                    const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
117
118                    window.scrollTo({
119                        top: offsetPosition,
120                        behavior: prefersReducedMotion ? 'auto' : 'smooth'
121                    });
122
123                    // Close mobile menu if open
124                    if (mobileMenu && mobileMenu.classList.contains('active')) {
125                        mobileMenu.classList.remove('active');
126                        document.body.style.overflow = '';
127                        // Update icon
128                        const icon = mobileMenuToggle.querySelector('svg');
129                        if (icon) {
130                            icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />';
131                        }
132                    }
133
134                    // Focus target for accessibility
135                    target.setAttribute('tabindex', '-1');
136                    target.focus();
137                }
138            }
139        });
140    });
141
142    // Card hover effects - removed to prevent layout shift
143    // Cards now use CSS-only hover effects (box-shadow changes only)
144
145    // Lazy loading images
146    if ('loading' in HTMLImageElement.prototype) {
147        const images = document.querySelectorAll('img[loading="lazy"]');
148        images.forEach(img => {
149            if (img.dataset.src) {
150                img.src = img.dataset.src;
151            }
152        });
153    } else {
154        // Fallback for browsers that don't support lazy loading
155        const script = document.createElement('script');
156        script.src = '/assets/vendor/lazysizes-5.3.2/lazysizes.min.js';
157        script.onerror = function() {
158            const fallbackScript = document.createElement('script');
159            fallbackScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js';
160            document.body.appendChild(fallbackScript);
161        };
162        document.body.appendChild(script);
163    }
164
165    // Intersection Observer for fade-in animations
166    // Respect prefers-reduced-motion (using variable declared above)
167    if (!prefersReducedMotion) {
168        const observerOptions = {
169            threshold: 0.1,
170            rootMargin: '0px 0px -50px 0px'
171        };
172
173        const observer = new IntersectionObserver(function(entries) {
174            entries.forEach(entry => {
175                if (entry.isIntersecting) {
176                    entry.target.style.opacity = '1';
177                    entry.target.style.transform = 'translateY(0)';
178                }
179            });
180        }, observerOptions);
181
182        // Observe elements for animation
183        document.querySelectorAll('.card, .home-section__widgets > *').forEach(el => {
184            el.style.opacity = '0';
185            el.style.transform = 'translateY(20px)';
186            el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
187            observer.observe(el);
188        });
189    }
190
191    // Product gallery interactions
192    const galleryViewport = document.querySelector('.product-gallery__viewport');
193    if (galleryViewport) {
194        const slides = Array.from(galleryViewport.querySelectorAll('.product-gallery__slide'));
195        const thumbButtons = Array.from(document.querySelectorAll('.product-gallery__thumb'));
196
197        const setActiveSlide = (index) => {
198            slides.forEach((slide, slideIndex) => {
199                slide.classList.toggle('product-gallery__slide--active', slideIndex === index);
200            });
201
202            thumbButtons.forEach((thumb, thumbIndex) => {
203                thumb.classList.toggle('product-gallery__thumb--active', thumbIndex === index);
204            });
205        };
206
207        thumbButtons.forEach((thumb, index) => {
208            thumb.addEventListener('click', () => setActiveSlide(index));
209        });
210    }
211    // ============================================
212    // Listing View Toggle (Grid / List)
213    // ============================================
214    document.querySelectorAll('.listing-view-toggle').forEach(toggleGroup => {
215        const targetId = toggleGroup.dataset.listingTarget;
216        const container = targetId ? document.getElementById(targetId) : null;
217        if (!container) return;
218
219        const storageKey = 'polycms_view_' + targetId;
220        const buttons = toggleGroup.querySelectorAll('.view-toggle-btn');
221
222        // Restore saved preference
223        const saved = localStorage.getItem(storageKey);
224        if (saved && (saved === 'grid' || saved === 'list')) {
225            container.classList.remove('is-grid', 'is-list');
226            container.classList.add('is-' + saved);
227            buttons.forEach(btn => {
228                btn.classList.toggle('active', btn.dataset.view === saved);
229            });
230        }
231
232        buttons.forEach(btn => {
233            btn.addEventListener('click', () => {
234                const view = btn.dataset.view;
235                container.classList.remove('is-grid', 'is-list');
236                container.classList.add('is-' + view);
237
238                buttons.forEach(b => b.classList.remove('active'));
239                btn.classList.add('active');
240
241                localStorage.setItem(storageKey, view);
242            });
243        });
244    });
245
246    // ============================================
247    // Post Scroll Tools
248    // ============================================
249    const scrollToTopBtn = document.getElementById('scroll-to-top');
250    if (scrollToTopBtn) {
251        scrollToTopBtn.addEventListener('click', () => {
252            window.scrollTo({
253                top: 0,
254                behavior: prefersReducedMotion ? 'auto' : 'smooth'
255            });
256        });
257    }
258
259    const scrollToBottomBtn = document.getElementById('scroll-to-bottom');
260    if (scrollToBottomBtn) {
261        scrollToBottomBtn.addEventListener('click', () => {
262            window.scrollTo({
263                top: document.body.scrollHeight,
264                behavior: prefersReducedMotion ? 'auto' : 'smooth'
265            });
266        });
267    }
268
269    // Close admin card edit dropdowns when clicking outside
270    document.addEventListener('click', function(e) {
271        if (!e.target.closest('.admin-card-edit-dropdown')) {
272            document.querySelectorAll('.admin-card-edit-menu').forEach(function(m) {
273                m.style.display = 'none';
274            });
275        }
276    });
277
278    console.log('PolyCMS Corporate Theme loaded successfully!');
279})();
280

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.