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(); });