PageSourceSearch

https://entr.com/js/scripts.js?2

js entr.com collected 2026-10-07 15:29:44 UTC 14,295 bytes, 449 lines download raw bytes

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.