1// Main JavaScript for Reiskosten Berekenen 2 3// Mobile Menu Toggle 4document.addEventListener('DOMContentLoaded', function() { 5 const menuToggle = document.querySelector('.mobile-menu-toggle'); 6 const navMenu = document.querySelector('.nav-menu'); 7 8 if (menuToggle && navMenu) { 9 menuToggle.addEventListener('click', function() { 10 navMenu.style.display = navMenu.style.display === 'flex' ? 'none' : 'flex'; 11 navMenu.style.flexDirection = 'column'; 12 navMenu.style.position = 'absolute'; 13 navMenu.style.top = '100%'; 14 navMenu.style.left = '0'; 15 navMenu.style.right = '0'; 16 navMenu.style.backgroundColor = 'white'; 17 navMenu.style.padding = '1rem'; 18 navMenu.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)'; 19 }); 20 21 // Close menu when clicking outside 22 document.addEventListener('click', function(e) { 23 if (!menuToggle.contains(e.target) && !navMenu.contains(e.target)) { 24 if (window.innerWidth < 768) { 25 navMenu.style.display = 'none'; 26 } 27 } 28 }); 29 } 30}); 31 32// Smooth Scroll for Anchor Links 33document.querySelectorAll('a[href^="#"]').forEach(anchor => { 34 anchor.addEventListener('click', function(e) { 35 const href = this.getAttribute('href'); 36 if (href !== '#' && href.length > 1) { 37 e.preventDefault(); 38 const target = document.querySelector(href); 39 if (target) { 40 target.scrollIntoView({ 41 behavior: 'smooth', 42 block: 'start' 43 }); 44 } 45 } 46 }); 47}); 48 49// Lazy Load Images 50if ('IntersectionObserver' in window) { 51 const imageObserver = new IntersectionObserver((entries, observer) => { 52 entries.forEach(entry => { 53 if (entry.isIntersecting) { 54 const img = entry.target; 55 if (img.dataset.src) { 56 img.src = img.dataset.src; 57 img.removeAttribute('data-src'); 58 observer.unobserve(img); 59 } 60 } 61 }); 62 }); 63 64 document.querySelectorAll('img[data-src]').forEach(img => { 65 imageObserver.observe(img); 66 }); 67} 68 69// Form Input Validation Feedback 70document.querySelectorAll('input[type="number"]').forEach(input => { 71 input.addEventListener('invalid', function(e) { 72 e.preventDefault(); 73 this.classList.add('invalid'); 74 }); 75 76 input.addEventListener('input', function() { 77 this.classList.remove('invalid'); 78 }); 79}); 80 81// Responsive Table Wrapper 82document.addEventListener('DOMContentLoaded', function() { 83 document.querySelectorAll('table').forEach(table => { 84 if (!table.parentElement.classList.contains('distance-table')) { 85 const wrapper = document.createElement('div'); 86 wrapper.className = 'table-responsive'; 87 wrapper.style.overflowX = 'auto'; 88 table.parentNode.insertBefore(wrapper, table); 89 wrapper.appendChild(table); 90 } 91 }); 92}); 93 94// Print Page Functionality 95function printPage() { 96 window.print(); 97} 98 99// Back to Top Button 100const backToTopButton = document.createElement('button'); 101backToTopButton.innerHTML = 'â'; 102backToTopButton.className = 'back-to-top'; 103backToTopButton.style.cssText = ` 104 position: fixed; 105 bottom: 20px; 106 right: 20px; 107 width: 50px; 108 height: 50px; 109 border-radius: 50%; 110 background: #0066CC; 111 color: white; 112 border: none; 113 font-size: 24px; 114 cursor: pointer; 115 display: none; 116 z-index: 999; 117 box-shadow: 0 4px 12px rgba(0,0,0,0.2); 118 transition: all 0.3s ease; 119`; 120 121document.body.appendChild(backToTopButton); 122 123window.addEventListener('scroll', function() { 124 if (window.pageYOffset > 300) { 125 backToTopButton.style.display = 'block'; 126 } else { 127 backToTopButton.style.display = 'none'; 128 } 129}); 130 131backToTopButton.addEventListener('click', function() { 132 window.scrollTo({ 133 top: 0, 134 behavior: 'smooth' 135 });
136}); 137 138// External Links - Open in New Tab 139document.querySelectorAll('a[href^="http"]').forEach(link => { 140 if (!link.hostname.includes(window.location.hostname)) { 141 link.setAttribute('target', '_blank'); 142 link.setAttribute('rel', 'noopener noreferrer'); 143 } 144}); 145 146// Service Worker Registration (for PWA) 147if ('serviceWorker' in navigator) { 148 window.addEventListener('load', function() { 149 // Uncomment to enable service worker 150 // navigator.serviceWorker.register('/sw.js') 151 // .then(reg => console.log('Service Worker registered')) 152 // .catch(err => console.log('Service Worker registration failed')); 153 }); 154} 155 156// Performance Monitoring 157window.addEventListener('load', function() { 158 if (window.performance && window.performance.timing) { 159 const perfData = window.performance.timing; 160 const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart; 161 162 // Track page load time in GA 163 if (typeof gtag !== 'undefined' && pageLoadTime > 0) { 164 gtag('event', 'timing_complete', { 165 name: 'load', 166 value: pageLoadTime, 167 event_category: 'Performance' 168 }); 169 } 170 } 171}); 172 173// Detect Ad Blocker 174function detectAdBlocker() { 175 const adTest = document.createElement('div'); 176 adTest.innerHTML = ' '; 177 adTest.className = 'adsbox'; 178 document.body.appendChild(adTest); 179 180 window.setTimeout(function() { 181 if (adTest.offsetHeight === 0) { 182 // Ad blocker detected 183 console.log('Ad blocker detected'); 184 } 185 adTest.remove(); 186 }, 100); 187} 188 189detectAdBlocker();
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.