PageSourceSearch

https://www.lsc-erftland.de/themes/custom/lsc_new/js/main.js?tk0jqt

js lsc-erftland.de collected 2026-10-06 02:25:47 UTC 11,531 bytes, 364 lines download raw bytes

1/**
2 * LSC New Theme - Main JavaScript
3 * Kunco-inspired navigation and interactions
4 */
5
6(function (Drupal, once) {
7  'use strict';
8
9  /**
10   * Offcanvas Menu Toggle
11   * Opens/closes mobile offcanvas menu
12   */
13  Drupal.behaviors.offcanvasMenu = {
14    attach: function (context) {
15      once('offcanvas-menu', '.menu-toggle', context).forEach(function (toggle) {
16        const menu = document.querySelector('.offcanvas-menu');
17        const overlay = document.querySelector('.offcanvas-overlay');
18        const closeBtn = document.querySelector('.offcanvas-close');
19
20        if (!menu || !overlay) return;
21
22        // Open menu
23        toggle.addEventListener('click', function (e) {
24          e.preventDefault();
25          e.stopPropagation();
26          
27          menu.classList.add('active');
28          overlay.classList.add('active');
29          document.body.classList.add('offcanvas-active');
30          toggle.setAttribute('aria-expanded', 'true');
31          
32          // Focus on close button for accessibility
33          setTimeout(function() {
34            if (closeBtn) closeBtn.focus();
35          }, 300);
36        });
37
38        // Close menu function
39        const closeMenu = function () {
40          menu.classList.remove('active');
41          overlay.classList.remove('active');
42          document.body.classList.remove('offcanvas-active');
43          toggle.setAttribute('aria-expanded', 'false');
44          
45          // Return focus to toggle button
46          toggle.focus();
47        };
48
49        // Close button click
50        if (closeBtn) {
51          closeBtn.addEventListener('click', function (e) {
52            e.preventDefault();
53            closeMenu();
54          });
55        }
56
57        // Overlay click
58        overlay.addEventListener('click', function () {
59          closeMenu();
60        });
61
62        // Close on ESC key
63        document.addEventListener('keydown', function (e) {
64          if (e.key === 'Escape' && menu.classList.contains('active')) {
65            closeMenu();
66          }
67        });
68      });
69    }
70  };
71
72  /**
73   * Offcanvas Submenu Accordion
74   * Expands/collapses submenu items in offcanvas menu
75   * Uses event delegation on document to ensure it works
76   */
77  Drupal.behaviors.offcanvasSubmenu = {
78    attach: function (context, settings) {
79      // Only attach once to the document
80      once('offcanvas-submenu', 'body', context).forEach(function () {
81        document.addEventListener('click', function (e) {
82          // Check if click is within offcanvas menu
83          const offcanvasMenu = e.target.closest('.offcanvas-menu');
84          if (!offcanvasMenu) return;
85
86          // Find the clicked link with data-submenu-toggle
87          const link = e.target.closest('[data-submenu-toggle]');
88          if (!link) return;
89
90          // Get the parent dropdown item
91          const item = link.parentElement;
92          if (!item || !item.classList.contains('dropdown')) return;
93
94          // Check if this dropdown has a submenu
95          const submenu = item.querySelector(':scope > .dropdown-menu');
96          if (!submenu) return;
97
98          // Prevent default and stop propagation
99          e.preventDefault();
100          e.stopPropagation();
101
102          // Close other open submenus at same level
103          const parent = item.parentElement;
104          if (parent) {
105            const siblings = Array.from(parent.children);
106            siblings.forEach(function (sibling) {
107              if (sibling !== item &&
108                  sibling.classList.contains('dropdown') &&
109                  sibling.classList.contains('active')) {
110                sibling.classList.remove('active');
111              }
112            });
113          }
114
115          // Toggle current submenu
116          item.classList.toggle('active');
117        }, true); // Use capture phase
118      });
119    }
120  };
121
122  /**
123   * Sticky Header with Scroll Effect
124   * Adds shadow when scrolling
125   */
126  Drupal.behaviors.stickyHeader = {
127    attach: function (context) {
128      once('sticky-header', '.site-header', context).forEach(function (header) {
129        let ticking = false;
130
131        const updateHeader = function () {
132          const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
133
134          if (currentScroll > 80) {
135            header.classList.add('scrolled');
136          } else {
137            header.classList.remove('scrolled');
138          }
139
140          ticking = false;
141        };
142
143        // Initial check
144        updateHeader();
145
146        window.addEventListener('scroll', function () {
147          if (!ticking) {
148            window.requestAnimationFrame(updateHeader);
149            ticking = true;
150          }
151        }, { passive: true });
152      });
153    }
154  };
155
156  /**
157   * More Menu Toggle (Desktop 3-dots)
158   * Optional: Can be used for additional menu options
159   */
160  Drupal.behaviors.moreMenuToggle = {
161    attach: function (context) {
162      once('more-menu', '.more-menu-toggle', context).forEach(function (toggle) {
163        toggle.addEventListener('click', function (e) {
164          e.preventDefault();
165          // Add your more menu functionality here
166          console.log('More menu clicked');
167        });
168      });
169    }
170  };
171
172  // Stats Counter Animation
173  Drupal.behaviors.statsCounter = {
174    attach: function (context) {
175      once('stats-counter', '.stat-number[data-count]', context).forEach(function (counter) {
176        const target = parseInt(counter.getAttribute('data-count'));
177        const duration = 2000; // 2 seconds
178        const increment = target / (duration / 16); // 60 FPS
179        let current = 0;
180
181        const observer = new IntersectionObserver(function (entries) {
182          entries.forEach(function (entry) {
183            if (entry.isIntersecting) {
184              const updateCounter = function () {
185                current += increment;
186                if (current < target) {
187                  counter.textContent = Math.ceil(current).toLocaleString();
188                  requestAnimationFrame(updateCounter);
189                } else {
190                  counter.textContent = target.toLocaleString();
191                }
192              };
193              updateCounter();
194              observer.unobserve(counter);
195            }
196          });
197        }, { threshold: 0.5 });
198
199        observer.observe(counter);
200      });
201    }
202  };
203
204  // Video Modal
205  Drupal.behaviors.videoModal = {
206    attach: function (context) {
207      once('video-modal', '.video-play-button', context).forEach(function (button) {
208        button.addEventListener('click', function () {
209          const videoUrl = button.getAttribute('data-video-url');
210          const modal = document.querySelector('.video-modal');
211          const modalContent = modal.querySelector('.video-modal-content');
212          const closeBtn = modal.querySelector('.video-modal-close');
213
214          if (!modal || !videoUrl) return;
215
216          // Create iframe
217          const iframe = document.createElement('iframe');
218          iframe.src = videoUrl + '?autoplay=1';
219          iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
220          iframe.allowFullscreen = true;
221
222          // Add iframe to modal
223          modalContent.innerHTML = '';
224          modalContent.appendChild(iframe);
225
226          // Show modal
227          modal.classList.add('active');
228          document.body.style.overflow = 'hidden';
229
230          // Close modal
231          const closeModal = function () {
232            modal.classList.remove('active');
233            modalContent.innerHTML = '';
234            document.body.style.overflow = '';
235          };
236
237          closeBtn.addEventListener('click', closeModal);
238
239          modal.addEventListener('click', function (e) {
240            if (e.target === modal) {
241              closeModal();
242            }
243          });
244
245          // Close on ESC
246          document.addEventListener('keydown', function (e) {
247            if (e.key === 'Escape' && modal.classList.contains('active')) {
248              closeModal();
249            }
250          });
251        });
252      });
253    }
254  };
255
256  // Progress Bar Animation
257  Drupal.behaviors.progressBar = {
258    attach: function (context) {
259      once('progress-bar', '.progress-bar-fill', context).forEach(function (bar) {
260        const percentage = bar.getAttribute('data-percentage');
261
262        const observer = new IntersectionObserver(function (entries) {
263          entries.forEach(function (entry) {
264            if (entry.isIntersecting) {
265              setTimeout(function () {
266                bar.style.width = percentage + '%';
267              }, 100);
268              observer.unobserve(bar);
269            }
270          });
271        }, { threshold: 0.5 });
272
273        observer.observe(bar);
274      });
275    }
276  };
277
278  // Smooth Scroll for Anchor Links
279  Drupal.behaviors.smoothScroll = {
280    attach: function (context) {
281      once('smooth-scroll', 'a[href^="#"]', context).forEach(function (link) {
282        link.addEventListener('click', function (e) {
283          const targetId = this.getAttribute('href');
284          if (targetId === '#') return;
285
286          const target = document.querySelector(targetId);
287          if (target) {
288            e.preventDefault();
289            const headerHeight = document.querySelector('.site-header').offsetHeight;
290            const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight;
291
292            window.scrollTo({
293              top: targetPosition,
294              behavior: 'smooth'
295            });
296          }
297        });
298      });
299    }
300  };
301
302  // Fade In Animation on Scroll
303  Drupal.behaviors.fadeInOnScroll = {
304    attach: function (context) {
305      once('fade-in', '.fade-in-up', context).forEach(function (element) {
306        const observer = new IntersectionObserver(function (entries) {
307          entries.forEach(function (entry) {
308            if (entry.isIntersecting) {
309              entry.target.style.opacity = '1';
310              entry.target.style.transform = 'translateY(0)';
311              observer.unobserve(entry.target);
312            }
313          });
314        }, { threshold: 0.1 });
315
316        element.style.opacity = '0';
317        element.style.transform = 'translateY(30px)';
318        element.style.transition = 'opacity 0.6s ease-out, transform 0.6s ease-out';
319
320        observer.observe(element);
321      });
322    }
323  };
324
325  // Bootstrap Carousel Enhancement
326  Drupal.behaviors.carouselEnhancement = {
327    attach: function (context) {
328      once('carousel-enhance', '.carousel', context).forEach(function (carousel) {
329        // Initialize Bootstrap carousel if not already initialized
330        if (typeof bootstrap !== 'undefined' && bootstrap.Carousel) {
331          new bootstrap.Carousel(carousel, {
332            interval: 5000,
333            wrap: true,
334            touch: true
335          });
336        }
337      });
338    }
339  };
340
341})(Drupal, once);
342// Image Text Lightbox
343(function() {
344  var lightbox = document.createElement('div');
345  lightbox.className = 'image-text-lightbox';
346  lightbox.innerHTML = '<img src="" alt="">';
347  document.body.appendChild(lightbox);
348  var lbImg = lightbox.querySelector('img');
349
350  document.querySelectorAll('.image-text-zoom').forEach(function(a) {
351    a.addEventListener('click', function(e) {
352      e.preventDefault();
353      var img = this.querySelector('img');
354      if (img) {
355        lbImg.src = img.src.replace(/\/styles\/[^/]+\/public\//, '/');
356        lightbox.classList.add('is-open');
357      }
358    });
359  });
360
361  lightbox.addEventListener('click', function() {
362    this.classList.remove('is-open');
363  });
364})();

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.