1gsap.registerPlugin(ScrollTrigger); 2 3// Desktop hero animation 4if (document.getElementById('hero') && window.innerWidth > 768) { 5 const heroTl = gsap.timeline({ 6 scrollTrigger: { 7 trigger: '#hero', 8 start: 'top top', 9 end: '+=700', 10 scrub: 1, 11 pin: true, 12 pinSpacing: true, 13 } 14 }); 15 if (document.getElementById('hero-vid')) { 16 heroTl.to('#hero-vid', { 17 scale: 1, 18 borderRadius: '0px', 19 ease: 'power2.inOut', 20 duration: 1 21 }, 0); 22 } 23 if (document.getElementById('hero-txt')) { 24 gsap.to('#hero-txt', { 25 opacity: 1, y: 0, 26 duration: 1, ease: 'power3.out', delay: 0.3, 27 onComplete: () => { 28 heroTl.to('#hero-txt', { x: 160, opacity: 0, ease: 'power2.in', duration: 0.4 }, 0); 29 } 30 }); 31 } 32} 33 34// Mobile hero animation (index page only - no icon-holders) 35if (document.getElementById('hero') && window.innerWidth <= 768 && !document.querySelector('#icon-holders')) { 36 const heroTl = gsap.timeline({ 37 scrollTrigger: { 38 trigger: '#hero', 39 start: 'top top', 40 end: '+=500', 41 scrub: 1, 42 pin: true, 43 pinSpacing: true, 44 } 45 }); 46 if (document.getElementById('hero-txt')) { 47 gsap.to('#hero-txt', { 48 opacity: 1, y: 0, 49 duration: 1, ease: 'power3.out', delay: 0.3, 50 onComplete: () => { 51 heroTl.to('#hero-txt', { x: 160, opacity: 0, ease: 'power2.in', duration: 0.4 }, 0); 52 } 53 }); 54 } 55 if (document.getElementById('nova')) { 56 heroTl.fromTo('#nova', { x: -70 }, { x: '120vw', ease: 'none', duration: 1 }, 0); 57 } 58} 59 60// Navigation dropdowns 61const navBg = document.querySelector('#main-nav'); 62let closeTimer; 63function setupDD(liId, ddId) { 64 const li = document.getElementById(liId); 65 const dd = document.getElementById(ddId); 66 if (!li || !dd) return; 67 const btn = li.querySelector('.nav-btn'); 68 if (!btn) return; 69 function open() { 70 document.querySelectorAll('.nav-links li.open').forEach(el => { if(el!==li) el.classList.remove('open'); }); 71 li.classList.add('open'); 72 } 73 function close() { li.classList.remove('open'); } 74 btn.addEventListener('click', e => { e.stopPropagation(); li.classList.contains('open') ? close() : open(); }); 75 li.addEventListener('mouseenter', () => { clearTimeout(closeTimer); open(); }); 76 li.addEventListener('mouseleave', () =>
76 { closeTimer = setTimeout(close, 120); }); 77} 78setupDD('li-erv','dd-erv'); 79setupDD('li-werk','dd-werk'); 80setupDD('li-bez','dd-bez'); 81document.addEventListener('click', () => document.querySelectorAll('.nav-links li.open').forEach(el => el.classList.remove('open'))); 82 83// Mobile nav 84const mobNav = document.getElementById('mob-nav'); 85const mobMenuBtn = document.getElementById('mob-menu-btn'); 86const mobCloseBtn = document.getElementById('mob-close'); 87if (mobNav && mobMenuBtn && mobCloseBtn) { 88 mobMenuBtn.addEventListener('click', () => mobNav.classList.add('open')); 89 mobCloseBtn.addEventListener('click', () => mobNav.classList.remove('open')); 90 [['mb-erv','ms-erv'],['mb-werk','ms-werk'],['mb-bez','ms-bez']].forEach(([bId,sId]) => { 91 const b = document.getElementById(bId), s = document.getElementById(sId); 92 if (b && s) { 93 b.addEventListener('click', () => { 94 const open = s.classList.contains('open'); 95 document.querySelectorAll('.mob-sub').forEach(x => x.classList.remove('open')); 96 document.querySelectorAll('.mob-group-btn').forEach(x => x.classList.remove('open')); 97 if (!open) { s.classList.add('open'); b.classList.add('open'); } 98 }); 99 } 100 }); 101} 102 103// Image slider 104// Image slider 105(function(){ 106 const slides = Array.from(document.querySelectorAll('.img-slide')); 107 const dots = Array.from(document.querySelectorAll('.slider-dot')); 108 if (slides.length === 0 || dots.length === 0) return; 109 110 let cur = 0, timer; 111 let startX = 0, isDragging = false; 112 113 // Initialize: first slide visible, rest hidden 114 slides.forEach((slide, i) => { 115 if (i === 0) { 116 slide.style.transform = 'translateX(0%)'; 117 slide.style.zIndex = 2; 118 } else { 119 slide.style.transform = 'translateX(102%)'; 120 slide.style.zIndex = 1; 121 } 122 slide.style.transition = 'none'; 123 }); 124 125 function goTo(n) { 126 if (n === cur) return; 127 const prev = cur; 128 cur = n; 129 130 // Position new slide off-screen 131 slides[cur].style.transition = 'none'; 132 slides[cur].style.transform = 'translateX(102%)'; 133 slides[cur].style.zIndex = 3; 134 void slides[cur].offsetWidth; // Force reflow 135 136 // Animate new slide in 137 slides[cur].style.transition = 'transform 1.4s cubic-bezier(0.22,1,0.36,1)'; 138 slides[cur].style.transform = 'translateX(0%)'; 139 140 // After animation, reset z-indexes 141 setTimeout(() => { 142 slides[prev].style.transition = 'none'; 143 slides[prev].style.transform = 'translateX(102%)'; 144 slides[prev].style.zIndex = 1; 145 146 slides[cur].style.transition = 'none'; 147 slides[cur].style.zIndex = 2; 148 }, 1400); 149 150 dots.forEach((d,i) => d.classList.toggle('active', i===cur)); 151 } 152 153 // Dot clicks 154 dots.forEach(d => d.addEventListener('click', () => { 155 clearInterval(timer); 156 goTo(+d.dataset.i); 157 timer = setInterval(() => goTo((cur+1)%slides.length), 4000); 158 })); 159 160 // Auto-play 161 timer = setInterval(() => goTo((cur+1)%slides.length), 4000); 162 163 // Manual slider controls (arrows) 164 const sliderPrev = document.getElementById('slider-prev'); 165 const sliderNext = document.getElementById('slider-next'); 166 if (sliderPrev && sliderNext) { 167 sliderPrev.addEventListener('click', () => { 168 clearInterval(timer); 169 goTo((cur - 1 + slides.length) % slides.length); 170 timer = setInterval(() => goTo((cur+1)%slides.length), 4000); 171 }); 172 173 sliderNext.addEventListener('click', () => { 174 clearInterval(timer); 175 goTo((cur + 1) % slides.length); 176 timer = setInterval(() => goTo((cur+1)%slides.length), 4000); 177 }); 178 } 179 180 // Touch/swipe support for mobile 181 const sliderContainer = document.querySelector('.img-slider'); 182 if (sliderContainer && 'ontouchstart' in window) { 183 sliderContainer.addEventListener('touchstart', (e) => { 184 startX = e.touches[0].clientX; 185 isDragging = true; 186 }); 187 188 sliderContainer.addEventListener('touchmove', (e) => { 189 if (!isDragging) return; 190 }); 191 192 sliderContainer.addEventListener('touchend', (e) => { 193 if (!isDragging) return; 194 isDragging = false;
195 196 const endX = e.changedTouches[0].clientX; 197 const diff = startX - endX; 198 199 if (Math.abs(diff) > 50) { 200 clearInterval(timer); 201 if (diff > 0) { 202 goTo((cur + 1) % slides.length); 203 } else { 204 goTo((cur - 1 + slides.length) % slides.length); 205 } 206 timer = setInterval(() => goTo((cur+1)%slides.length), 4000); 207 } 208 }); 209 } 210})(); 211 212// Review dots 213document.querySelectorAll('.review-dot').forEach(dot => { 214 dot.addEventListener('click', () => { 215 const reviewIndex = dot.getAttribute('data-review'); 216 217 document.querySelectorAll('.review-quote').forEach(review => { 218 review.style.display = 'none'; 219 }); 220 221 const selectedReview = document.getElementById(`review-${reviewIndex}`); 222 if (selectedReview) { 223 selectedReview.style.display = 'block'; 224 selectedReview.style.animation = 'none'; 225 selectedReview.offsetHeight; 226 selectedReview.style.animation = 'slideInFromRight 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards'; 227 } 228 229 document.querySelectorAll('.review-dot').forEach(d => d.classList.remove('active')); 230 dot.classList.add('active'); 231 }); 232}); 233 234// Auto-rotate reviews 235(function() { 236 const reviewDots = Array.from(document.querySelectorAll('.review-dot')); 237 if (reviewDots.length === 0) return; 238 239 let currentReview = 0; 240 241 function showReview(index) { 242 document.querySelectorAll('.review-quote').forEach(review => { 243 review.style.display = 'none'; 244 }); 245 246 const selectedReview = document.getElementById(`review-${index}`); 247 if (selectedReview) { 248 selectedReview.style.display = 'block';
249 selectedReview.style.animation = 'none'; 250 selectedReview.offsetHeight; 251 selectedReview.style.animation = 'slideInFromRight 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards'; 252 } 253 254 reviewDots.forEach(d => d.classList.remove('active')); 255 reviewDots[index].classList.add('active'); 256 } 257 258 let timer = setInterval(() => { 259 currentReview = (currentReview + 1) % reviewDots.length; 260 showReview(currentReview); 261 }, 5000); 262 263 reviewDots.forEach(dot => { 264 dot.addEventListener('click', () => { 265 clearInterval(timer); 266 currentReview = parseInt(dot.getAttribute('data-review')); 267 showReview(currentReview); 268 timer = setInterval(() => { 269 currentReview = (currentReview + 1) % reviewDots.length; 270 showReview(currentReview); 271 }, 5000); 272 }); 273 }); 274})(); 275 276// Reveal animations 277const obs = new IntersectionObserver(entries => { 278 entries.forEach(e => { if(e.isIntersecting){ e.target.classList.add('visible'); obs.unobserve(e.target); }}); 279}, { threshold: 0.12 }); 280document.querySelectorAll('.reveal, .reveal-stagger').forEach(el => obs.observe(el)); 281 282// Nova footer - MOVED BEFORE PARALLAX 283if (document.getElementById('nova-footer')) { 284 gsap.timeline({ 285 scrollTrigger: { 286 trigger: document.documentElement, 287 start: () => `${document.documentElement.scrollHeight * 0.4} top`, 288 end: 'bottom bottom', 289 scrub: 1, 290 invalidateOnRefresh: true, 291 } 292 }) 293 .fromTo('#nova-footer', 294 { x: '110vw' }, 295 { x: '-85px', ease: 'none', duration: 1 } 296 ); 297 298 // Refresh after page load 299 window.addEventListener('load', () => { 300 ScrollTrigger.refresh(); 301 }); 302} 303 304// Parallax hero image (bezoek page only) 305if (document.getElementById('hero-image')) { 306 window.addEventListener('scroll', () => { 307 const scrolled = window.pageYOffset; 308 const heroImage = document.getElementById('hero-image'); 309 if (heroImage) { 310 heroImage.style.transform = `translateY(${scrolled * 0.5}px)`; 311 } 312 }); 313} 314 315// Contact form 316const contactForm = document.getElementById('contact-form'); 317if (contactForm) { 318 contactForm.addEventListener('submit', function(e) { 319 e.preventDefault(); 320 321 const form = this; 322 const formData = new FormData(form); 323 const statusDiv = form.querySelector('.form-status'); 324 325 fetch('send-email.php', { 326 method: 'POST', 327 body: formData 328 }) 329 .then(response => response.text()) 330 .then(data => { 331 statusDiv.textContent = data; 332 statusDiv.className = 'form-status success'; 333 form.reset(); 334 }) 335 .catch(error => { 336 statusDiv.textContent = 'Er ging iets mis. Probeer het later opnieuw.'; 337 statusDiv.className = 'form-status error'; 338 }); 339 }); 340} 341 342// FAQ Accordion 343document.querySelectorAll('.faq-item').forEach(button => { 344 button.addEventListener('click', () => { 345 const faqId = button.getAttribute('data-faq'); 346 const answer = document.getElementById(faqId); 347 if (!answer) return; 348 349 const isOpen = answer.classList.contains('open'); 350 351 document.querySelectorAll('.faq-answer').forEach(ans => { 352 ans.classList.remove('open'); 353 }); 354 document.querySelectorAll('.faq-item').forEach(btn => { 355 btn.classList.remove('active'); 356 }); 357 358 if (!isOpen) { 359 answer.classList.add('open'); 360 button.classList.add('active'); 361 } 362 }); 363}); 364 365// Nav background on scroll - MOVED TO END 366const heroElementForNav = document.getElementById('hero') || document.getElementById('page-hero'); 367const mobLogoFixed = document.querySelector('.mob-logo-fixed'); 368const mobTicket = document.querySelector('.mob-float-tickets'); 369 370if (heroElementForNav) { 371 gsap.to('#main-nav', { 372 scrollTrigger: { 373 trigger: heroElementForNav, 374 start: 'bottom top+=68', 375 end: 'bottom top+=68', 376 onEnter: () => { 377 const mainNav = document.getElementById('main-nav'); 378 if (mainNav) mainNav.classList.add('scrolled'); 379 if (mobLogoFixed) mobLogoFixed.classList.add('scrolled'); 380 if (mobTicket) mobTicket.classList.add('scrolled'); 381 }, 382 onLeaveBack: () => { 383 const mainNav = document.getElementById('main-nav'); 384 if (mainNav) mainNav.classList.remove('scrolled'); 385 if (mobLogoFixed) mobLogoFixed.classList.remove('scrolled'); 386 if (mobTicket) mobTicket.classList.remove('scrolled'); 387 } 388 } 389 }); 390} 391
392// Review nav arrows 393(function() { 394 const section = document.getElementById('reviews'); 395 if (!section) return; 396 const inner = section.querySelector('.reviews-inner'); 397 if (!inner) return; 398 399 const prev = document.createElement('button'); 400 const next = document.createElement('button'); 401 402 prev.className = 'review-arrow review-arrow-prev'; 403 next.className = 'review-arrow review-arrow-next'; 404 prev.innerHTML = '<img src="img/arrow_1.svg" style="transform: rotate(180deg);">'; 405 next.innerHTML = '<img src="img/arrow_1.svg">'; 406 prev.setAttribute('aria-label', 'Vorige review'); 407 next.setAttribute('aria-label', 'Volgende review'); 408 409 inner.appendChild(prev); 410 inner.appendChild(next); 411 412 section.addEventListener('mouseenter', () => { 413 prev.style.opacity = '1'; 414 next.style.opacity = '1'; 415 }); 416 section.addEventListener('mouseleave', () => { 417 prev.style.opacity = '0'; 418 next.style.opacity = '0'; 419 }); 420 421 const dots = Array.from(document.querySelectorAll('.review-dot')); 422 423 function getActive() { 424 return dots.findIndex(d => d.classList.contains('active')); 425 } 426 427 prev.addEventListener('click', () => dots[(getActive() - 1 + dots.length) % dots.length].click()); 428 next.addEventListener('click', () => dots[(getActive() + 1) % dots.length].click()); 429})(); 430 431// Track initial page load 432window.addEventListener('load', function() { 433 if (typeof dataLayer !== 'undefined') { 434 dataLayer.push({ 435 event: 'virtual_pageview', 436 page_path: window.location.pathname + window.location.search 437 }); 438 } 439}); 440 441// Track URL changes (if you're using history API or hash changes) 442window.addEventListener('popstate', function() { 443 if (typeof dataLayer !== 'undefined') { 444 dataLayer.push({ 445 event: 'virtual_pageview', 446 page_path: window.location.pathname + window.location.search 447 }); 448 } 449});
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.