PageSourceSearch

https://www.parfumbella.nl/wp-content/themes/parfumbella/assets/js/main.js?ver=1.0.3

js parfumbella.nl collected 2026-10-08 11:22:53 UTC 12,808 bytes, 375 lines download raw bytes

1/**
2 * Parfumbella Theme JavaScript
3 *
4 * @package Parfumbella
5 */
6
7(function($) {
8    'use strict';
9
10    /**
11     * Theme Toggle
12     */
13    function initThemeToggle() {
14        const toggleButtons = document.querySelectorAll('.theme-toggle');
15
16        toggleButtons.forEach(function(button) {
17            button.addEventListener('click', function() {
18                const currentTheme = document.documentElement.getAttribute('data-theme');
19                const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
20
21                document.documentElement.setAttribute('data-theme', newTheme);
22                localStorage.setItem('theme', newTheme);
23            });
24        });
25    }
26
27    /**
28     * Mobile Menu
29     */
30    function initMobileMenu() {
31        const mobileMenu = document.getElementById('mobile-menu');
32        const openButton = document.querySelector('.mobile-menu-toggle');
33        const closeButton = document.querySelector('.mobile-menu-close');
34        const backdrop = document.querySelector('.mobile-menu-backdrop');
35
36        if (!mobileMenu || !openButton) return;
37
38        function openMenu() {
39            mobileMenu.setAttribute('aria-hidden', 'false');
40            openButton.setAttribute('aria-expanded', 'true');
41            document.body.style.overflow = 'hidden';
42        }
43
44        function closeMenu() {
45            mobileMenu.setAttribute('aria-hidden', 'true');
46            openButton.setAttribute('aria-expanded', 'false');
47            document.body.style.overflow = '';
48        }
49
50        openButton.addEventListener('click', openMenu);
51
52        if (closeButton) {
53            closeButton.addEventListener('click', closeMenu);
54        }
55
56        if (backdrop) {
57            backdrop.addEventListener('click', closeMenu);
58        }
59
60        // Close on Escape key
61        document.addEventListener('keydown', function(e) {
62            if (e.key === 'Escape' && mobileMenu.getAttribute('aria-hidden') === 'false') {
63                closeMenu();
64            }
65        });
66
67        // Handle submenu toggles in mobile menu
68        const menuItemsWithChildren = document.querySelectorAll('.mobile-nav-menu .menu-item-has-children > a');
69
70        menuItemsWithChildren.forEach(function(link) {
71            link.addEventListener('click', function(e) {
72                e.preventDefault();
73                const parent = this.parentElement;
74                const subMenu = parent.querySelector('.sub-menu');
75
76                if (subMenu) {
77                    parent.classList.toggle('is-open');
78                    subMenu.style.display = parent.classList.contains('is-open') ? 'block' : 'none';
79                }
80            });
81        });
82    }
83
84    /**
85     * Footer Accordion (Mobile)
86     */
87    function initFooterAccordion() {
88        if (window.innerWidth > 767) return;
89
90        const footerTitles = document.querySelectorAll('.footer-widget-title');
91
92        footerTitles.forEach(function(title) {
93            title.addEventListener('click', function() {
94                const content = this.nextElementSibling;
95
96                // Close other open sections
97                footerTitles.forEach(function(otherTitle) {
98                    if (otherTitle !== title) {
99                        otherTitle.classList.remove('active');
100                        const otherContent = otherTitle.nextElementSibling;
101                        if (otherContent) {
102                            otherContent.style.display = 'none';
103                        }
104                    }
105                });
106
107                // Toggle current section
108                this.classList.toggle('active');
109                if (content) {
110                    content.style.display = this.classList.contains('active') ? 'block' : 'none';
111                }
112            });
113        });
114    }
115
116    /**
117     * FAQ Accordion (Smooth Animation)
118     */
119    function initFaqAccordion() {
120        const faqItems = document.querySelectorAll('.faq-item');
121
122        faqItems.forEach(function(item) {
123            const summary = item.querySelector('.faq-question');
124            const answer = item.querySelector('.faq-answer');
125
126            if (!summary || !answer) return;
127
128            // Set initial height for closed state
129            answer.style.maxHeight = '0px';
130            answer.style.overflow = 'hidden';
131            answer.style.transition = 'max-height 0.3s ease, padding 0.3s ease';
132            answer.style.paddingTop = '0';
133            answer.style.paddingBottom = '0';
134
135            summary.addEventListener('click', function(e) {
136                e.preventDefault();
137
138                const isOpen = item.hasAttribute('open');
139
140                if (isOpen) {
141                    // Closing
142                    answer.style.maxHeight = '0px';
143                    answer.style.paddingTop = '0';
144                    answer.style.paddingBottom = '0';
145
146                    setTimeout(function() {
147                        item.removeAttribute('open');
148                    }, 300);
149                } else {
150                    // Opening
151                    item.setAttribute('open', '');
152                    answer.style.maxHeight = answer.scrollHeight + 32 + 'px';
153                    answer.style.paddingTop = '';
154                    answer.style.paddingBottom = '';
155                }
156            });
157        });
158    }
159
160    /**
161     * Product Tabs Accordion (Mobile)
162     */
163    function initProductTabsAccordion() {
164        if (window.innerWidth > 767) return;
165
166        const tabPanels = document.querySelectorAll('.woocommerce-tabs .woocommerce-Tabs-panel');
167
168        if (!tabPanels.length) return;
169
170        // Show first tab by default
171        if (tabPanels[0]) {
172            tabPanels[0].classList.add('active');
173        }
174
175        tabPanels.forEach(function(panel) {
176            // Create clickable header from tab label
177            const tabId = panel.getAttribute('id');
178            const tabLink = document.querySelector('.woocommerce-tabs .tabs a[href="#' + tabId + '"]');
179
180            if (!tabLink) return;
181
182            const header = document.createElement('div');
183            header.className = 'accordion-tab-header';
184            header.textContent = tabLink.textContent;
185            header.style.cssText = 'padding: var(--spacing-md); background: var(--color-surface); border: 1px solid var(--color-border); font-weight: var(--font-weight-semibold); cursor: pointer; margin-bottom: 0;';
186
187            panel.parentNode.insertBefore(header, panel);
188
189            // Update active state styling
190            if (panel.classList.contains('active')) {
191                header.style.background = 'var(--color-primary)';
192                header.style.color = 'var(--color-white)';
193            }
194
195            header.addEventListener('click', function() {
196                const isActive = panel.classList.contains('active');
197
198                // Close all panels
199                tabPanels.forEach(function(p) {
200                    p.classList.remove('active');
201                    const h = p.previousElementSibling;
202                    if (h && h.classList.contains('accordion-tab-header')) {
203                        h.style.background = 'var(--color-surface)';
204                        h.style.color = '';
205                    }
206                });
207
208                // Toggle current panel
209                if (!isActive) {
210                    panel.classList.add('active');
211                    header.style.background = 'var(--color-primary)';
212                    header.style.color = 'var(--color-white)';
213                }
214            });
215        });
216    }
217
218    /**
219     * Product Thumbnail Slider Navigation
220     */
221    function initThumbSlider() {
222        const thumbContainer = document.querySelector('.woocommerce-product-gallery ol.flex-control-thumbs');
223
224        if (!thumbContainer || window.innerWidth <= 767) return;
225
226        const gallery = document.querySelector('.woocommerce-product-gallery');
227        if (!gallery) return;
228
229        // Create navigation arrows
230        const prevArrow = document.createElement('button');
231        prevArrow.className = 'thumb-nav-arrow thumb-nav-prev';
232        prevArrow.innerHTML = '&larr;';
233        prevArrow.setAttribute('aria-label', 'Previous thumbnails');
234
235        const nextArrow = document.createElement('button');
236        nextArrow.className = 'thumb-nav-arrow thumb-nav-next';
237        nextArrow.innerHTML = '&rarr;';
238        nextArrow.setAttribute('aria-label', 'Next thumbnails');
239
240        gallery.appendChild(prevArrow);
241        gallery.appendChild(nextArrow);
242
243        // Scroll amount (one thumbnail width + gap)
244        const scrollAmount = 116; // 100px thumb + 16px gap
245
246        function updateArrows() {
247            const scrollLeft = thumbContainer.scrollLeft;
248            const maxScroll = thumbContainer.scrollWidth - thumbContainer.clientWidth;
249
250            prevArrow.classList.toggle('disabled', scrollLeft <= 0);
251            nextArrow.classList.toggle('disabled', scrollLeft >= maxScroll);
252        }
253
254        prevArrow.addEventListener('click', function() {
255            thumbContainer.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
256        });
257
258        nextArrow.addEventListener('click', function() {
259            thumbContainer.scrollBy({ left: scrollAmount, behavior: 'smooth' });
260        });
261
262        thumbContainer.addEventListener('scroll', updateArrows);
263        updateArrows();
264    }
265
266    /**
267     * Sticky Header Shadow
268     */
269    function initStickyHeader() {
270        const header = document.querySelector('.site-header');
271        if (!header) return;
272
273        window.addEventListener('scroll', function() {
274            if (window.scrollY > 10) {
275                header.classList.add('is-scrolled');
276            } else {
277                header.classList.remove('is-scrolled');
278            }
279        });
280    }
281
282    /**
283     * Variable Product Radio Selector
284     */
285    function initVariationRadios() {
286        var $form = $('.variations_form');
287        if (!$form.length) return;
288
289        var $rows = $form.find('.variation-radio-row');
290        if (!$rows.length) return;
291
292        // Select first available variation on load
293        var $firstAvailable = $rows.not('.out-of-stock').first();
294        if ($firstAvailable.length) {
295            selectVariation($firstAvailable);
296        }
297
298        // Handle radio row click
299        $rows.on('click', function() {
300            if ($(this).hasClass('out-of-stock')) return;
301            selectVariation($(this));
302        });
303
304        function selectVariation($row) {
305            var $radio = $row.find('input[type="radio"]');
306            var attrName = $row.closest('.variation-radio-group').data('attribute');
307            var value = $radio.val();
308
309            // Update UI
310            $rows.removeClass('is-selected');
311            $row.addClass('is-selected');
312            $radio.prop('checked', true);
313
314            // Sync with hidden WooCommerce dropdown
315            var $select = $form.find('select[name="' + $radio.attr('name') + '"]');
316            if ($select.length) {
317                $select.val(value).trigger('change');
318            }
319        }
320    }
321
322    /**
323     * Sidebar Filter Search
324     */
325    function initFilterSearch() {
326        var inputs = document.querySelectorAll('.filter-search-input');
327        inputs.forEach(function(input) {
328            var listId = input.getAttribute('data-list');
329            var list = document.getElementById(listId);
330            if (!list) return;
331
332            input.addEventListener('input', function() {
333                var query = this.value.toLowerCase();
334                var items = list.querySelectorAll('li');
335                items.forEach(function(item) {
336                    var text = item.textContent.toLowerCase();
337                    item.style.display = text.indexOf(query) !== -1 ? '' : 'none';
338                });
339            });
340        });
341    }
342
343    /**
344     * Initialize on DOM Ready
345     */
346    document.addEventListener('DOMContentLoaded', function() {
347        initThemeToggle();
348        initMobileMenu();
349        initFooterAccordion();
350        initFaqAccordion();
351        initProductTabsAccordion();
352        initThumbSlider();
353        initStickyHeader();
354        initVariationRadios();
355        initFilterSearch();
356    });
357
358    /**
359     * Reinitialize on window resize
360     */
361    let resizeTimer;
362    window.addEventListener('resize', function() {
363        clearTimeout(resizeTimer);
364        resizeTimer = setTimeout(function() {
365            // Reinit footer accordion based on screen size
366            const footerContents = document.querySelectorAll('.footer-content');
367            if (window.innerWidth > 767) {
368                footerContents.forEach(function(content) {
369                    content.style.display = '';
370                });
371            }
372        }, 250);
373    });
374
375})(jQuery);

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.