const SIGNS = {NL:'bordje.png', DE:'de_bordje.png', EN:'en_bordje.png'}; const STAR = ''; const esc = s => String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])); let lang = 'NL'; function render(l){ lang = T[l] ? l : 'NL'; const t = T[lang]; document.documentElement.lang = lang.toLowerCase(); document.title = t.metaTitle; document.querySelector('meta[name="description"]').content = t.metaDesc; document.querySelectorAll('[data-i]').forEach(el => { const v = t[el.dataset.i]; if (v === undefined) return; el.textContent = v; if (el.tagName === 'FIGCAPTION') el.hidden = v === ''; }); document.querySelectorAll('[data-i-alt]').forEach(el => { el.alt = t[el.dataset.iAlt] || ''; }); document.querySelectorAll('[data-i-aria]').forEach(el => el.setAttribute('aria-label', t[el.dataset.iAria] || '')); document.querySelectorAll('.js-sign').forEach(img => { img.onerror = () => { img.onerror = null; img.src = 'https://duinputter.nl/images/' + SIGNS[lang]; }; img.src = 'images/' + SIGNS[lang]; }); document.querySelector('.js-quoteTr').hidden = !t.quoteTr; const L = (name, html) => document.querySelectorAll('[data-list="'+name+'"]').forEach(el => el.innerHTML = html); L('stats', t.stats.map(s => '
'+esc(s.n)+''+esc(s.l)+'
').join('')); const mq = t.marquee.concat(t.marquee).map(m => ''+esc(m)+STAR+'').join(''); L('marquee', mq); L('near', t.near.map(n => '
  • '+esc(n.l)+''+esc(n.m)+'
  • ').join('')); L('rates', t.rates.map(r => '
    '+esc(r.k)+''+esc(r.v)+'
    ').join('')); L('revSub', t.revSub.map(r => ''+esc(r.k)+' '+esc(r.v)+'').join('')); document.querySelectorAll('[data-lang]').forEach(b => b.setAttribute('aria-pressed', b.dataset.lang === lang)); document.querySelector('#lightbox .close').setAttribute('aria-label', t.close); try { localStorage.setItem('dp-lang', lang); } catch(e) {} renderAvailability(); } // availability: live from the travelbase/Texel.net booking API, no backend of our own const AVAIL_LOCALE = {NL:'nl-NL', DE:'de-DE', EN:'en-GB'}; let availTrips = null, availState = 'loading'; function groupAvailability(trips){ const byDate = {}; trips.forEach(tr => { (byDate[tr.date] = byDate[tr.date] || []).push(tr); }); return Object.keys(byDate).sort().slice(0, 6).map(date => { const options = byDate[date].sort((a, b) => a.duration - b.duration); const pick = options.find(o => o.duration === 7) || options[0]; return {date, duration: pick.duration, price: pick.price}; }); } function renderAvailability(){ const box = document.querySelector('[data-avail]'); if (!box) return; const t = T[lang], list = box.querySelector('.avail-list'), empty = box.querySelector('.avail-empty'); if (availState === 'loading') { list.hidden = false; list.innerHTML = '
  • '+esc(t.availLoading)+'
  • '; empty.hidden = true; return; } if (availState === 'ok') { const dtf = new Intl.DateTimeFormat(AVAIL_LOCALE[lang] || 'nl-NL', {day:'numeric', month:'short'}); const eur = new Intl.NumberFormat(AVAIL_LOCALE[lang] || 'nl-NL', {style:'currency', currency:'EUR', maximumFractionDigits:0}); list.hidden = false; list.innerHTML = availTrips.map(a => '
  • '+esc(dtf.format(new Date(a.date+'T00:00:00')))+''+a.duration+' '+esc(t.availNights)+''+esc(eur.format(a.price))+'
  • ').join(''); empty.hidden = true; return; } list.hidden = true; list.innerHTML = ''; empty.hidden = false; empty.textContent = availState === 'error' ? t.availError : t.availEmpty; } async function fetchAvailability(){ try { const start = new Date().toISOString().slice(0, 10); const endDate = new Date(); endDate.setMonth(endDate.getMonth() + 18); const end = endDate.toISOString().slice(0, 10); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); const res = await fetch('https://texel-gateway.travelbase.nl/graphql', { method: 'POST', signal: controller.signal, headers: {'content-type': 'application/json', 'x-api-key': 'da2-cksz5xfa35dbdh2qclpnag63wi', 'x-site-language': 'nl'}, body: JSON.stringify({ operationName: 'TripPlanner', variables: {slug: 'de-duinputter-6092', start, end, persons: 1, pets: 0, babies: 0}, query: 'query TripPlanner($slug: String!, $start: String!, $end: String!, $persons: Int!, $pets: Int!, $babies: Int!) { rentalUnit(slug: $slug) { trips(pets: $pets, persons: $persons, babies: $babies, startDate: $start, endDate: $end) { date price duration __typename } __typename } }' }) }); clearTimeout(timeout); if (!res.ok) throw new Error('http ' + res.status); const json = await res.json(); const trips = json && json.data && json.data.rentalUnit && json.data.rentalUnit.trips || []; availTrips = groupAvailability(trips); availState = availTrips.length ? 'ok' : 'empty'; } catch (e) { availState = 'error'; } renderAvailability(); } fetchAvailability(); // initial language: ?lang=, then saved choice, then browser language (function(){ let l = (new URLSearchParams(location.search).get('lang') || '').toUpperCase(); if (!T[l]) { try { l = localStorage.getItem('dp-lang') || ''; } catch(e) { l = ''; } } if (!T[l]) { const n = (navigator.language || 'nl').slice(0,2).toUpperCase(); l = T[n] ? n : 'NL'; } render(l); })(); document.querySelectorAll('[data-lang]').forEach(b => b.addEventListener('click', () => { render(b.dataset.lang); const u = new URL(location.href); u.searchParams.set('lang', lang.toLowerCase()); history.replaceState(null, '', u); })); // header shadow + mobile menu const header = document.querySelector('.site-header'); addEventListener('scroll', () => header.classList.toggle('scrolled', scrollY > 10), {passive:true}); const menuBtn = document.querySelector('.menu-btn'), menu = document.getElementById('mobile-menu'); menuBtn.addEventListener('click', () => { const o = menu.classList.toggle('open'); menuBtn.setAttribute('aria-expanded', o); }); menu.querySelectorAll('a').forEach(a => a.addEventListener('click', () => { menu.classList.remove('open'); menuBtn.setAttribute('aria-expanded', false); })); // guests stepper let guests = 2; const out = document.getElementById('guests'); document.querySelectorAll('[data-step]').forEach(b => b.addEventListener('click', () => { guests = Math.min(6, Math.max(1, guests + +b.dataset.step)); out.textContent = guests; })); // booking form -> opens mail program with the request filled in document.getElementById('bookForm').addEventListener('submit', e => { e.preventDefault(); const f = e.target, t = T[lang]; if (!f.reportValidity()) return; const body = [t.arr+': '+f.arr.value, t.dep+': '+f.dep.value, t.guests+': '+guests, t.email+': '+f.email.value, '', f.msg.value].join('\n'); location.href = 'mailto:info@duinputter.nl?subject=' + encodeURIComponent(t.mailSubject) + '&body=' + encodeURIComponent(body); }); // lightbox const lb = document.getElementById('lightbox'); document.querySelectorAll('.gallery button').forEach(b => b.addEventListener('click', () => { const img = b.querySelector('img'); lb.querySelector('img').src = img.src; lb.querySelector('img').alt = img.alt; lb.querySelector('p').textContent = img.alt; lb.showModal(); })); lb.querySelector('.close').addEventListener('click', () => lb.close()); lb.addEventListener('click', e => { if (e.target === lb) lb.close(); });