Search – Nude-Places * { box-sizing: border-box; } html, body { margin: 0; padding: 0; } body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; background: #f3f3f3; color: #222; text-align: left; } .wrap { width: 100%; max-width: none; margin: 0; padding: 0 8px 28px; } .top { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; margin: 0 0 4px; padding: 0; } .lang-flags { display: flex; flex-direction: row; gap: 4px; align-items: center; } .lang-flags button { appearance: none; border: 1px solid #ccc; background: #fff; border-radius: 4px; padding: 1px; line-height: 0; cursor: pointer; width: 26px; height: 18px; display: flex; align-items: center; justify-content: center; } .lang-flags button:hover { background: #f5f5f5; } .lang-flags button.active { border-color: #1565c0; box-shadow: 0 0 0 1px #1565c0; background: #e3f2fd; } .lang-flags button svg { display: block; width: 22px; height: 14px; border-radius: 1px; } .viewToggle { display: none; gap: 6px; align-items: center; } .viewToggle button { font-family: inherit; font-size: 13px; font-weight: 600; padding: 6px 11px; border-radius: 8px; border: 1px solid #ccc; background: #fff; color: #333; cursor: pointer; white-space: nowrap; } .viewToggle button:hover { background: #f5f5f5; } .viewToggle button.active { border-color: #1565c0; background: #e3f2fd; color: #1565c0; } .appBanner { display: none; background: #e3f2fd; border: 1px solid #90caf9; border-radius: 8px; padding: 10px 12px; margin: 0 0 8px; font-size: 13px; line-height: 1.4; color: #1565c0; text-align: left; } .appBanner a { color: #0d47a1; font-weight: 600; text-decoration: underline; } .layout { display: grid; grid-template-columns: 420px minmax(0, 1fr); gap: 18px; width: 100%; align-items: start; } aside { width: 100%; background: #fff; border: 1px solid #ddd; border-radius: 10px; padding: 14px 16px; min-width: 0; position: sticky; top: 0; align-self: start; z-index: 5; text-align: left; } main { width: 100%; min-width: 0; text-align: left; } aside .sec { margin: 8px 0 3px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #888; border-bottom: 1px solid #eee; padding-bottom: 3px; text-align: left; } aside .sec:first-child { margin-top: 0; } #secLocation, #secType, #secNaturism, #secPets, #secAccess { margin-top: 6px; } #secFacilities { margin-top: 14px; } .lbl { display: block; font-size: 12px; color: #777; margin: 0 0 3px; text-align: left; } select, input[type="text"] { width: 100%; font-family: inherit; font-size: 13px; padding: 7px 9px; margin-bottom: 6px; border: 1px solid #ccc; border-radius: 6px; background: #fff; text-align: left; } #qFiltersWarn { display: none; margin: -4px 0 10px; padding: 7px 10px; font-size: 12px; font-weight: 600; line-height: 1.35; color: #b45309; background: #fff8e1; border: 1px solid #f9a825; border-radius: 6px; text-align: left; } #qFiltersWarn.visible { display: block; } #qFiltersWarnText { margin: 0 0 8px; text-align: left; } #btnClearCriteria { display: block; width: 100%; margin: 0; padding: 7px 10px; font-family: inherit; font-size: 12px; font-weight: 600; border-radius: 6px; border: 1px solid #c62828; background: #fff; color: #c62828; cursor: pointer; } #btnClearCriteria:hover { background: #fff5f5; } .check { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; font-size: 13px; } .check input { margin: 0; } #btnReset { display: none; width: 100%; margin: 0 0 12px; padding: 8px; font-family: inherit; font-size: 13px; font-weight: 600; border-radius: 8px; border: 1px solid #c62828; background: #fff; color: #c62828; cursor: pointer; } #btnReset:hover { background: #fff5f5; } #btnReset.visible { display: block; } #btnSearchNow { display: none; width: 100%; margin: 12px 0 4px; padding: 12px; font-family: inherit; font-size: 15px; font-weight: 700; border-radius: 8px; border: none; background: #1565c0; color: #fff; cursor: pointer; } #btnSearchNow:hover { background: #0d47a1; } #count { background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 8px 12px; margin-bottom: 9px; font-weight: 600; font-size: 13px; text-align: left; } #sortNote { display: none; background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 7px 12px; margin: -5px 0 9px; font-size: 12px; color: #555; text-align: left; } #results, #results a.card, #results a.card .body, #results a.card h3, #results a.card .name-text, #results a.card .m, #results a.card .loc-line, #results a.card .distance, #results a.card .access-line { text-align: left !important; text-justify: auto; text-align-last: left !important; } a.card { position: relative; display: flex; gap: 11px; width: 100%; max-width: 100%; border: 1px solid #ddd; border-left: 6px solid #8fbc8f; border-radius: 10px; padding: 10px; margin: 0 0 8px; text-decoration: none; color: inherit; background: #f1f8f1; text-align: left; } a.card.mem { border-left-color: #e57373; background: #fff5f5; } a.card img.thumb, .ph { width: 76px; height: 76px; object-fit: cover; border-radius: 6px; background: #eee; flex-shrink: 0; } a.card .body { flex: 1; min-width: 0; text-align: left; } a.card h3 { margin: 0 0 3px; font-size: 14px; line-height: 1.25; padding-right: 72px; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; text-align: left; } a.card h3 .name-text { min-width: 0; text-align: left; } a.card h3 .badge-icons { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; } a.card h3 .badge-icons img { width: 18px; height: 18px; object-fit: contain; display: block; } a.card .m { font-size: 12px; color: #666; line-height: 1.45; text-align: left; } a.card .loc-line { font-size: 12px; color: #555; line-height: 1.4; margin-top: 2px; text-align: left; } a.card .loc-line .cont { color: #777; } a.card .loc-line .sep { display: inline-block; vertical-align: 0.05em; margin: 0 0.15em; color: #999; } a.card .distance { font-size: 11px; color: #999; margin-top: 2px; text-align: left; } a.card .access-line { font-size: 12px; color: #666; margin-top: 3px; text-align: left; } .tip { position: absolute; top: 8px; right: 10px; z-index: 1; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; border-radius: 999px; padding: 3px 8px; white-space: nowrap; } .tip.tip-lux { color: #8a6d00; background: linear-gradient(135deg, #fff8e1, #ffe082); border: 1px solid #f9a825; box-shadow: 0 1px 2px rgba(249, 168, 37, 0.25); } .tip.tip-pro { color: #546e7a; background: linear-gradient(135deg, #f5f7fa, #cfd8dc); border: 1px solid #90a4ae; box-shadow: 0 1px 2px rgba(120, 144, 156, 0.2); } @media (max-width: 1099px) { a.card .tip { position: static; display: inline-block; margin: 0 0 6px 0; align-self: flex-start; } a.card h3 { padding-right: 0; } a.card .body { display: flex; flex-direction: column; align-items: flex-start; } } .icons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; align-items: center; } .icons img { width: 22px; height: 22px; object-fit: contain; display: block; } .err { padding: 1.5rem; text-align: center; color: #666; background: #fff; border: 1px solid #ddd; border-radius: 8px; } #countryWrap, #regionWrap, #deptWrap, #subAccom, #subCamping { display: none; } #debug { display: none; background: #fff3cd; border: 1px solid #ffc107; padding: 8px; margin-bottom: 8px; font-size: 12px; white-space: pre-wrap; } @media (max-width: 999px) { html, body { margin: 0 !important; padding: 0 !important; } .wrap { width: 100% !important; max-width: 100% !important; margin: 0 !important; padding: 0 2px 16px !important; } .top { margin: 0 0 4px !important; padding: 0 !important; gap: 3px; align-items: stretch; } .lang-flags { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; } .viewToggle { display: none !important; } .appBanner { display: block; } .layout { display: flex !important; flex-direction: column; width: 100% !important; max-width: 100% !important; gap: 6px; margin: 0 !important; } aside { position: relative !important; top: auto !important; width: 100% !important; max-width: 100% !important; margin: 0 !important; order: 1; padding: 8px 10px; border-radius: 8px; display: block !important; } main { width: 100% !important; max-width: 100% !important; margin: 0 !important; order: 2; display: block !important; } #count { margin-top: 0; width: 100%; } a.card { width: 100% !important; max-width: 100% !important; } .hide-on-mobile { display: none !important; } #secPets, #honden, #secAccess, #pasje { display: none !important; } aside .sec { margin: 6px 0 2px; padding-bottom: 2px; } aside .sec:first-child { margin-top: 0; } #secFacilities { margin-top: 10px; } #secLocation, #secType, #secNaturism, #secPets, #secAccess { margin-top: 4px; } .lbl { margin: 0 0 2px; font-size: 11px; } select, input[type="text"] { margin-bottom: 4px; padding: 6px 8px; } #subAccom select, #subCamping select { margin-bottom: 4px; } #btnReset { margin: 0 0 8px; padding: 7px; } #btnSearchNow { margin: 8px 0 2px; padding: 11px; display: block !important; } body .item-page, body #content, body .container, body .container-fluid, body #sp-component, body .sp-module-content, body .sppb-row-container, body .sppb-column-addons { max-width: 100% !important; width: 100% !important; padding-left: 0 !important; padding-right: 0 !important; margin-left: 0 !important; margin-right: 0 !important; } .wrap { margin-top: -10px !important; padding-top: 0 !important; } .u-section-1 .u-container-layout-1, .u-section-1 .u-container-layout, .u-container-layout-1, .u-container-layout, .custom-expanded, .u-post-details, .u-post-details-1, .u-container-style { padding-top: 0 !important; margin-top: 0 !important; } .u-section-1, section.u-image, #sec-763d { min-height: 0 !important; padding-top: 0 !important; margin-top: 0 !important; } .appBanner { margin-top: 0 !important; } } @media (max-width: 767px) { .wrap { margin-top: -10px !important; } } @media (max-width: 500px) { body { font-size: 14px; } .wrap { padding: 0 !important; margin-top: -99px !important; } .top { margin: 0 0 2px !important; gap: 2px; } .appBanner { font-size: 12px; padding: 8px 10px; margin-bottom: 6px; } aside { padding: 6px 8px; border-radius: 6px; } select, input[type="text"] { font-size: 14px; padding: 7px 8px; margin-bottom: 3px; } aside .sec { margin: 5px 0 2px; } .check { font-size: 14px; margin-bottom: 7px; } #btnReset { font-size: 14px; padding: 8px; margin-bottom: 6px; } #btnSearchNow { font-size: 15px; padding: 11px; margin-top: 6px; } .icons img { width: 20px; height: 20px; } a.card { flex-direction: column; gap: 8px; padding: 10px 6px; border-radius: 8px; } a.card img.thumb, a.card .ph { width: 100%; height: 140px; order: 2; } a.card .body { order: 1; width: 100%; } a.card h3 { padding-right: 0; font-size: 15px; } a.card .m, a.card .loc-line { font-size: 12px; } } Overzicht Zoekfilters Clear filters Search Location Continent Country Region Department Type Facilities Restaurant Pool Natural water WiFi Accessible Singles Day recreation Target Naturism Pets Access Nu zoeken Loading... window.onerror = function (msg, src, line) { var d = document.getElementById('debug'); if (d) { d.style.display = 'block'; d.textContent = 'JS error: ' + msg + ' (line ' + line + ')'; } return false; }; (function () { var API = 'https://www.blootlocaties.nl/api/nude-places.json'; var VERSION_URL = 'https://www.blootlocaties.nl/api/version.json'; var ICON_BASE = 'https://www.blootlocaties.nl/app/icons/'; var APP_URL = 'https://www.blootlocaties.nl/download-the-app.html'; var FILTER_STATE_KEY = 'np_filter_state'; var FILTER_RESTORE_KEY = 'np_restore_filters'; var CACHE_KEY = 'np_places_cache_v2'; var VERSION_KEY = 'np_cache_versions'; var CACHE_TTL_MS = 24 * 60 * 60 * 1000; var all = []; var userLocation = null; var suppressSave = false; function isPageReload() { try { var nav = performance.getEntriesByType && performance.getEntriesByType('navigation'); if (nav && nav[0] && nav[0].type === 'reload') return true; if (performance.navigation && performance.navigation.type === 1) return true; } catch (e) {} return false; } function clearPlacesCache() { try { localStorage.removeItem(CACHE_KEY); } catch (e) {} } function tryLocalStorageCache() { try { var raw = localStorage.getItem(CACHE_KEY); if (!raw) return null; var cached = JSON.parse(raw); if (cached && cached.ts && Date.now() - cached.ts < CACHE_TTL_MS && Array.isArray(cached.data)) { return cached.data; } } catch (e) {} return null; } function saveLocalStorageCache(list) { try { localStorage.setItem(CACHE_KEY, JSON.stringify({ ts: Date.now(), data: list })); } catch (e) {} } function detectOsLang() { var nav = (navigator.language || navigator.userLanguage || 'en').toLowerCase(); if (nav.indexOf('nl') === 0) return 'nl'; if (nav.indexOf('fr') === 0) return 'fr'; if (nav.indexOf('de') === 0) return 'de'; if (nav.indexOf('es') === 0) return 'es'; return 'en'; } function isMobileLayout() { return window.matchMedia('(max-width: 999px)').matches; } var isMobile = isMobileLayout(); var stored = localStorage.getItem('np_lang'); var lang = stored || (isMobile ? detectOsLang() : 'en'); function getStoredVersions() { try { var raw = localStorage.getItem(VERSION_KEY); if (raw) { var v = JSON.parse(raw); if (v && typeof v === 'object') return v; } } catch (e) {} return {}; } function setStoredVersions(versions) { try { localStorage.setItem(VERSION_KEY, JSON.stringify(versions)); } catch (e) {} } function fetchVersion() { return fetch(VERSION_URL + '?t=' + Date.now(), { cache: 'no-store' }) .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .catch(function () { return null; }); } var UI = { search: { en: 'Search by keyword', nl: 'Zoeken op keyword', fr: 'Recherche par mot-clé', de: 'Suche nach Stichwort', es: 'Buscar por palabra clave' }, location: { en: 'Location', nl: 'Locatie', fr: 'Localisation', de: 'Standort', es: 'Ubicación' }, continent: { en: 'Continent', nl: 'Continent', fr: 'Continent', de: 'Kontinent', es: 'Continente' }, country: { en: 'Country', nl: 'Land', fr: 'Pays', de: 'Land', es: 'País' }, region: { en: 'Region', nl: 'Regio', fr: 'Région', de: 'Region', es: 'Región' }, department: { en: 'Department', nl: 'Departement', fr: 'Département', de: 'Département', es: 'Departamento' }, island: { en: 'Island', nl: 'Eiland', fr: 'Île', de: 'Insel', es: 'Isla' }, type: { en: 'Type', nl: 'Type', fr: 'Type', de: 'Typ', es: 'Tipo' }, facilities: { en: 'Facilities', nl: 'Voorzieningen', fr: 'Équipements', de: 'Ausstattung', es: 'Servicios' }, restaurant: { en: 'Restaurant (meals, snacks, pizzas)', nl: "Restaurant (maaltijden, snacks, pizza's)", fr: 'Restaurant (repas, snacks, pizzas)', de: 'Restaurant (Mahlzeiten, Snacks, Pizza)', es: 'Restaurante (comidas, snacks, pizzas)' }, pool: { en: 'Pool', nl: 'Zwembad', fr: 'Piscine', de: 'Pool', es: 'Piscina' }, nature: { en: 'Natural water (sea, river, lake etc.)', nl: 'Natuurwater (zee, rivier, meer etc.)', fr: 'Eau naturelle (mer, rivière, lac etc.)', de: 'Naturgewässer (Meer, Fluss, See usw.)', es: 'Agua natural (mar, río, lago etc.)' }, wifi: { en: 'WiFi', nl: 'WiFi', fr: 'WiFi', de: 'WLAN', es: 'WiFi' }, accessible: { en: 'Accessible for people with disabilities', nl: 'Geschikt voor mindervaliden', fr: 'Accessible aux personnes handicapées', de: 'Für Menschen mit Behinderungen geeignet', es: 'Accesible para personas con discapacidad' }, singles: { en: 'Singles welcome', nl: 'Alleengaanden welkom', fr: 'Personnes seules bienvenues', de: 'Singles willkommen', es: 'Solteros bienvenidos' }, day: { en: 'Open for day visitors', nl: 'Open voor dagrecreatie', fr: 'Ouvert aux visiteurs à la journée', de: 'Für Tagesgäste geöffnet', es: 'Abierto para visitantes de día' }, target: { en: 'Target audience', nl: 'Doelgroep', fr: 'Public cible', de: 'Zielgruppe', es: 'Público objetivo' }, naturism: { en: 'Type of nude recreation', nl: 'Type naaktrecreatie', fr: 'Type de loisirs naturistes', de: 'Art der Nackterholung', es: 'Tipo de recreación nudista' }, pets: { en: 'Pets', nl: 'Huisdieren', fr: 'Animaux', de: 'Haustiere', es: 'Mascotas' }, access: { en: 'Access requirements', nl: 'Toegangsvoorwaarden', fr: 'Conditions d’accès', de: 'Zugangsvoraussetzungen', es: 'Requisitos de acceso' }, clear: { en: 'Clear filters', nl: 'Filters wissen', fr: 'Effacer les filtres', de: 'Filter löschen', es: 'Borrar filtros' }, searchNow: { en: 'Search now', nl: 'Nu zoeken', fr: 'Rechercher', de: 'Jetzt suchen', es: 'Buscar ahora' }, allCont: { en: 'All continents', nl: 'Alle continenten', fr: 'Tous les continents', de: 'Alle Kontinente', es: 'Todos los continentes' }, allCoun: { en: 'All countries', nl: 'Alle landen', fr: 'Tous les pays', de: 'Alle Länder', es: 'Todos los países' }, allReg: { en: 'All regions', nl: 'Alle regio’s', fr: 'Toutes les régions', de: 'Alle Regionen', es: 'Todas las regiones' }, allDept: { en: 'All departments', nl: 'Alle departementen', fr: 'Tous les départements', de: 'Alle Départements', es: 'Todos los departamentos' }, allIslands: { en: 'Islands with Nude Places', nl: 'Eilanden met Blootlocaties', fr: 'Îles avec lieux', de: 'Inseln mit Orten', es: 'Islas con lugares' }, any: { en: 'No preference', nl: 'Geen voorkeur', fr: 'Aucune préférence', de: 'Keine Präferenz', es: 'Sin preferencia' }, camping: { en: 'Camping', nl: 'Camping', fr: 'Camping', de: 'Camping', es: 'Camping' }, accom: { en: 'Accommodation', nl: 'Accommodatie', fr: 'Hébergement', de: 'Unterkunft', es: 'Alojamiento' }, accomApt: { en: 'Apartments / gîtes', nl: 'Appartementen / gîtes', fr: 'Appartements / gîtes', de: 'Apartments / Gîtes', es: 'Apartamentos / gîtes' }, accomBb: { en: 'B&B', nl: 'B&B', fr: 'Chambre d’hôtes', de: 'B&B / Pension', es: 'B&B' }, accomHotel: { en: 'Hotel', nl: 'Hotel', fr: 'Hôtel', de: 'Hotel', es: 'Hotel' }, accomResort: { en: 'Resort', nl: 'Resort', fr: 'Resort', de: 'Resort', es: 'Resort' }, accomHome: { en: 'Holiday home', nl: 'Vakantiehuis', fr: 'Maison de vacances', de: 'Ferienhaus', es: 'Casa de vacaciones' }, accomVillage: { en: 'Naturist village', nl: 'Naturistendorp', fr: 'Village naturiste', de: 'FKK-Dorf', es: 'Pueblo naturista' }, accomRental: { en: 'Camp rental', nl: 'Campingverhuur', fr: 'Location sur camping', de: 'Miete auf dem Campingplatz', es: 'Alquiler en camping' }, campFamily: { en: 'Family campsites', nl: 'Familiecampings', fr: 'Campings familiaux', de: 'Familien-Campingplätze', es: 'Campings familiares' }, campNature: { en: 'Nature campsites', nl: 'Natuurcampings', fr: 'Campings nature', de: 'Naturcampingplätze', es: 'Campings de naturaleza' }, campClub: { en: 'Naturist / Nudist clubs', nl: 'Verenigingen en clubterreinen', fr: 'Clubs naturistes', de: 'FKK-Vereine', es: 'Clubes naturistas' }, family: { en: 'Family friendly', nl: 'Gezinsvriendelijk', fr: 'Familial', de: 'Familienfreundlich', es: 'Familiar' }, adults: { en: 'Adults only', nl: 'Alleen volwassenen', fr: 'Adultes uniquement', de: 'Nur Erwachsene', es: 'Solo adultos' }, couples: { en: 'Couples only', nl: 'Alleen koppels', fr: 'Couples uniquement', de: 'Nur Paare', es: 'Solo parejas' }, gay: { en: 'Gay only', nl: 'Alleen gay', fr: 'Gay uniquement', de: 'Nur gay', es: 'Solo gay' }, fullNat: { en: 'Fully naturist', nl: 'Naturistisch', fr: 'Naturiste', de: 'Voll FKK', es: 'Naturista' }, cloth: { en: 'Clothing optional', nl: 'Kleding optioneel', fr: 'Tenue facultative', de: 'Textil optional', es: 'Ropa opcional' }, partNat: { en: 'Partly naturist', nl: 'Deels naturistisch', fr: 'Partiellement naturiste', de: 'Teilweise FKK', es: 'Parcialmente naturista' }, noCard: { en: 'No membership card required', nl: 'Toegang zonder lidmaatschapspas', fr: 'Accès sans carte de membre', de: 'Zugang ohne Mitgliedskarte', es: 'Acceso sin tarjeta de membresía' }, yesCard: { en: 'Access only with a membership card', nl: 'Toegang alleen met lidmaatschapspas', fr: 'Accès uniquement avec une carte de membre', de: 'Zugang nur mit Mitgliedskarte', es: 'Acceso solo con tarjeta de miembro' }, petsYes: { en: 'Pets welcome', nl: 'Huisdieren welkom', fr: 'Animaux acceptés', de: 'Haustiere erlaubt', es: 'Mascotas bienvenidas' }, petsNo: { en: 'No pets', nl: 'Geen huisdieren', fr: 'Sans animaux', de: 'Keine Haustiere', es: 'Sin mascotas' }, placesOne: { en: 'Nude Place', nl: 'Blootlocatie', fr: 'lieu', de: 'Ort', es: 'lugar' }, placesMany: { en: 'Nude Places', nl: 'Blootlocaties', fr: 'lieux', de: 'Orte', es: 'lugares' }, free: { en: 'No membership card required', nl: 'Toegang zonder lidmaatschapspas', fr: 'Accès sans carte de membre', de: 'Zugang ohne Mitgliedskarte', es: 'Acceso sin tarjeta de membresía' }, mem: { en: 'Access only with a membership card', nl: 'Toegang alleen met lidmaatschapspas', fr: 'Accès uniquement avec une carte de membre', de: 'Zugang nur mit Mitgliedskarte', es: 'Acceso solo con tarjeta de miembro' }, noRes: { en: 'No results', nl: 'Geen resultaten', fr: 'Aucun résultat', de: 'Keine Ergebnisse', es: 'Sin resultados' }, europe: { en: 'Europe', nl: 'Europa', fr: 'Europe', de: 'Europa', es: 'Europa' }, na: { en: 'North America', nl: 'Noord-Amerika', fr: 'Amérique du Nord', de: 'Nordamerika', es: 'Norteamérica' }, csa: { en: 'Central & South America', nl: 'Midden- en Zuid-Amerika', fr: 'Amérique centrale et du Sud', de: 'Mittel- und Südamerika', es: 'Centroamérica y Sudamérica' }, asia: { en: 'Asia', nl: 'Azië', fr: 'Asie', de: 'Asien', es: 'Asia' }, africa: { en: 'Africa', nl: 'Afrika', fr: 'Afrique', de: 'Afrika', es: 'África' }, oceania: { en: 'Oceania', nl: 'Oceanië', fr: 'Océanie', de: 'Ozeanien', es: 'Oceanía' }, near: { en: 'away', nl: 'afstand', fr: 'de distance', de: 'entfernt', es: 'de distancia' }, tip: { en: 'Excellent choice', nl: 'Uitstekende keuze', fr: 'Excellent choix', de: 'Ausgezeichnete Wahl', es: 'Excelente elección' }, tipPro: { en: 'Recommended', nl: 'Aanrader', fr: 'Recommandé', de: 'Empfehlung', es: 'Recomendado' }, overview: { en: 'Overview', nl: 'Overzicht', fr: 'Aperçu', de: 'Übersicht', es: 'Resumen' }, searchFilters: { en: 'Search filters', nl: 'Zoekfilters', fr: 'Filtres', de: 'Suchfilter', es: 'Filtros' }, verified: { en: 'Verified by the accommodation owners', nl: 'Geverifieerd door de accommodatiehouders', fr: 'Vérifié par les propriétaires de l’hébergement', de: 'Von den Unterkunftsbetreibern verifiziert', es: 'Verificado por los propietarios del alojamiento' }, sponsor: { en: 'Sponsor', nl: 'Sponsor', fr: 'Sponsor', de: 'Sponsor', es: 'Patrocinador' }, sponsordeluxe: { en: 'VIP Sponsor', nl: 'VIP-Sponsor', fr: 'Sponsor VIP', de: 'VIP-Sponsor', es: 'Patrocinador VIP' }, appBanner: { en: 'It looks like you are using a mobile device. For many more search options, use a desktop computer or download our Android app.', nl: 'Zo te zien gebruik je een mobiel apparaat. Voor veel meer zoekfuncties kun je een desktopcomputer gebruiken of onze Android-app downloaden.', fr: 'Il semble que vous utilisiez un appareil mobile. Pour accéder à beaucoup plus de possibilités de recherche, utilisez un ordinateur ou téléchargez notre application Android.', de: 'Es sieht so aus, als würden Sie ein Mobilgerät verwenden. Für deutlich mehr Suchmöglichkeiten nutzen Sie einen Desktop-Computer oder laden Sie unsere Android-App herunter.', es: 'Parece que está utilizando un dispositivo móvil. Para muchas más opciones de búsqueda, utilice un ordenador o descargue nuestra aplicación para Android.' }, appLink: { en: 'Download the app', nl: 'Download de app', fr: 'Télécharger l’app', de: 'App herunterladen', es: 'Descargar la app' }, sortByLocation: { en: 'Results are ordered by distance from your location.', nl: 'Volgorde zoekresultaten op basis van jouw locatie.', fr: 'Les résultats sont triés selon la distance par rapport à votre position.', de: 'Ergebnisse sind nach Entfernung zu Ihrem Standort sortiert.', es: 'Los resultados están ordenados por distancia a su ubicación.' }, sortRestByLocation: { en: 'The rest of the results are ordered by distance from your location.', nl: 'De rest van de zoekresultaten wordt weergegeven op basis van jouw locatie.', fr: 'Le reste des résultats est trié selon la distance par rapport à votre position.', de: 'Die restlichen Ergebnisse sind nach Entfernung zu Ihrem Standort sortiert.', es: 'El resto de los resultados se muestran según la distancia a su ubicación.' }, sortEnableLocation: { en: 'For the best results, please enable location access.', nl: 'Zet je locatie aan voor een betere werking van de site.', fr: 'Activez la localisation pour un meilleur fonctionnement du site.', de: 'Aktivieren Sie den Standort für eine bessere Nutzung der Seite.', es: 'Activa la ubicación para un mejor funcionamiento del sitio.' }, filtersActive: { en: 'Note: search filters are active!', nl: 'Let op: zoekfilters actief!', fr: 'Attention : filtres de recherche actifs !', de: 'Achtung: Suchfilter aktiv!', es: '¡Atención: filtros de búsqueda activos!' }, qNotInFilters: { en: 'This nude place does not match your search criteria', nl: 'Deze blootlocatie valt niet binnen jouw zoekcriteria', fr: 'Ce lieu ne correspond pas à vos critères de recherche', de: 'Dieser Ort entspricht nicht Ihren Suchkriterien', es: 'Este lugar no coincide con sus criterios de búsqueda' }, clearCriteria: { en: 'Clear search criteria', nl: 'Zoekcriteria wissen', fr: 'Effacer les critères de recherche', de: 'Suchkriterien löschen', es: 'Borrar criterios de búsqueda' } }; function t(k) { var m = UI[k]; return (m && (m[lang] || m.en)) || k; } var TREE = { europe: { austria: [], croatia: [], cyprus: [], 'czech-republic': [], denmark: [], finland: [], germany: [], hungary: [], luxembourg: [], montenegro: [], norway: [], poland: [], slovenia: [], sweden: [], switzerland: [], belgium: ['vlaanderen', 'wallonie'], france: { 'auvergne-rhone-alpes': ['ain', 'allier', 'ardeche', 'cantal', 'drome', 'isere', 'loire', 'puy-de-dome', 'rhone', 'savoie', 'haute-savoie'], 'bourgogne-franche-comte': ['doubs', 'jura', 'nievre', 'haute-saone', 'saone-et-loire'], bretagne: ['cotes-darmor', 'finistere', 'ille-et-vilaine', 'morbihan'], 'centre-val-de-loire': ['cher', 'indre', 'indre-et-loire', 'loir-et-cher', 'loiret'], corse: ['corse-du-sud', 'haute-corse'], 'grand-est': ['aube', 'meurthe-et-moselle', 'bas-rhin', 'haut-rhin'], 'hauts-de-france': ['nord', 'oise', 'pas-de-calais'], 'ile-de-france': ['essonne', 'val-de-marne', 'val-doise'], normandie: ['calvados', 'eure', 'manche', 'orne', 'seine-maritime'], 'nouvelle-aquitaine': ['charente', 'charente-maritime', 'correze', 'creuse', 'dordogne', 'gironde', 'landes', 'lot-et-garonne', 'pyrenees-atlantiques', 'deux-sevres', 'vienne', 'haute-vienne'], occitanie: ['ariege', 'aude', 'aveyron', 'gard', 'haute-garonne', 'gers', 'herault', 'lot', 'hautes-pyrenees', 'pyrenees-orientales', 'tarn', 'tarn-et-garonne'], 'pays-de-la-loire': ['loire-atlantique', 'maine-et-loire', 'mayenne', 'vendee'], 'provence-alpes-cote-dazur': ['alpes-de-haute-provence', 'alpes-maritimes', 'bouches-du-rhone', 'var', 'vaucluse'] }, greece: ['crete', 'kefalonia', 'peloponnesos', 'rhode', 'skiathos'], italy: ['abruzzo', 'calabria', 'emilia-romagna', 'lazio', 'liguria', 'molise', 'piemonte', 'puglia', 'sardegna', 'sicilia', 'toscana', 'umbria'], netherlands: ['groningen', 'friesland', 'drenthe', 'overijssel', 'gelderland', 'flevoland', 'utrecht', 'noord-holland', 'zuid-holland', 'zeeland', 'noord-brabant', 'limburg'], portugal: ['alentejo', 'algarve', 'norte'], spain: { andalucia: ['almeria', 'cadiz', 'granada', 'malaga'], cataluna: ['barcelona', 'girona', 'tarragona'], extremadura: ['caceres'], murcia: ['murcia-murcia'], valencia: ['alicante', 'castellon', 'valencia-valencia'], 'balearic-islands': ['majorca'], 'canary-islands': ['fuerteventura', 'gran-canaria', 'lanzarote', 'tenerife'] }, 'united-kingdom': ['england', 'scotland', 'wales'] }, 'north-america': { canada: ['alberta', 'british-columbia', 'manitoba', 'ontario', 'saskatchewan', 'quebec'], 'united-states': ['alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado', 'connecticut', 'delaware', 'florida', 'georgia', 'hawaii', 'idaho', 'illinois', 'indiana', 'iowa', 'kansas', 'kentucky', 'louisiana', 'maine', 'maryland', 'massachusetts', 'michigan', 'minnesota', 'mississippi', 'missouri', 'montana', 'nebraska', 'nevada', 'new-hampshire', 'new-jersey', 'new-mexico', 'new-york', 'north-carolina', 'north-dakota', 'ohio', 'oklahoma', 'oregon', 'pennsylvania', 'rhode-island', 'south-carolina', 'south-dakota', 'tennessee', 'texas', 'utah', 'vermont', 'virginia', 'washington', 'west-virginia', 'wisconsin', 'wyoming'] }, 'central-south-america': { caribbean: ['aruba', 'bonaire', 'curacao', 'dominican-republic', 'guadeloupe', 'jamaica', 'martinique', 'st-martin'], 'central-america': ['costa-rica', 'honduras', 'panama', 'mexico'], 'south-america': ['argentina', 'brazil', 'colombia', 'uruguay'] }, asia: { bali: [], thailand: [], philippines: [] }, africa: { 'south-africa': [] }, oceania: { australia: ['new-south-wales', 'northern-territory', 'queensland', 'south-australia', 'victoria', 'western-australia'], 'new-zealand': { 'north-island': ['auckland', 'waikato', 'bay-of-plenty', 'hawkes-bay', 'taranaki', 'manawatu-wanganui', 'wellington'], 'south-island': ['tasman', 'nelson', 'canterbury', 'otago', 'southland', 'west-coast'] } } }; var FR_DEPT_NUM = { ain: '01', allier: '03', ardeche: '07', cantal: '15', drome: '26', isere: '38', loire: '42', 'puy-de-dome': '63', rhone: '69', savoie: '73', 'haute-savoie': '74', doubs: '25', jura: '39', nievre: '58', 'haute-saone': '70', 'saone-et-loire': '71', 'cotes-darmor': '22', finistere: '29', 'ille-et-vilaine': '35', morbihan: '56', cher: '18', indre: '36', 'indre-et-loire': '37', 'loir-et-cher': '41', loiret: '45', 'corse-du-sud': '2A', 'haute-corse': '2B', aube: '10', 'meurthe-et-moselle': '54', 'bas-rhin': '67', 'haut-rhin': '68', nord: '59', oise: '60', 'pas-de-calais': '62', essonne: '91', 'val-de-marne': '94', 'val-doise': '95', calvados: '14', eure: '27', manche: '50', orne: '61', 'seine-maritime': '76', charente: '16', 'charente-maritime': '17', correze: '19', creuse: '23', dordogne: '24', gironde: '33', landes: '40', 'lot-et-garonne': '47', 'pyrenees-atlantiques': '64', 'deux-sevres': '79', vienne: '86', 'haute-vienne': '87', ariege: '09', aude: '11', aveyron: '12', gard: '30', 'haute-garonne': '31', gers: '32', herault: '34', lot: '46', 'hautes-pyrenees': '65', 'pyrenees-orientales': '66', tarn: '81', 'tarn-et-garonne': '82', 'loire-atlantique': '44', 'maine-et-loire': '49', mayenne: '53', vendee: '85', 'alpes-de-haute-provence': '04', 'alpes-maritimes': '06', 'bouches-du-rhone': '13', var: '83', vaucluse: '84' }; var LABELS = { france: { en: 'France', nl: 'Frankrijk', de: 'Frankreich', fr: 'France', es: 'Francia' }, netherlands: { en: 'Netherlands', nl: 'Nederland', de: 'Niederlande', fr: 'Pays-Bas', es: 'Países Bajos' }, spain: { en: 'Spain', nl: 'Spanje', de: 'Spanien', fr: 'Espagne', es: 'España' }, germany: { en: 'Germany', nl: 'Duitsland', de: 'Deutschland', fr: 'Allemagne', es: 'Alemania' }, belgium: { en: 'Belgium', nl: 'België', de: 'Belgien', fr: 'Belgique', es: 'Bélgica' }, italy: { en: 'Italy', nl: 'Italië', de: 'Italien', fr: 'Italie', es: 'Italia' }, portugal: { en: 'Portugal', nl: 'Portugal', de: 'Portugal', fr: 'Portugal', es: 'Portugal' }, greece: { en: 'Greece', nl: 'Griekenland', de: 'Griechenland', fr: 'Grèce', es: 'Grecia' }, 'united-kingdom': { en: 'United Kingdom', nl: 'Verenigd Koninkrijk', de: 'Vereinigtes Königreich', fr: 'Royaume-Uni', es: 'Reino Unido' }, 'united-states': { en: 'United States', nl: 'Verenigde Staten', de: 'Vereinigte Staaten', fr: 'États-Unis', es: 'Estados Unidos' }, canada: { en: 'Canada', nl: 'Canada', de: 'Kanada', fr: 'Canada', es: 'Canadá' }, australia: { en: 'Australia', nl: 'Australië', de: 'Australien', fr: 'Australie', es: 'Australia' }, 'new-zealand': { en: 'New Zealand', nl: 'Nieuw-Zeeland', de: 'Neuseeland', fr: 'Nouvelle-Zélande', es: 'Nueva Zelanda' }, austria: { en: 'Austria', nl: 'Oostenrijk', de: 'Österreich', fr: 'Autriche', es: 'Austria' }, switzerland: { en: 'Switzerland', nl: 'Zwitserland', de: 'Schweiz', fr: 'Suisse', es: 'Suiza' }, croatia: { en: 'Croatia', nl: 'Kroatië', de: 'Kroatien', fr: 'Croatie', es: 'Croacia' }, hungary: { en: 'Hungary', nl: 'Hongarije', de: 'Ungarn', fr: 'Hongrie', es: 'Hungría' }, luxembourg: { en: 'Luxembourg', nl: 'Luxemburg', de: 'Luxemburg', fr: 'Luxembourg', es: 'Luxemburgo' }, poland: { en: 'Poland', nl: 'Polen', de: 'Polen', fr: 'Pologne', es: 'Polonia' }, crete: { en: 'Crete', nl: 'Kreta', de: 'Kreta', fr: 'Crète', es: 'Creta' }, rhode: { en: 'Rhodes', nl: 'Rhodos', de: 'Rhodos', fr: 'Rhodes', es: 'Rodas' }, 'czech-republic': { en: 'Czech Republic', nl: 'Tsjechië', de: 'Tschechien', fr: 'République tchèque', es: 'República Checa' }, denmark: { en: 'Denmark', nl: 'Denemarken', de: 'Dänemark', fr: 'Danemark', es: 'Dinamarca' }, sweden: { en: 'Sweden', nl: 'Zweden', de: 'Schweden', fr: 'Suède', es: 'Suecia' }, norway: { en: 'Norway', nl: 'Noorwegen', de: 'Norwegen', fr: 'Norvège', es: 'Noruega' }, finland: { en: 'Finland', nl: 'Finland', de: 'Finnland', fr: 'Finlande', es: 'Finlandia' }, thailand: { en: 'Thailand', nl: 'Thailand', de: 'Thailand', fr: 'Thaïlande', es: 'Tailandia' }, brazil: { en: 'Brazil', nl: 'Brazilië', de: 'Brasilien', fr: 'Brésil', es: 'Brasil' }, mexico: { en: 'Mexico', nl: 'Mexico', de: 'Mexiko', fr: 'Mexique', es: 'México' }, argentina: { en: 'Argentina', nl: 'Argentinië', de: 'Argentinien', fr: 'Argentine', es: 'Argentina' }, colombia: { en: 'Colombia', nl: 'Colombia', de: 'Kolumbien', fr: 'Colombie', es: 'Colombia' }, uruguay: { en: 'Uruguay', nl: 'Uruguay', de: 'Uruguay', fr: 'Uruguay', es: 'Uruguay' }, 'costa-rica': { en: 'Costa Rica', nl: 'Costa Rica', de: 'Costa Rica', fr: 'Costa Rica', es: 'Costa Rica' }, honduras: { en: 'Honduras', nl: 'Honduras', de: 'Honduras', fr: 'Honduras', es: 'Honduras' }, panama: { en: 'Panama', nl: 'Panama', de: 'Panama', fr: 'Panama', es: 'Panamá' }, aruba: { en: 'Aruba', nl: 'Aruba', de: 'Aruba', fr: 'Aruba', es: 'Aruba' }, bonaire: { en: 'Bonaire', nl: 'Bonaire', de: 'Bonaire', fr: 'Bonaire', es: 'Bonaire' }, curacao: { en: 'Curaçao', nl: 'Curaçao', de: 'Curaçao', fr: 'Curaçao', es: 'Curazao' }, 'dominican-republic': { en: 'Dominican Republic', nl: 'Dominicaanse Republiek', de: 'Dominikanische Republik', fr: 'République dominicaine', es: 'República Dominicana' }, guadeloupe: { en: 'Guadeloupe', nl: 'Guadeloupe', de: 'Guadeloupe', fr: 'Guadeloupe', es: 'Guadalupe' }, jamaica: { en: 'Jamaica', nl: 'Jamaica', de: 'Jamaika', fr: 'Jamaïque', es: 'Jamaica' }, martinique: { en: 'Martinique', nl: 'Martinique', de: 'Martinique', fr: 'Martinique', es: 'Martinica' }, 'st-martin': { en: 'St. Martin', nl: 'Sint-Maarten', de: 'St. Martin', fr: 'Saint-Martin', es: 'San Martín' }, 'south-africa': { en: 'South Africa', nl: 'Zuid-Afrika', de: 'Südafrika', fr: 'Afrique du Sud', es: 'Sudáfrica' }, 'nouvelle-aquitaine': { en: 'Nouvelle-Aquitaine', nl: 'Nouvelle-Aquitaine', de: 'Nouvelle-Aquitaine', fr: 'Nouvelle-Aquitaine', es: 'Nueva Aquitania' }, bretagne: { en: 'Brittany', nl: 'Bretagne', de: 'Bretagne', fr: 'Bretagne', es: 'Bretaña' }, occitanie: { en: 'Occitanie', nl: 'Occitanie', de: 'Okzitanien', fr: 'Occitanie', es: 'Occitania' }, 'provence-alpes-cote-dazur': { en: "Provence-Alpes-Côte d'Azur", nl: "Provence-Alpes-Côte d'Azur", de: "Provence-Alpes-Côte d'Azur", fr: "Provence-Alpes-Côte d'Azur", es: "Provenza-Alpes-Costa Azul" }, normandie: { en: 'Normandy', nl: 'Normandië', de: 'Normandie', fr: 'Normandie', es: 'Normandía' }, andalucia: { en: 'Andalusia', nl: 'Andalusië', de: 'Andalusien', fr: 'Andalousie', es: 'Andalucía' }, cataluna: { en: 'Catalonia', nl: 'Catalonië', de: 'Katalonien', fr: 'Catalogne', es: 'Cataluña' }, vlaanderen: { en: 'Flanders', nl: 'Vlaanderen', de: 'Flandern', fr: 'Flandre', es: 'Flandes' }, wallonie: { en: 'Wallonia', nl: 'Wallonië', de: 'Wallonien', fr: 'Wallonie', es: 'Valonia' }, 'haute-vienne': { en: 'Haute-Vienne', nl: 'Haute-Vienne', de: 'Haute-Vienne', fr: 'Haute-Vienne', es: 'Haute-Vienne' }, 'central-america': { en: 'Central America', nl: 'Midden-Amerika', fr: 'Amérique centrale', de: 'Mittelamerika', es: 'Centroamérica' }, caribbean: { en: 'Caribbean', nl: 'Caribisch gebied', fr: 'Caraïbes', de: 'Karibik', es: 'Caribe' }, 'south-america': { en: 'South America', nl: 'Zuid-Amerika', fr: 'Amérique du Sud', de: 'Südamerika', es: 'Sudamérica' }, 'north-island': { en: 'North Island', nl: 'North Island', fr: 'Île du Nord', de: 'Nordinsel', es: 'Isla Norte' }, 'south-island': { en: 'South Island', nl: 'South Island', fr: 'Île du Sud', de: 'Südinsel', es: 'Isla Sur' }, 'balearic-islands': { en: 'Balearic Islands', nl: 'Balearen', de: 'Balearen', fr: 'Îles Baléares', es: 'Islas Baleares' }, 'canary-islands': { en: 'Canary Islands', nl: 'Canarische Eilanden', de: 'Kanarische Inseln', fr: 'Îles Canaries', es: 'Islas Canarias' }, majorca: { en: 'Majorca', nl: 'Mallorca', de: 'Mallorca', fr: 'Majorque', es: 'Mallorca' }, tenerife: { en: 'Tenerife', nl: 'Tenerife', de: 'Teneriffa', fr: 'Ténérife', es: 'Tenerife' }, 'gran-canaria': { en: 'Gran Canaria', nl: 'Gran Canaria', de: 'Gran Canaria', fr: 'Grande Canarie', es: 'Gran Canaria' }, lanzarote: { en: 'Lanzarote', nl: 'Lanzarote', de: 'Lanzarote', fr: 'Lanzarote', es: 'Lanzarote' }, fuerteventura: { en: 'Fuerteventura', nl: 'Fuerteventura', de: 'Fuerteventura', fr: 'Fuerteventura', es: 'Fuerteventura' }, groningen: { en: 'Groningen', nl: 'Groningen', de: 'Groningen', fr: 'Groningue', es: 'Groninga' }, friesland: { en: 'Friesland', nl: 'Friesland', de: 'Friesland', fr: 'Frise', es: 'Frisia' }, drenthe: { en: 'Drenthe', nl: 'Drenthe', de: 'Drenthe', fr: 'Drenthe', es: 'Drente' }, overijssel: { en: 'Overijssel', nl: 'Overijssel', de: 'Overijssel', fr: 'Overijssel', es: 'Overijssel' }, gelderland: { en: 'Gelderland', nl: 'Gelderland', de: 'Gelderland', fr: 'Gueldre', es: 'Gueldres' }, flevoland: { en: 'Flevoland', nl: 'Flevoland', de: 'Flevoland', fr: 'Flevoland', es: 'Flevoland' }, utrecht: { en: 'Utrecht', nl: 'Utrecht', de: 'Utrecht', fr: 'Utrecht', es: 'Utrecht' }, 'noord-holland': { en: 'North Holland', nl: 'Noord-Holland', de: 'Nordholland', fr: 'Hollande-Septentrionale', es: 'Holanda Septentrional' }, 'zuid-holland': { en: 'South Holland', nl: 'Zuid-Holland', de: 'Südholland', fr: 'Hollande-Méridionale', es: 'Holanda Meridional' }, zeeland: { en: 'Zeeland', nl: 'Zeeland', de: 'Zeeland', fr: 'Zélande', es: 'Zelanda' }, 'noord-brabant': { en: 'North Brabant', nl: 'Noord-Brabant', de: 'Nordbrabant', fr: 'Brabant-Septentrional', es: 'Brabante Septentrional' }, limburg: { en: 'Limburg', nl: 'Limburg', de: 'Limburg', fr: 'Limbourg', es: 'Limburgo' } }; function continentLabel(id) { var keyMap = { europe: 'europe', 'north-america': 'na', 'central-south-america': 'csa', asia: 'asia', africa: 'africa', oceania: 'oceania' }; return t(keyMap[id] || id); } function label(id, withDeptNum) { var L = LABELS[id]; if (L) return L[lang] || L.en || id; var base = String(id || '').replace(/-/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); }); if (withDeptNum && FR_DEPT_NUM[id]) return base + ' (' + FR_DEPT_NUM[id] + ')'; return base; } var PARENT = {}; (function () { function walk(node, path) { if (Array.isArray(node)) { node.forEach(function (id) { PARENT[id] = path.concat([id]); }); return; } if (node && typeof node === 'object') { Object.keys(node).forEach(function (k) { PARENT[k] = path.concat([k]); walk(node[k], path.concat([k])); }); } } Object.keys(TREE).forEach(function (cont) { walk(TREE[cont], [cont]); }); })(); function getLocationInfo(raw) { var id = String(raw || '').toLowerCase().trim(); var path = PARENT[id]; if (!path || !path.length) return { continent: '', pathParts: [label(id, true)] }; var continent = path[0]; var rest = path.slice(1); var hiddenGroups = { 'central-america': true, caribbean: true, 'south-america': true, 'north-island': true, 'south-island': true }; rest = rest.filter(function (x) { return !hiddenGroups[x]; }); return { continent: continentLabel(continent), pathParts: rest.map(function (x) { return label(x, true); }) }; } function collectIds(node) { var out = {}; function walk(n) { if (Array.isArray(n)) n.forEach(function (id) { out[id] = true; }); else if (n && typeof n === 'object') { Object.keys(n).forEach(function (k) { out[k] = true; walk(n[k]); }); } } walk(node); return out; } function esc(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function norm(s) { return String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim(); } function fillSelect(el, items, selected) { el.innerHTML = items.map(function (it) { return '' + esc(it.l) + ''; }).join(''); if (selected != null) { try { el.value = selected; } catch (e) {} } } function buildIcons(f) { var icons = []; function add(base, file, title) { icons.push(''); } var cats = String(f.field_category || ''); var pets = String(f.field_pets || '').toLowerCase(); var pool = String(f.field_zwembad || '').toLowerCase(); var water = String(f.field_water || '').toLowerCase(); var services = Array.isArray(f.field_services) ? f.field_services.join(' ') : String(f.field_services || ''); var rest = String(f.field_restauratie || '').toLowerCase(); var day = String(f.field_dagrecreatie || '').toLowerCase(); var mem = !!String(f.field_vereniging || '').trim(); if (mem) add(ICON_BASE, 'card.png', t('mem')); else add(ICON_BASE, 'no_card.png', t('free')); if (pets.indexOf('mee') >= 0) add(ICON_BASE, 'pets.png', t('petsYes')); if (pool && pool.indexOf('nee') < 0) add(ICON_BASE, 'pool.png', t('pool')); if (water.indexOf('zee') >= 0) add(ICON_BASE, 'sea.png', t('nature')); else if (water.indexOf('mer') >= 0) add(ICON_BASE, 'lake.png', t('nature')); else if (water.indexOf('riv') >= 0 || water.indexOf('bee') >= 0) add(ICON_BASE, 'river.png', t('nature')); else if (water.trim()) add(ICON_BASE, 'lake.png', t('nature')); if (services.toLowerCase().indexOf('wif') >= 0) add(ICON_BASE, 'wifi.png', t('wifi')); if (rest.split(',').some(function (c) { return ['res', 'rac', 'hlf', 'vol', 'all', 'sna', 'piz', 'bar'].indexOf(c.trim()) >= 0; })) add(ICON_BASE, 'restaurant.png', t('restaurant')); if (cats.indexOf('1736') >= 0) add(ICON_BASE, 'wheelchair_yes.png', t('accessible')); if (day.indexOf('gzb') >= 0) add(ICON_BASE, 'dagrecreatie.png', t('day')); if (!icons.length) return ''; return '' + icons.join('') + ''; } function normalizePro(val) { return String(val || '').toLowerCase().replace(/\s+/g, ' ').trim(); } function getProLevelFromFields(f) { var val = normalizePro(f && f.field_pro_pakket); if (!val) return null; if (val.indexOf('lux') >= 0) return 'lux'; if (val.indexOf('pro') >= 0) return 'pro'; return null; } function buildNameBadges(f) { var level = getProLevelFromFields(f); var html = ''; var geverifieerd = String(f.field_geverifieerd || '').toLowerCase().trim(); if (geverifieerd === 'kls') { html += ''; } if (level === 'lux') { html += ''; } else if (level === 'pro') { html += ''; } if (!html) return ''; return '' + html + ''; } function updateLangFlags() { var buttons = document.querySelectorAll('#langFlags button'); for (var i = 0; i < buttons.length; i++) { var btn = buttons[i]; if (btn.getAttribute('data-lang') === lang) btn.classList.add('active'); else btn.classList.remove('active'); } } function updateAppBanner() { var el = document.getElementById('appBanner'); if (!el) return; el.innerHTML = esc(t('appBanner')) + ' ' + esc(t('appLink')) + ''; } function applyUi() { document.getElementById('secSearch').textContent = t('search'); document.getElementById('secLocation').textContent = t('location'); document.getElementById('lblContinent').textContent = t('continent'); document.getElementById('lblCountry').textContent = t('country'); document.getElementById('lblRegion').textContent = t('region'); document.getElementById('secType').textContent = t('type'); document.getElementById('secFacilities').textContent = t('facilities'); document.getElementById('lblRestaurant').textContent = t('restaurant'); document.getElementById('lblPool').textContent = t('pool'); document.getElementById('lblNature').textContent = t('nature'); document.getElementById('lblWifi').textContent = t('wifi'); document.getElementById('lblAccess').textContent = t('accessible'); document.getElementById('lblSingles').textContent = t('singles'); document.getElementById('lblDay').textContent = t('day'); document.getElementById('secTarget').textContent = t('target'); document.getElementById('secNaturism').textContent = t('naturism'); document.getElementById('secPets').textContent = t('pets'); document.getElementById('secAccess').textContent = t('access'); document.getElementById('btnReset').textContent = t('clear'); document.getElementById('btnSearchNow').textContent = t('searchNow'); document.getElementById('btnViewResults').textContent = t('overview'); document.getElementById('btnViewFilters').textContent = t('searchFilters'); document.getElementById('btnClearCriteria').textContent = t('clearCriteria'); updateLangFlags(); updateAppBanner(); fillDept(); fillSelect(document.getElementById('continent'), [ { v: 'any', l: t('allCont') }, { v: 'europe', l: t('europe') }, { v: 'north-america', l: t('na') }, { v: 'central-south-america', l: t('csa') }, { v: 'asia', l: t('asia') }, { v: 'africa', l: t('africa') }, { v: 'oceania', l: t('oceania') } ], document.getElementById('continent').value || 'any'); fillSelect(document.getElementById('mainType'), [ { v: 'any', l: t('any') }, { v: 'camping', l: t('camping') }, { v: 'accommodatie', l: t('accom') } ], document.getElementById('mainType').value || 'any'); fillSelect(document.getElementById('campType'), [ { v: 'any', l: t('any') }, { v: '668', l: t('campFamily') }, { v: '671', l: t('campNature') }, { v: '672', l: t('campClub') } ], document.getElementById('campType').value || 'any'); fillSelect(document.getElementById('accomType'), [ { v: 'any', l: t('any') }, { v: '661', l: t('accomApt') }, { v: '662', l: t('accomBb') }, { v: '663', l: t('accomHotel') }, { v: '664', l: t('accomResort') }, { v: '666', l: t('accomHome') }, { v: '667', l: t('accomVillage') }, { v: '669', l: t('accomRental') } ], document.getElementById('accomType').value || 'any'); fillSelect(document.getElementById('doelgroep'), [ { v: 'any', l: t('any') }, { v: 'everyone', l: t('family') }, { v: 'ado', l: t('adults') }, { v: 'apo', l: t('couples') }, { v: 'hom', l: t('gay') } ], document.getElementById('doelgroep').value || 'any'); fillSelect(document.getElementById('naturisme'), [ { v: 'any', l: t('any') }, { v: '679', l: t('fullNat') }, { v: '680', l: t('cloth') }, { v: '681', l: t('partNat') } ], document.getElementById('naturisme').value || 'any'); fillSelect(document.getElementById('honden'), [ { v: 'any', l: t('any') }, { v: 'yes', l: t('petsYes') }, { v: 'no', l: t('petsNo') } ], document.getElementById('honden').value || 'any'); fillSelect(document.getElementById('pasje'), [ { v: 'any', l: t('any') }, { v: 'no', l: t('noCard') }, { v: 'yes', l: t('yesCard') } ], document.getElementById('pasje').value || 'any'); fillCountry(); updateQFiltersWarn(); } function fillCountry() { var cont = document.getElementById('continent').value; var wrap = document.getElementById('countryWrap'); var regWrap = document.getElementById('regionWrap'); var deptWrap = document.getElementById('deptWrap'); if (!cont || cont === 'any' || !TREE[cont]) { wrap.style.display = 'none'; regWrap.style.display = 'none'; deptWrap.style.display = 'none'; return; } var countries = Object.keys(TREE[cont]); if (cont === 'central-south-america') { countries = ['central-america', 'caribbean', 'south-america']; } else { countries.sort(function (a, b) { return label(a).localeCompare(label(b), lang); }); } var prev = document.getElementById('country').value; fillSelect(document.getElementById('country'), [{ v: '', l: t('allCoun') }].concat(countries.map(function (id) { return { v: id, l: label(id) }; })), prev); wrap.style.display = 'block'; fillRegion(); } function fillRegion() { var cont = document.getElementById('continent').value; var country = document.getElementById('country').value; var regWrap = document.getElementById('regionWrap'); var deptWrap = document.getElementById('deptWrap'); var node = TREE[cont] && TREE[cont][country]; if (!country || node == null) { regWrap.style.display = 'none'; deptWrap.style.display = 'none'; return; } var ids = Array.isArray(node) ? node.slice() : Object.keys(node); if (!ids.length) { regWrap.style.display = 'none'; deptWrap.style.display = 'none'; return; } if (cont === 'oceania' && country === 'new-zealand') { ids = ['north-island', 'south-island']; } else if (country === 'netherlands' && Array.isArray(node)) { ids = node.slice(); } else { ids.sort(function (a, b) { return label(a).localeCompare(label(b), lang); }); } var prev = document.getElementById('region').value; fillSelect(document.getElementById('region'), [{ v: '', l: t('allReg') }].concat(ids.map(function (id) { return { v: id, l: label(id) }; })), prev); regWrap.style.display = 'block'; fillDept(); } function isSpainIslandsRegion(region) { return region === 'balearic-islands' || region === 'canary-islands'; } function fillDept() { var cont = document.getElementById('continent').value; var country = document.getElementById('country').value; var region = document.getElementById('region').value; var deptWrap = document.getElementById('deptWrap'); var lbl = document.getElementById('lblDept'); var isFrance = cont === 'europe' && country === 'france' && region; var isSpainIsl = cont === 'europe' && country === 'spain' && isSpainIslandsRegion(region); if (!isFrance && !isSpainIsl) { deptWrap.style.display = 'none'; return; } var node = isFrance ? TREE.europe.france[region] : TREE.europe.spain[region]; if (!Array.isArray(node) || !node.length) { deptWrap.style.display = 'none'; return; } lbl.textContent = isSpainIsl ? t('island') : t('department'); var allLabel = isSpainIsl ? t('allIslands') : t('allDept'); var ids = node.slice(); ids.sort(function (a, b) { return label(a).localeCompare(label(b), lang); }); var prev = document.getElementById('dept').value; fillSelect(document.getElementById('dept'), [{ v: '', l: allLabel }].concat(ids.map(function (id) { return { v: id, l: label(id, false) }; })), prev); deptWrap.style.display = 'block'; } function allowedIds(st) { if (!st.country || !TREE[st.continent]) return null; var node = TREE[st.continent][st.country]; if (node == null) return null; if (st.dept && st.region) { if ((st.country === 'france') || (st.country === 'spain' && isSpainIslandsRegion(st.region))) { var o = {}; o[st.dept] = true; return o; } } if (st.region) { if (Array.isArray(node)) { var one = {}; one[st.region] = true; return one; } if (node[st.region] != null) { var set = collectIds(node[st.region]); set[st.region] = true; return set; } var o2 = {}; o2[st.region] = true; return o2; } var allSet = collectIds(node); allSet[st.country] = true; return allSet; } function matchesContinent(raw, cont) { if (!cont || cont === 'any') return true; if (!TREE[cont]) return false; if (TREE[cont][raw] != null) return true; var found = false; Object.keys(TREE[cont]).forEach(function (c) { var ids = collectIds(TREE[cont][c]); ids[c] = true; if (ids[raw]) found = true; }); return found; } function readState() { var mainType = document.getElementById('mainType').value; var accomType = document.getElementById('accomType').value; var campType = document.getElementById('campType').value; var types = {}; if (mainType === 'camping') { if (campType === 'any') { types['668'] = 1; types['671'] = 1; types['672'] = 1; types['675'] = 1; } else { types[campType] = 1; } } else if (mainType === 'accommodatie') { if (accomType === 'any') { ['661','662','663','664','665','666','667','669'].forEach(function (x) { types[x] = 1; }); } else types[accomType] = 1; } return { continent: document.getElementById('continent').value, country: document.getElementById('country').value, region: document.getElementById('region').value, dept: document.getElementById('dept').value, mainType: mainType, accomType: accomType, campType: campType, types: types, doelgroep: document.getElementById('doelgroep').value, naturisme: document.getElementById('naturisme').value, restaurant: document.getElementById('restaurant').checked, zwembad: document.getElementById('zwembad').checked, natuurwater: document.getElementById('natuurwater').checked, wifi: document.getElementById('wifi').checked, mindervaliden: document.getElementById('mindervaliden').checked, alleengaanden: document.getElementById('alleengaanden').checked, dagrecreatie: document.getElementById('dagrecreatie').checked, pasje: document.getElementById('pasje').value, honden: document.getElementById('honden').value, q: document.getElementById('q').value.trim() }; } function saveFilterState() { try { sessionStorage.setItem(FILTER_STATE_KEY, JSON.stringify(readState())); } catch (e) {} } function clearSavedFilters() { try { sessionStorage.removeItem(FILTER_STATE_KEY); sessionStorage.removeItem(FILTER_RESTORE_KEY); } catch (e) {} } function restoreFiltersFromStorage() { var raw = null; try { raw = sessionStorage.getItem(FILTER_RESTORE_KEY) || sessionStorage.getItem(FILTER_STATE_KEY); } catch (e) {} if (!raw) return false; var st; try { st = JSON.parse(raw); } catch (e) { return false; } if (!st || typeof st !== 'object') return false; try { sessionStorage.removeItem(FILTER_RESTORE_KEY); } catch (e) {} suppressSave = true; try { document.getElementById('q').value = st.q || ''; document.getElementById('continent').value = st.continent || 'any'; fillCountry(); document.getElementById('country').value = st.country || ''; fillRegion(); document.getElementById('region').value = st.region || ''; fillDept(); document.getElementById('dept').value = st.dept || ''; var mainType = st.mainType || 'any'; if (!st.mainType && st.types) { var keys = Object.keys(st.types); if (keys.indexOf('668') >= 0 || keys.indexOf('671') >= 0 || keys.indexOf('672') >= 0 || keys.indexOf('675') >= 0) { mainType = 'camping'; } else if (keys.length) { mainType = 'accommodatie'; } } document.getElementById('mainType').value = mainType; document.getElementById('subAccom').style.display = mainType === 'accommodatie' ? 'block' : 'none'; document.getElementById('subCamping').style.display = mainType === 'camping' ? 'block' : 'none'; var accomType = st.accomType || 'any'; if (!st.accomType && st.types && mainType === 'accommodatie') { var tkeys = Object.keys(st.types); if (tkeys.length === 1) accomType = tkeys[0]; } document.getElementById('accomType').value = accomType; var campType = st.campType || 'any'; if (!st.campType && st.types && mainType === 'camping') { var ckeys = Object.keys(st.types); if (ckeys.length === 1) campType = ckeys[0]; } document.getElementById('campType').value = campType; document.getElementById('doelgroep').value = st.doelgroep || 'any'; document.getElementById('naturisme').value = st.naturisme || 'any'; document.getElementById('pasje').value = st.pasje || 'any'; document.getElementById('honden').value = st.honden || 'any'; ['restaurant','zwembad','natuurwater','wifi','mindervaliden','alleengaanden','dagrecreatie'].forEach(function (id) { var el = document.getElementById(id); if (el) el.checked = !!st[id]; }); } finally { suppressSave = false; } return true; } function hasActiveFilter(st) { if (!st) return false; if (st.q) return true; if (st.continent && st.continent !== 'any') return true; if (st.country) return true; if (st.region) return true; if (st.dept) return true; if (Object.keys(st.types || {}).length) return true; if (st.doelgroep && st.doelgroep !== 'any') return true; if (st.naturisme && st.naturisme !== 'any') return true; if (st.pasje && st.pasje !== 'any') return true; if (st.honden && st.honden !== 'any') return true; if (st.restaurant || st.zwembad || st.natuurwater || st.wifi || st.mindervaliden || st.alleengaanden || st.dagrecreatie) return true; return false; } function hasOtherFiltersActive(st) { st = st || readState(); if (st.continent && st.continent !== 'any') return true; if (st.country) return true; if (st.region) return true; if (st.dept) return true; if (Object.keys(st.types || {}).length) return true; if (st.doelgroep && st.doelgroep !== 'any') return true; if (st.naturisme && st.naturisme !== 'any') return true; if (st.pasje && st.pasje !== 'any') return true; if (st.honden && st.honden !== 'any') return true; if (st.restaurant || st.zwembad || st.natuurwater || st.wifi || st.mindervaliden || st.alleengaanden || st.dagrecreatie) return true; return false; } function matchesQuery(loc, q) { if (!q) return true; var f = loc.fields || {}; var raw = String(f.field_country || '').toLowerCase().trim(); var info = getLocationInfo(raw); var pathStr = (info.pathParts || []).join(' '); var blob = norm([f.field_name, f.field_city, info.continent, pathStr].join(' ')); return blob.indexOf(norm(q)) >= 0; } function updateQFiltersWarn() { var el = document.getElementById('qFiltersWarn'); var textEl = document.getElementById('qFiltersWarnText'); if (!el || !textEl) return; var st = readState(); var q = st.q; if (!q || !all.length || !hasOtherFiltersActive(st)) { el.classList.remove('visible'); textEl.textContent = ''; return; } var qMatches = all.filter(function (loc) { return matchesQuery(loc, q); }); if (!qMatches.length) { el.classList.remove('visible'); textEl.textContent = ''; return; } var shown = filterList(st); if (shown.length > 0) { el.classList.remove('visible'); textEl.textContent = ''; return; } textEl.textContent = t('qNotInFilters'); document.getElementById('btnClearCriteria').textContent = t('clearCriteria'); el.classList.add('visible'); } function updateResetButton(st) { var btn = document.getElementById('btnReset'); if (hasActiveFilter(st || readState())) btn.classList.add('visible'); else btn.classList.remove('visible'); } function countryHasRegions(continent, country) { if (!continent || !country || !TREE[continent]) return false; var node = TREE[continent][country]; if (node == null) return false; if (Array.isArray(node)) return node.length > 0; if (typeof node === 'object') return Object.keys(node).length > 0; return false; } function getProLevel(loc) { return getProLevelFromFields(loc.fields || {}); } function isVerified(loc) { return String((loc.fields || {}).field_geverifieerd || '').toLowerCase().trim() === 'kls'; } function isTipActive(loc, st) { if (!st || !st.country) return false; if (!st.continent || st.continent === 'any') return false; var level = getProLevel(loc); if (!level) return false; if (level === 'lux') return true; if (countryHasRegions(st.continent, st.country)) return !!st.region; return true; } function noLocationRank(loc) { var level = getProLevel(loc); var verified = isVerified(loc); if (level === 'lux') return verified ? 0 : 1; if (level === 'pro') return verified ? 2 : 3; if (verified) return 4; return 5; } function sponsorPriority(loc, st) { if (!st || !st.country || !st.continent || st.continent === 'any') return 2; var level = getProLevel(loc); if (level === 'lux') return 0; if (level === 'pro') { if (st.dept || st.region) return 1; return 2; } return 2; } function filterList(st) { var allow = allowedIds(st); return all.filter(function (loc) { var f = loc.fields || {}; var raw = String(f.field_country || '').toLowerCase().trim(); if (!matchesContinent(raw, st.continent)) return false; if (allow && !allow[raw]) return false; if (st.doelgroep && st.doelgroep !== 'any') { var doelgroep = String(f.field_doelgroep || '').toLowerCase().trim(); if (st.doelgroep === 'everyone') { if (doelgroep.indexOf('ado') >= 0 || doelgroep.indexOf('adults only') >= 0 || doelgroep.indexOf('apo') >= 0 || doelgroep.indexOf('couples only') >= 0 || doelgroep.indexOf('hom') >= 0 || doelgroep.indexOf('hgm') >= 0 || doelgroep.indexOf('gay') >= 0) return false; } else if (st.doelgroep === 'hom') { if (doelgroep.indexOf('hom') < 0 && doelgroep.indexOf('hgm') < 0 && doelgroep.indexOf('gay') < 0) return false; } else if (doelgroep.indexOf(st.doelgroep) < 0) return false; } if (st.restaurant) { var rest = String(f.field_restauratie || '').toLowerCase(); if (!rest.trim()) return false; if (!rest.split(',').some(function (c) { return ['res','rac','hlf','vol','all'].indexOf(c.trim()) >= 0; })) return false; } if (st.dagrecreatie && String(f.field_dagrecreatie || '').toLowerCase().indexOf('gzb') < 0) return false; var typeKeys = Object.keys(st.types); if (typeKeys.length) { var ids = String(f.field_category || '').split(',').map(function (x) { return x.trim(); }); var ok = false; typeKeys.forEach(function (tk) { if (ids.indexOf(tk) >= 0) ok = true; }); if (!ok) return false; } if (st.naturisme !== 'any') { var ids2 = String(f.field_category || '').split(',').map(function (x) { return x.trim(); }); if (st.naturisme === '681') { if (ids2.indexOf('681') < 0 && ids2.indexOf('682') < 0) return false; } else if (ids2.indexOf(st.naturisme) < 0) return false; } if (st.zwembad) { var z = String(f.field_zwembad || '').toLowerCase(); if (!z || z.indexOf('nee') >= 0) return false; } if (st.natuurwater && !String(f.field_water || '').trim()) return false; if (st.alleengaanden && String(f.field_category || '').indexOf('676') < 0) return false; if (st.mindervaliden && String(f.field_category || '').indexOf('1736') < 0) return false; if (st.honden === 'yes' && String(f.field_pets || '').toLowerCase().indexOf('mee') < 0) return false; if (st.honden === 'no' && String(f.field_pets || '').toLowerCase().indexOf('nee') < 0) return false; if (st.wifi) { var services = Array.isArray(f.field_services) ? f.field_services.join(' ') : String(f.field_services || ''); if (services.toLowerCase().indexOf('wif') < 0) return false; } if (st.pasje !== 'any') { var free = !String(f.field_vereniging || '').trim(); if (st.pasje === 'yes' && free) return false; if (st.pasje === 'no' && !free) return false; } if (st.q && !matchesQuery(loc, st.q)) return false; return true; }); } function distanceKm(lat1, lon1, lat2, lon2) { var R = 6371; var dLat = (lat2 - lat1) * Math.PI / 180; var dLon = (lon2 - lon1) * Math.PI / 180; var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function sortResults(list, st) { return list .map(function (loc, index) { var f = loc.fields || {}; var lat = parseFloat(f.field_gmap_lat); var lng = parseFloat(f.field_gmap_lng); var distance = userLocation && Number.isFinite(lat) && Number.isFinite(lng) ? distanceKm(userLocation.lat, userLocation.lng, lat, lng) : Number.POSITIVE_INFINITY; loc._distance = distance; loc._originalIndex = index; loc._tipActive = isTipActive(loc, st); loc._noLocRank = noLocationRank(loc); loc._sponsorPrio = sponsorPriority(loc, st); loc._rand = Math.random(); return loc; }) .sort(function (a, b) { if (!userLocation) { if (a._noLocRank !== b._noLocRank) return a._noLocRank - b._noLocRank; return a._rand - b._rand; } if (a._sponsorPrio !== b._sponsorPrio) return a._sponsorPrio - b._sponsorPrio; if (a._distance !== b._distance) return a._distance - b._distance; return a._rand - b._rand; }); } function formatDistance(km) { if (!Number.isFinite(km)) return ''; if (km < 1) return Math.round(km * 1000) + ' m'; if (km < 10) return km.toFixed(1) + ' km ' + t('near'); return Math.round(km) + ' km ' + t('near'); } function formatCount(n) { if (n === 1) return '1 ' + t('placesOne'); return n + ' ' + t('placesMany'); } function isTiny() { return window.matchMedia('(max-width: 500px)').matches; } function updateSortNote(list) { var el = document.getElementById('sortNote'); if (!el) return; var sponsorsBoosted = false; if (list && list.length) { for (var i = 0; i < list.length; i++) { if (list[i]._sponsorPrio != null && list[i]._sponsorPrio < 2) { sponsorsBoosted = true; break; } } } if (userLocation && sponsorsBoosted) { el.style.display = 'none'; el.textContent = ''; return; } if (userLocation) { el.style.display = 'block'; el.textContent = t('sortByLocation'); } else { el.style.display = 'block'; el.textContent = t('sortEnableLocation'); } } function render(list) { var st = readState(); list = sortResults(list.slice(), st); document.getElementById('count').textContent = formatCount(list.length); updateSortNote(list); var el = document.getElementById('results'); var tiny = isTiny(); if (!list.length) { el.innerHTML = '' + esc(t('noRes')) + ''; return; } var html = ''; var lastSponsorIndex = -1; for (var i = 0; i < list.length; i++) { if (list[i]._sponsorPrio != null && list[i]._sponsorPrio < 2) { lastSponsorIndex = i; } } list.forEach(function (loc, index) { var f = loc.fields || {}; var mem = !!String(f.field_vereniging || '').trim(); var img = f.field_image || ''; var info = getLocationInfo(f.field_country); var tipActive = isTipActive(loc, st); var href = loc.sid != null ? '/detail.html?sid=' + encodeURIComponent(loc.sid) : '#'; html += ''; html += img ? '' : ''; html += ''; if (tipActive) { var level = getProLevel(loc); if (level === 'lux') { html += '' + esc(t('tip')) + ''; } else if (level === 'pro') { html += '' + esc(t('tipPro')) + ''; } } html += '' + esc(f.field_name || '') + '' + buildNameBadges(f) + ''; var address = [f.field_street, f.field_zipcode, f.field_city].filter(Boolean).join(', '); if (address) html += '' + esc(address) + ''; if (info.pathParts && info.pathParts.length) { if (tiny) { html += ''; html += info.pathParts.map(function (p) { return esc(p); }).join('→'); html += ''; } else { html += ''; if (info.continent) html += '' + esc(info.continent) + ''; if (info.continent && info.pathParts.length) html += '→'; html += info.pathParts.map(function (p) { return esc(p); }).join('→'); html += ''; } } else if (!tiny && info.continent) { html += '' + esc(info.continent) + ''; } if (Number.isFinite(loc._distance) && loc._distance !== Number.POSITIVE_INFINITY) { html += '' + esc(formatDistance(loc._distance)) + ''; } html += '' + esc(mem ? t('mem') : t('free')) + ''; html += buildIcons(f); html += ''; if (index === lastSponsorIndex && userLocation && lastSponsorIndex < list.length - 1) { html += '' + esc(t('sortRestByLocation')) + ''; } }); el.innerHTML = html; document.querySelectorAll('#results a.card').forEach(function (a) { a.addEventListener('click', function () { try { var href = a.getAttribute('href') || ''; var m = href.match(/[?&]sid=([^&]+)/); if (!m) return; var id = decodeURIComponent(m[1]); var found = null; for (var i = 0; i < all.length; i++) { if (String(all[i].sid) === String(id)) { found = all[i]; break; } } if (found) { sessionStorage.setItem('np_detail_place', JSON.stringify(found)); } } catch (e) {} }); }); } function scrollResultsToTop() { var el = document.getElementById('count') || document.getElementById('results'); if (el && el.scrollIntoView) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } } function scrollToResultsIfNeeded() { try { if (sessionStorage.getItem('np_scroll_to_results') !== '1') return; sessionStorage.removeItem('np_scroll_to_results'); } catch (e) { return; } setTimeout(function () { var el = document.getElementById('count'); if (el && el.scrollIntoView) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } }, 120); } function run(fromUser) { try { var st = readState(); updateResetButton(st); updateQFiltersWarn(); saveFilterState(); render(filterList(st)); if (fromUser && !isMobileLayout()) { scrollResultsToTop(); } } catch (e) { document.getElementById('debug').style.display = 'block'; document.getElementById('debug').textContent = String(e); } } function searchNow() { run(false); scrollResultsToTop(); } function getUserLocation() { if (!navigator.geolocation) { run(); return; } navigator.geolocation.getCurrentPosition( function (position) { userLocation = { lat: position.coords.latitude, lng: position.coords.longitude }; run(); }, function () { run(); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } function setViewMode(mode) { document.body.classList.remove('view-results', 'view-filters'); document.body.classList.add(mode === 'filters' ? 'view-filters' : 'view-results'); document.getElementById('btnViewResults').classList.toggle('active', mode !== 'filters'); document.getElementById('btnViewFilters').classList.toggle('active', mode === 'filters'); } function onFilterChange() { if (isMobileLayout()) { run(false); return; } run(true); } function clearOtherFiltersKeepQ() { var qVal = document.getElementById('q').value; clearSavedFilters(); document.getElementById('continent').value = 'any'; document.getElementById('country').value = ''; document.getElementById('region').value = ''; document.getElementById('dept').value = ''; document.getElementById('mainType').value = 'any'; document.getElementById('subAccom').style.display = 'none'; document.getElementById('subCamping').style.display = 'none'; document.getElementById('accomType').value = 'any'; document.getElementById('campType').value = 'any'; ['restaurant','zwembad','natuurwater','wifi','mindervaliden','alleengaanden','dagrecreatie'] .forEach(function (id) { document.getElementById(id).checked = false; }); document.getElementById('doelgroep').value = 'any'; document.getElementById('naturisme').value = 'any'; document.getElementById('pasje').value = 'any'; document.getElementById('honden').value = 'any'; document.getElementById('q').value = qVal; fillCountry(); updateQFiltersWarn(); if (isMobileLayout()) run(false); else run(true); } function bind() { document.getElementById('continent').onchange = function () { document.getElementById('country').value = ''; document.getElementById('region').value = ''; document.getElementById('dept').value = ''; fillCountry(); onFilterChange(); }; document.getElementById('country').onchange = function () { document.getElementById('region').value = ''; document.getElementById('dept').value = ''; fillRegion(); onFilterChange(); }; document.getElementById('region').onchange = function () { document.getElementById('dept').value = ''; fillDept(); onFilterChange(); }; document.getElementById('dept').onchange = function () { onFilterChange(); }; document.getElementById('q').oninput = function () { updateQFiltersWarn(); onFilterChange(); }; document.getElementById('q').onfocus = function () { updateQFiltersWarn(); }; document.getElementById('mainType').onchange = function () { var isCamping = this.value === 'camping'; var isAccom = this.value === 'accommodatie'; document.getElementById('subCamping').style.display = isCamping ? 'block' : 'none'; document.getElementById('subAccom').style.display = isAccom ? 'block' : 'none'; onFilterChange(); }; ['accomType','campType','doelgroep','naturisme','pasje','honden','restaurant','zwembad', 'natuurwater','wifi','mindervaliden','alleengaanden','dagrecreatie'].forEach(function (id) { var el = document.getElementById(id); if (el) el.onchange = function () { onFilterChange(); }; }); document.getElementById('btnSearchNow').onclick = function () { searchNow(); }; document.getElementById('btnClearCriteria').onclick = function () { clearOtherFiltersKeepQ(); }; document.getElementById('btnReset').onclick = function () { clearSavedFilters(); document.getElementById('q').value = ''; document.getElementById('continent').value = 'any'; document.getElementById('country').value = ''; document.getElementById('region').value = ''; document.getElementById('dept').value = ''; document.getElementById('mainType').value = 'any'; document.getElementById('subAccom').style.display = 'none'; document.getElementById('subCamping').style.display = 'none'; document.getElementById('accomType').value = 'any'; document.getElementById('campType').value = 'any'; ['restaurant','zwembad','natuurwater','wifi','mindervaliden','alleengaanden','dagrecreatie'] .forEach(function (id) { document.getElementById(id).checked = false; }); document.getElementById('doelgroep').value = 'any'; document.getElementById('naturisme').value = 'any'; document.getElementById('pasje').value = 'any'; document.getElementById('honden').value = 'any'; fillCountry(); updateQFiltersWarn(); if (isMobileLayout()) run(false); else run(true); }; var flagBtns = document.querySelectorAll('#langFlags button'); for (var i = 0; i < flagBtns.length; i++) { flagBtns[i].onclick = function () { lang = this.getAttribute('data-lang'); localStorage.setItem('np_lang', lang); applyUi(); run(true); }; } document.getElementById('btnViewResults').onclick = function () { setViewMode('results'); }; document.getElementById('btnViewFilters').onclick = function () { setViewMode('filters'); }; var lastTiny = isTiny(); window.addEventListener('resize', function () { var now = isTiny(); if (now !== lastTiny) { lastTiny = now; run(); } }); window.addEventListener('pageshow', function () { if (!all.length) return; if (restoreFiltersFromStorage()) run(false); scrollToResultsIfNeeded(); }); } function afterDataReady() { restoreFiltersFromStorage(); run(false); getUserLocation(); scrollToResultsIfNeeded(); } function fetchPlacesNetwork() { return fetch(API + '?t=' + Date.now(), { cache: 'no-store' }) .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .then(function (data) { var list = Array.isArray(data) ? data : []; saveLocalStorageCache(list); return list; }); } function loadData() { var reloaded = isPageReload(); if (reloaded) clearPlacesCache(); if (!reloaded) { var fromLS = tryLocalStorageCache(); if (fromLS) { all = fromLS; afterDataReady(); fetchVersion().then(function (remote) { var remotePlaces = remote && remote.places != null ? Number(remote.places) : null; var storedPlaces = getStoredVersions().places != null ? Number(getStoredVersions().places) : null; if (remotePlaces != null && storedPlaces != null && remotePlaces !== storedPlaces) { fetchPlacesNetwork() .then(function (list) { all = list; var v = getStoredVersions(); v.places = remotePlaces; setStoredVersions(v); run(false); }) .catch(function () {}); } }); return; } } var done = false; var safety = setTimeout(function () { if (!done) { done = true; document.getElementById('count').textContent = 'Error'; document.getElementById('debug').style.display = 'block'; document.getElementById('debug').textContent = 'Timeout loading data'; } }, 20000); fetchVersion().then(function () {}).catch(function () {}); fetchPlacesNetwork() .then(function (list) { if (done) return; done = true; clearTimeout(safety); all = list; fetchVersion().then(function (remote) { if (remote && remote.places != null) { var v = getStoredVersions(); v.places = Number(remote.places); setStoredVersions(v); } }); afterDataReady(); }) .catch(function (e) { if (done) return; done = true; clearTimeout(safety); var fallback = tryLocalStorageCache(); if (fallback) { all = fallback; afterDataReady(); return; } document.getElementById('count').textContent = 'Error'; document.getElementById('debug').style.display = 'block'; document.getElementById('debug').textContent = String(e); }); } bind(); applyUi(); document.getElementById('count').textContent = 'Loading...'; loadData(); })();