PageSourceSearch

https://shop.schulshop.at/wp-content/themes/lorentschitsch-shop/assets/js/theme.js?ver=2.4.0

js schulshop.at collected 2026-10-07 01:18:30 UTC 22,029 bytes, 536 lines download raw bytes

1/**
2 * LorShop Theme JavaScript
3 * Mobile menu, search, account dropdown, categories mega menu, hero carousel,
4 * product tabs, cart sidebar, quantity controls (single + card grid)
5 *
6 * @package LorShop
7 */
8
9(function() {
10    'use strict';
11
12    function init() {
13
14    // ================================================
15    // Mobile Menu
16    // ================================================
17    var mobileMenuToggle = document.getElementById('mobile-menu-toggle');
18    var mobileMenu = document.getElementById('mobile-menu');
19
20    if (mobileMenuToggle && mobileMenu) {
21        mobileMenuToggle.addEventListener('click', function() {
22            mobileMenu.classList.toggle('hidden');
23        });
24    }
25
26    // ================================================
27    // Mobile Search
28    // ================================================
29    var mobileSearchToggle = document.getElementById('mobile-search-toggle');
30    var mobileSearch = document.getElementById('mobile-search');
31
32    if (mobileSearchToggle && mobileSearch) {
33        mobileSearchToggle.addEventListener('click', function() {
34            mobileSearch.classList.toggle('hidden');
35            if (!mobileSearch.classList.contains('hidden')) {
36                var input = mobileSearch.querySelector('input');
37                if (input) input.focus();
38            }
39        });
40    }
41
42    // ================================================
43    // Account Dropdown
44    // ================================================
45    var accountToggle = document.getElementById('account-toggle');
46    var accountMenu = document.getElementById('account-menu');
47
48    if (accountToggle && accountMenu) {
49        accountToggle.addEventListener('click', function(e) {
50            e.stopPropagation();
51            accountMenu.classList.toggle('hidden');
52        });
53
54        document.addEventListener('click', function(e) {
55            if (!accountMenu.contains(e.target) && !accountToggle.contains(e.target)) {
56                accountMenu.classList.add('hidden');
57            }
58        });
59    }
60
61    // ================================================
62    // Categories Mega Menu
63    // ================================================
64    var categoriesToggle = document.getElementById('categories-toggle');
65    var categoriesDropdown = document.getElementById('categories-dropdown');
66
67    if (categoriesToggle && categoriesDropdown) {
68        categoriesToggle.addEventListener('click', function(e) {
69            e.stopPropagation();
70            categoriesDropdown.classList.toggle('hidden');
71        });
72
73        document.addEventListener('click', function(e) {
74            if (!categoriesDropdown.contains(e.target) && !categoriesToggle.contains(e.target)) {
75                categoriesDropdown.classList.add('hidden');
76            }
77        });
78    }
79
80    // ================================================
81    // Global WooCommerce Notices (floating + auto-hide)
82    // ================================================
83    var globalNotices = document.getElementById('lorshop-global-notices');
84    if (globalNotices) {
85        var noticeItems = globalNotices.querySelectorAll('.woocommerce-message, .woocommerce-info, .woocommerce-error');
86
87        function dismissNotice(item) {
88            if (!item || item.classList.contains('lorshop-notice-hide')) return;
89            item.classList.add('lorshop-notice-hide');
90            setTimeout(function() {
91                item.remove();
92                if (!globalNotices.querySelector('.woocommerce-message, .woocommerce-info, .woocommerce-error')) {
93                    globalNotices.remove();
94                }
95            }, 260);
96        }
97
98        noticeItems.forEach(function(item) {
99            var closeBtn = document.createElement('button');
100            closeBtn.type = 'button';
101            closeBtn.className = 'lorshop-notice-close';
102            closeBtn.setAttribute('aria-label', 'Hinweis schließen');
103            closeBtn.textContent = '×';
104            closeBtn.addEventListener('click', function() {
105                dismissNotice(item);
106            });
107            item.appendChild(closeBtn);
108
109            var timeout = setTimeout(function() {
110                dismissNotice(item);
111            }, 6500);
112
113            item.addEventListener('mouseenter', function() {
114                clearTimeout(timeout);
115            });
116
117            item.addEventListener('mouseleave', function() {
118                timeout = setTimeout(function() {
119                    dismissNotice(item);
120                }, 2500);
121            });
122        });
123    }
124
125    // ================================================
126    // Hero Carousel (image-only, progress-bar dots, scale transitions)
127    // ================================================
128    var carousel = document.getElementById('hero-carousel');
129    if (carousel) {
130        var slides = carousel.querySelectorAll('.carousel-slide');
131        var dots = carousel.querySelectorAll('.carousel-dot');
132        var prevBtn = carousel.querySelector('.carousel-prev');
133        var nextBtn = carousel.querySelector('.carousel-next');
134        var currentSlide = 0;
135        var autoplayTimer;
136        var interval = parseInt(carousel.getAttribute('data-interval')) || 6000;
137
138        function goToSlide(index) {
139            // Update slides (scale + opacity transition)
140            slides.forEach(function(slide, i) {
141                if (i === index) {
142                    slide.classList.remove('opacity-0', 'scale-[1.08]');
143                    slide.classList.add('opacity-100', 'scale-100');
144                } else {
145                    slide.classList.remove('opacity-100', 'scale-100');
146                    slide.classList.add('opacity-0', 'scale-[1.08]');
147                }
148            });
149
150            // Update progress-bar dots
151            dots.forEach(function(dot, i) {
152                var progressBar = dot.querySelector('.carousel-progress');
153
154                if (i === index) {
155                    dot.classList.add('active');
156                    dot.style.width = '40px';
157
158                    // Create or restart progress bar
159                    if (!progressBar) {
160                        progressBar = document.createElement('div');
161                        progressBar.className = 'carousel-progress absolute inset-0 rounded-full';
162                        progressBar.style.backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--lorshop-primary').trim() || '#e30513';
163                        dot.appendChild(progressBar);
164                    }
165                    // Restart animation
166                    progressBar.style.animation = 'none';
167                    // Force reflow
168                    void progressBar.offsetWidth;
169                    progressBar.style.animation = 'progress-bar ' + interval + 'ms linear';
170                } else {
171                    dot.classList.remove('active');
172                    dot.style.width = '12px';
173                    if (progressBar) {
174                        progressBar.style.animation = 'none';
175                    }
176                }
177            });
178
179            currentSlide = index;
180        }
181
182        function nextSlide() {
183            goToSlide((currentSlide + 1) % slides.length);
184        }
185
186        function prevSlide() {
187            goToSlide((currentSlide - 1 + slides.length) % slides.length);
188        }
189
190        function startAutoplay() {
191            stopAutoplay();
192            autoplayTimer = setInterval(nextSlide, interval);
193        }
194
195        function stopAutoplay() {
196            if (autoplayTimer) {
197                clearInterval(autoplayTimer);
198                autoplayTimer = null;
199            }
200        }
201
202        if (prevBtn) prevBtn.addEventListener('click', function() { stopAutoplay(); prevSlide(); startAutoplay(); });
203        if (nextBtn) nextBtn.addEventListener('click', function() { stopAutoplay(); nextSlide(); startAutoplay(); });
204
205        dots.forEach(function(dot) {
206            dot.addEventListener('click', function() {
207                stopAutoplay();
208                goToSlide(parseInt(this.getAttribute('data-goto')));
209                startAutoplay();
210            });
211        });
212
213        // Only autoplay if more than 1 slide
214        if (slides.length > 1) {
215            startAutoplay();
216        }
217
218        // Pause on hover
219        carousel.addEventListener('mouseenter', stopAutoplay);
220        carousel.addEventListener('mouseleave', function() {
221            if (slides.length > 1) startAutoplay();
222        });
223    }
224
225    // ================================================
226    // Product Image Switcher
227    // ================================================
228    window.switchImage = function(thumb) {
229        var mainImage = document.getElementById('product-main-image');
230        if (!mainImage || !thumb) return;
231        
232        var newSrc = thumb.getAttribute('data-image');
233        mainImage.src = newSrc;
234
235        // Update active thumbnail border
236        document.querySelectorAll('.product-thumb').forEach(function(t) {
237            t.classList.remove('border-blue-500', 'active');
238            t.classList.add('border-gray-200');
239        });
240        thumb.classList.remove('border-gray-200');
241        thumb.classList.add('border-blue-500', 'active');
242    };
243
244    // ================================================
245    // Product Tabs
246    // ================================================
247    var tabBtns = document.querySelectorAll('.product-tab-btn');
248    var tabContents = document.querySelectorAll('.product-tab-content');
249
250    tabBtns.forEach(function(btn) {
251        btn.addEventListener('click', function() {
252            var tab = this.getAttribute('data-tab');
253            var primary = getComputedStyle(document.documentElement).getPropertyValue('--lorshop-primary').trim();
254
255            // Update buttons
256            tabBtns.forEach(function(b) {
257                b.classList.remove('active');
258                b.style.borderColor = 'transparent';
259                b.style.color = '#6b7280';
260            });
261            this.classList.add('active');
262            this.style.borderColor = primary;
263            this.style.color = primary;
264
265            // Update content
266            tabContents.forEach(function(content) {
267                if (content.getAttribute('data-tab') === tab) {
268                    content.classList.remove('hidden');
269                } else {
270                    content.classList.add('hidden');
271                }
272            });
273        });
274    });
275
276    // ================================================
277    // Quantity Controls — Single Product Page
278    // ================================================
279    var qtyMinus = document.querySelector('.qty-minus');
280    var qtyPlus = document.querySelector('.qty-plus');
281    var qtyInput = document.getElementById('product-qty');
282
283    if (qtyMinus && qtyPlus && qtyInput) {
284        qtyMinus.addEventListener('click', function() {
285            var val = parseInt(qtyInput.value) || 1;
286            if (val > 1) qtyInput.value = val - 1;
287        });
288        qtyPlus.addEventListener('click', function() {
289            var val = parseInt(qtyInput.value) || 1;
290            var max = parseInt(qtyInput.getAttribute('max'));
291            if (!max || val < max) qtyInput.value = val + 1;
292        });
293    }
294
295    // ================================================
296    // Quantity Controls — Product Card Grid
297    // ================================================
298    document.querySelectorAll('.qty-minus[data-product]').forEach(function(btn
298) {
299        btn.addEventListener('click', function(e) {
300            e.preventDefault();
301            e.stopPropagation();
302            var productId = this.getAttribute('data-product');
303            var display = document.querySelector('.qty-display[data-product="' + productId + '"]');
304            if (!display) return;
305            var val = parseInt(display.textContent) || 1;
306            if (val > 1) display.textContent = val - 1;
307        });
308    });
309
310    document.querySelectorAll('.qty-plus[data-product]').forEach(function(btn) {
311        btn.addEventListener('click', function(e) {
312            e.preventDefault();
313            e.stopPropagation();
314            var productId = this.getAttribute('data-product');
315            var display = document.querySelector('.qty-display[data-product="' + productId + '"]');
316            if (!display) return;
317            var val = parseInt(display.textContent) || 1;
318            display.textContent = val + 1;
319        });
320    });
321
322    // ================================================
323    // Floating toast (shared with WooCommerce notice styling)
324    // ================================================
325    function lorshopShowToast(message, type) {
326        var container = document.getElementById('lorshop-global-notices');
327        if (!container) {
328            container = document.createElement('div');
329            container.id = 'lorshop-global-notices';
330            container.className = 'lorshop-global-notices';
331            container.setAttribute('aria-live', 'polite');
332            container.setAttribute('aria-atomic', 'true');
333            container.appendChild(Object.assign(document.createElement('div'), { className: 'woocommerce-notices-wrapper' }));
334            document.body.appendChild(container);
335        }
336        var wrapper = container.querySelector('.woocommerce-notices-wrapper') || container;
337
338        var notice = document.createElement('div');
339        notice.className = 'woocommerce-' + (type || 'message');
340        notice.setAttribute('role', 'alert');
341        notice.textContent = message;
342        wrapper.appendChild(notice);
343
344        var dismiss = function() {
345            if (notice.classList.contains('lorshop-notice-hide')) return;
346            notice.classList.add('lorshop-notice-hide');
347            setTimeout(function() {
348                notice.remove();
349                if (!container.querySelector('.woocommerce-message, .woocommerce-info, .woocommerce-error')) container.remove();
350            }, 260);
351        };
352        var closeBtn = document.createElement('button');
353        closeBtn.type = 'button';
354        closeBtn.className = 'lorshop-notice-close';
355        closeBtn.setAttribute('aria-label', 'Hinweis schließen');
356        closeBtn.textContent = '×';
357        closeBtn.addEventListener('click', dismiss);
358        notice.appendChild(closeBtn);
359
360        var timeout = setTimeout(dismiss, 6500);
361        notice.addEventListener('mouseenter', function() { clearTimeout(timeout); });
362        notice.addEventListener('mouseleave', function() { timeout = setTimeout(dismiss, 2500); });
363    }
364    window.lorshopShowToast = lorshopShowToast;
365
366    // ================================================
367    // Add-to-cart on card grid (AJAX + loading state)
368    // ================================================
369    var spinnerSvg = '<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>';
370    var checkSvg = '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>';
371
372    document.querySelectorAll('.add-to-cart-btn[data-product-id]').forEach(function(btn) {
373        btn.addEventListener('click', function(e) {
374            e.preventDefault();
375            if (btn.getAttribute('data-loading') === '1') return;
376
377            var productId = btn.getAttribute('data-product-id');
378            var productName = btn.getAttribute('data-product-name') || '';
379            var display = document.querySelector('.qty-display[data-product="' + productId + '"]');
380            var qty = display ? parseInt(display.textContent) || 1 : 1;
381            var originalHtml = btn.innerHTML;
382
383            // Loading state inside the button.
384            btn.setAttribute('data-loading', '1');
385            btn.style.pointerEvents = 'none';
386            btn.style.opacity = '0.85';
387            btn.innerHTML = spinnerSvg + '<span>Hinzufügen…</span>';
388
389            var body = new URLSearchParams();
390            body.append('product_id', productId);
391            body.append('quantity', qty);
392
393            fetch('/?wc-ajax=add_to_cart', {
394                method: 'POST',
395                credentials: 'same-origin',
396                headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' },
397                body: body.toString()
398            })
399            .then(function(r) { return r.json(); })
400            .then(function(data) {
401                // Variation required / other server-side redirect.
402                if (data && data.error && data.product_url) {
403                    window.location.href = data.product_url;
404                    return;
405                }
406
407                // Apply WooCommerce fragments (mini-cart sidebar, header count, …).
408                if (data && data.fragments) {
409                    Object.keys(data.fragments).forEach(function(sel) {
410                        document.querySelectorAll(sel).forEach(function(el) {
411                            var tmp = document.createElement('div');
412                            tmp.innerHTML = data.fragments[sel];
413                            if (tmp.firstElementChild) el.replaceWith(tmp.firstElementChild);
414                        });
415                    });
416                    document.body.dispatchEvent(new CustomEvent('wc_fragments_refreshed'));
417                }
418
419                // Success feedback: button check + floating toast.
420                btn.innerHTML = checkSvg + '<span>Hinzugefügt</span>';
421                lorshopShowToast((productName ? productName + ' ' : '') + 'wurde in den Warenkorb gelegt.', 'message');
422
423                setTimeout(function() {
424                    btn.innerHTML = originalHtml;
425                    btn.removeAttribute('data-loading');
426                    btn.style.pointerEvents = '';
427                    btn.style.opacity = '';
428                }, 1600);
429            })
430            .catch(function() {
431                // Network/parse failure → fall back to the standard add-to-cart URL.
432                window.location.href = btn.href;
433            });
434        });
435    });
436
437    // ================================================
438    // Cart Sidebar
439    // ================================================
440    window.toggleCartSidebar = function() {
441        var sidebar = document.getElementById('cart-sidebar');
442        if (!sidebar) return;
443
444        if (sidebar.classList.contains('open')) {
445            sidebar.classList.remove('open');
446            document.body.style.overflow = '';
447            setTimeout(function() { sidebar.classList.add('hidden'); }, 300);
448        } else {
449            sidebar.classList.remove('hidden');
450            // Force reflow
451            sidebar.offsetHeight;
452            sidebar.classList.add('open');
453            document.body.style.overflow = 'hidden';
454        }
455    };
456
457    var cartBtn = document.getElementById('header-cart-btn');
458    if (cartBtn) {
459        cartBtn.addEventListener('click', function(e) {
460            e.preventDefault();
461            window.toggleCartSidebar();
462        });
463    }
464
465    // ================================================
466    // Close dropdowns/menus on Escape
467    // ================================================
468    document.addEventListener('keydown', function(e) {
469        if (e.key === 'Escape') {
470            // Close account dropdown
471            if (accountMenu && !accountMenu.classList.contains('hidden')) {
472                accountMenu.classList.add('hidden');
473            }
474            // Close categories dropdown
475            if (categoriesDropdown && !categoriesDropdown.classList.contains('hidden')) {
476                categoriesDropdown.classList.add('hidden');
477            }
478            // Close mobile menu
479            if (mobileMenu && !mobileMenu.classList.contains('hidden')) {
480                mobileMenu.classList.add('hidden');
481            }
482            // Close cart sidebar
483            var sidebar = document.getElementById('cart-sidebar');
484            if (sidebar && sidebar.classList.contains('open')) {
485                window.toggleCartSidebar();
486            }
487        }
488    });
489
490    // ================================================
491    // Scroll Reveal (IntersectionObserver)
492    // ================================================
493    if ('IntersectionObserver' in window && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
494        var revealObserver = new IntersectionObserver(function(entries) {
495            entries.forEach(function(entry) {
496                if (entry.isIntersecting) {
497                    entry.target.classList.add('is-visible');
498                    revealObserver.unobserve(entry.target);
499                }
500            });
501        }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
502
503        document.querySelectorAll('.ls-reveal').forEach(function(el) {
504            revealObserver.observe(el);
505        });
506    } else {
507        document.querySelectorAll('.ls-reveal').forEach(function(el) {
508            el.classList.add('is-visible');
509        });
510    }
511
512    // ================================================
513    // Header scroll effect (subtle shadow)
514    // ================================================
515    var header = document.getElementById('site-header');
516    if (header) {
517        window.addEventListener('scroll', function() {
518            var scrollY = window.pageYOffset || document.documentElement.scrollTop;
519            if (scrollY > 10) {
520                header.style.boxShadow = '0 4px 20px rgba(0,0,0,0.15)';
521            } else {
522                header.style.boxShadow = '';
523            }
524        }, { passive: true });
525    }
526
527    }
528
529    // Run when DOM is ready
530    if (document.readyState === 'loading') {
531        document.addEventListener('DOMContentLoaded', init);
532    } else {
533        init();
534    }
535
536})();

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.