Cottages aan Rice Lake
Vind de cottage die bij je verblijf in Elmhirst’s Resort past
Ontdek de cottages van Elmhirst’s Resort aan de noordelijke oever van Rice Lake. Bekijk foto's, capaciteit en beschrijvingen voordat je prijs en beschikbaarheid controleert.
Cottages laden …
Meer cottages tonen
/* =========================================================
BNO TRAVEL – FELLES BULK 3 / DESTINASJONSKATALOG V2
Samme kort, modal, mobilvisning og prissjekk på alle sider
========================================================= */
.bno-direct-catalog,
.bno-direct-modal {
--bno-navy: #0c1a38;
--bno-navy-hover: #162b55;
--bno-text: #3f4a5e;
--bno-muted: #5b667b;
--bno-border: rgba(12, 26, 56, 0.14);
--bno-soft: #eef3f8;
--bno-white: #ffffff;
--bno-success-bg: #edf8f1;
--bno-success: #176b35;
--bno-error-bg: #fff1f1;
--bno-error: #8f1d1d;
}
.bno-direct-catalog,
.bno-direct-catalog *,
.bno-direct-modal,
.bno-direct-modal * {
box-sizing: border-box;
}
.bno-direct-catalog__status {
margin: 28px 0 0;
padding: 15px 17px;
border: 1px solid var(--bno-border);
border-radius: 14px;
background: var(--bno-white) !important;
color: var(--bno-navy) !important;
font-size: 15px;
line-height: 1.5;
}
.bno-direct-catalog__status.is-error {
border-color: rgba(143, 29, 29, 0.24);
background: var(--bno-error-bg) !important;
color: var(--bno-error) !important;
}
.bno-direct-catalog__grid {
display: grid !important;
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
gap: 24px !important;
align-items: stretch;
margin-top: 28px;
}
.bno-direct-card {
display: flex;
min-width: 0;
height: 100%;
overflow: hidden;
border: 1px solid var(--bno-border);
border-radius: 20px;
background: #ffffff !important;
box-shadow: 0 12px 34px rgba(12, 26, 56, 0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.bno-direct-card:hover {
transform: translateY(-4px);
box-shadow: 0 18px 42px rgba(12, 26, 56, 0.14);
}
.bno-direct-card__button,
.bno-direct-card__button:hover,
.bno-direct-card__button:focus,
.bno-direct-card__button:focus-visible,
.bno-direct-card__button:active {
display: flex !important;
flex: 1 1 auto;
flex-direction: column;
width: 100% !important;
min-width: 0 !important;
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden;
border: 0 !important;
border-radius: inherit !important;
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
color: inherit !important;
box-shadow: none !important;
text-align: left !important;
text-decoration: none !important;
cursor: pointer;
appearance: none !important;
-webkit-appearance: none !important;
}
.bno-direct-card__media {
position: relative;
flex: 0 0 auto;
width: 100%;
min-width: 0;
aspect-ratio: 4 / 3;
overflow: hidden;
background: var(--bno-navy);
}
.bno-direct-card__media img {
display: block;
width: 100% !important;
height: 100% !important;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
object-fit: cover;
object-position: center;
border: 0;
border-radius: 0;
transition: transform 0.35s ease;
}
.bno-direct-card:hover .bno-direct-card__media img {
transform: scale(1.035);
}
.bno-direct-card__count {
position: absolute;
right: 12px;
bottom: 12px;
z-index: 2;
padding: 7px 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 999px;
background: rgba(12, 26, 56, 0.92) !important;
color: #ffffff !important;
font-size: 12px;
font-weight: 800;
line-height: 1;
white-space: nowrap;
}
.bno-direct-card__body {
display: flex !important;
flex: 1 1 auto;
flex-direction: column;
width: 100%;
min-width: 0;
min-height: 238px;
padding: 20px;
overflow: hidden;
background: #ffffff !important;
color: var(--bno-navy) !important;
}
.bno-direct-card__body h3 {
display: -webkit-box;
width: 100%;
min-width: 0;
max-width: 100%;
margin: 0;
overflow: hidden;
color: var(--bno-navy) !important;
font-size: clamp(18px, 1.45vw, 22px);
font-weight: 800;
line-height: 1.2;
white-space: normal !important;
overflow-wrap: anywhere;
word-break: normal;
hyphens: auto;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.bno-direct-card__meta {
margin: 8px 0 0;
color: var(--bno-muted) !important;
font-size: 13px;
font-weight: 800;
line-height: 1.4;
}
.bno-direct-card__description {
display: -webkit-box;
width: 100%;
min-width: 0;
margin: 14px 0 0;
overflow: hidden;
color: var(--bno-text) !important;
font-size: 14px;
line-height: 1.6;
white-space: normal !important;
overflow-wrap: anywhere;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
}
.bno-direct-card__link {
display: inline-flex;
align-items: center;
align-self: flex-start;
margin-top: auto;
padding-top: 18px;
color: var(--bno-navy) !important;
font-size: 14px;
font-weight: 800;
}
.bno-direct-more-wrap {
display: flex;
justify-content: center;
margin-top: 34px;
}
.bno-direct-more-wrap[hidden],
.bno-direct-modal[hidden],
.bno-direct-message[hidden],
.bno-direct-price[hidden],
.bno-direct-continue[hidden] {
display: none !important;
}
.bno-direct-more,
.bno-direct-more:hover,
.bno-direct-more:focus,
.bno-direct-more:active,
.bno-direct-check-button,
.bno-direct-check-button:hover,
.bno-direct-check-button:focus,
.bno-direct-check-button:active,
.bno-direct-book-button,
.bno-direct-book-button:hover,
.bno-direct-book-button:focus,
.bno-direct-book-button:active {
display: inline-flex !important;
align-items: center;
justify-content: center;
min-height: 50px !important;
margin: 0 !important;
padding: 12px 20px !important;
border: 1px solid var(--bno-navy) !important;
border-radius: 11px !important;
background: var(--bno-navy) !important;
background-image: none !important;
color: #ffffff !important;
box-shadow: none !important;
font: inherit;
font-size: 14px !important;
font-weight: 800 !important;
line-height: 1.3;
text-align: center;
text-decoration: none !important;
cursor: pointer;
appearance: none !important;
-webkit-appearance: none !important;
}
.bno-direct-more:hover,
.bno-direct-check-button:hover,
.bno-direct-book-button:hover {
background: var(--bno-navy-hover) !important;
}
.bno-direct-check-button:disabled,
.bno-direct-book-button:disabled {
border-color: #778195 !important;
background: #778195 !important;
cursor: wait;
}
.bno-direct-modal {
position: fixed;
z-index: 999999;
inset: 0;
display: grid;
place-items: center;
width: 100%;
max-width: 100%;
padding: 24px;
overflow: hidden;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
.bno-direct-modal__backdrop,
.bno-direct-modal__backdrop:hover,
.bno-direct-modal__backdrop:focus,
.bno-direct-modal__backdrop:active {
position: absolute;
z-index: 0;
inset: 0;
width: 100%;
height: 100%;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
border-radius: 0 !important;
background: rgba(4, 12, 28, 0.82) !important;
box-shadow: none !important;
cursor: zoom-out;
backdrop-filter: blur(3px);
appearance: none !important;
-webkit-appearance: none !important;
}
.bno-direct-modal__dialog {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: minmax(0, 1.16fr) minmax(390px, 0.84fr);
width: min(1180px, calc(100vw - 48px));
max-width: 100%;
height: min(760px, calc(100dvh - 48px));
max-height: calc(100dvh - 48px);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 22px;
background: #ffffff;
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.4);
}
.bno-direct-modal__gallery {
position: relative;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
background: var(--bno-navy);
}
.bno-direct-modal__image {
display: block;
width: 100% !important;
height: 100% !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: none !important;
max-height: none !important;
margin: 0 !important;
padding: 0 !important;
object-fit: cover;
object-position: center;
border: 0;
border-radius: 0;
}
.bno-direct-modal__content {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
padding: 40px 34px 36px;
overflow-x: hidden;
overflow-y: auto;
background: #ffffff !important;
color: var(--bno-text) !important;
scrollbar-color: rgba(12, 26, 56, 0.32) transparent;
scrollbar-width: thin;
}
.bno-direct-modal__content * {
min-width: 0;
}
.bno-direct-eyebrow {
display: inline-block;
color: var(--bno-muted) !important;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.04em;
line-height: 1.4;
text-transform: uppercase;
}
.bno-direct-modal__content h3 {
width: 100%;
max-width: 100%;
margin: 10px 0 0;
color: var(--bno-navy) !important;
font-size: clamp(28px, 3vw, 40px);
font-weight: 800;
line-height: 1.08;
white-space: normal !important;
overflow-wrap: anywhere;
hyphens: auto;
}
.bno-direct-modal__capacity {
margin: 12px 0 0;
color: var(--bno-muted) !important;
font-size: 14px;
font-weight: 800;
}
.bno-direct-modal__description {
width: 100%;
max-height: 245px;
margin-top: 20px;
padding-right: 8px;
overflow-x: hidden;
overflow-y: auto;
color: var(--bno-text) !important;
font-size: 15px;
line-height: 1.72;
white-space: pre-line;
overflow-wrap: anywhere;
}
.bno-direct-modal__close,
.bno-direct-modal__close:hover,
.bno-direct-modal__close:focus,
.bno-direct-modal__close:active {
position: absolute;
z-index: 12;
top: 14px;
right: 14px;
display: grid !important;
width: 44px !important;
min-width: 44px !important;
height: 44px !important;
min-height: 44px !important;
margin: 0 !important;
padding: 0 !important;
place-items: center !important;
border: 1px solid rgba(255, 255, 255, 0.25) !important;
border-radius: 999px !important;
background: var(--bno-navy) !important;
color: #ffffff !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.24) !important;
font-family: Arial, sans-serif !important;
font-size: 28px !important;
font-weight: 400 !important;
line-height: 1 !important;
cursor: pointer;
}
.bno-direct-modal__arrow,
.bno-direct-modal__arrow:hover,
.bno-direct-modal__arrow:focus,
.bno-direct-modal__arrow:active {
position: absolute;
z-index: 8;
top: 50%;
display: grid !important;
width: 48px !important;
min-width: 48px !important;
height: 48px !important;
min-height: 48px !important;
margin: 0 !important;
padding: 0 !important;
place-items: center !important;
border: 1px solid rgba(255, 255, 255, 0.25) !important;
border-radius: 999px !important;
background: rgba(12, 26, 56, 0.92) !important;
color: #ffffff !important;
box-shadow: 0 7px 22px rgba(0, 0, 0, 0.24) !important;
font-family: Arial, sans-serif !important;
font-size: 35px !important;
font-weight: 400 !important;
line-height: 1 !important;
cursor: pointer;
transform: translateY(-50%);
}
.bno-direct-modal__arrow--prev { left: 18px; }
.bno-direct-modal__arrow--next { right: 18px; }
.bno-direct-modal__arrow[hidden] { display: none !important; }
.bno-direct-modal__counter {
position: absolute;
z-index: 8;
right: 18px;
bottom: 18px;
padding: 8px 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 999px;
background: rgba(12, 26, 56, 0.92) !important;
color: #ffffff !important;
font-size: 13px;
font-weight: 800;
line-height: 1;
white-space: nowrap;
}
.bno-direct-booking {
margin-top: 30px;
padding-top: 28px;
border-top: 1px solid var(--bno-border);
}
.bno-direct-booking__heading h4 {
width: 100%;
margin: 6px 0 0;
color: var(--bno-navy) !important;
font-size: 22px;
font-weight: 800;
line-height: 1.25;
white-space: normal !important;
overflow-wrap: anywhere;
}
.bno-direct-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
width: 100%;
margin-top: 20px;
}
.bno-direct-form label {
display: grid;
gap: 7px;
width: 100%;
min-width: 0;
color: var(--bno-navy) !important;
font-size: 13px;
font-weight: 800;
}
.bno-direct-form input {
display: block;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
min-height: 48px;
margin: 0 !important;
padding: 10px 12px !important;
border: 1px solid rgba(12, 26, 56, 0.22) !important;
border-radius: 10px !important;
background: #ffffff !important;
color: var(--bno-navy) !important;
box-shadow: none !important;
font: inherit;
font-size: 16px !important;
}
.bno-direct-form input:focus {
border-color: var(--bno-navy) !important;
outline: 2px solid rgba(12, 26, 56, 0.14) !important;
outline-offset: 1px;
}
.bno-direct-check-button {
grid-column: 1 / -1;
width: 100% !important;
}
.bno-direct-message {
width: 100%;
margin-top: 15px;
padding: 14px 15px;
border-radius: 11px;
background: var(--bno-soft) !important;
color: var(--bno-navy) !important;
font-size: 14px;
line-height: 1.5;
white-space: normal !important;
overflow-wrap: anywhere;
}
.bno-direct-message.is-success {
background: var(--bno-success-bg) !important;
color: var(--bno-success) !important;
}
.bno-direct-message.is-error {
background: var(--bno-error-bg) !important;
color: var(--bno-error) !important;
}
.bno-direct-price {
width: 100%;
margin-top: 18px;
overflow: hidden;
border: 1px solid var(--bno-border);
border-radius: 14px;
background: #ffffff !important;
}
.bno-direct-price__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
width: 100%;
padding: 13px 15px;
border-top: 1px solid rgba(12, 26, 56, 0.1);
background: #ffffff !important;
color: var(--bno-navy) !important;
}
.bno-direct-price__row:first-child { border-top: 0; }
.bno-direct-price__row span,
.bno-direct-price__row strong {
min-width: 0;
max-width: 100%;
color: inherit !important;
white-space: normal !important;
overflow-wrap: anywhere;
}
.bno-direct-price__row strong {
flex: 0 0 auto;
text-align: right;
}
.bno-direct-price__row--total {
background: var(--bno-navy) !important;
color: #ffffff !important;
}
.bno-direct-continue {
width: 100%;
margin-top: 18px;
}
.bno-direct-book-button {
width: 100% !important;
min-height: 54px !important;
font-size: 15px !important;
}
.bno-direct-continue p {
width: 100%;
margin: 10px 0 0;
color: var(--bno-muted) !important;
font-size: 12px;
line-height: 1.5;
text-align: center;
white-space: normal !important;
overflow-wrap: anywhere;
}
@media (max-width: 980px) {
.bno-direct-catalog__grid {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
}
@media (max-width: 900px) {
.bno-direct-modal {
display: block !important;
width: 100% !important;
max-width: 100% !important;
padding: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
background: #ffffff !important;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.bno-direct-modal__backdrop {
position: fixed !important;
}
.bno-direct-modal__dialog {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
height: auto !important;
min-height: 100vh !important;
min-height: 100dvh !important;
max-height: none !important;
margin: 0 !important;
overflow: visible !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
}
.bno-direct-modal__gallery {
width: 100% !important;
max-width: 100% !important;
height: clamp(320px, 60vw, 520px) !important;
min-height: 320px !important;
}
.bno-direct-modal__content {
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
height: auto !important;
max-height: none !important;
margin: 0 !important;
padding: 34px clamp(20px, 5vw, 44px) 52px !important;
overflow: visible !important;
}
.bno-direct-modal__description {
max-height: none !important;
padding-right: 0 !important;
overflow: visible !important;
}
.bno-direct-modal__close {
position: fixed !important;
top: max(12px, env(safe-area-inset-top)) !important;
right: max(12px, env(safe-area-inset-right)) !important;
}
.bno-direct-price__row {
display: grid !important;
grid-template-columns: minmax(0, 1fr) !important;
gap: 7px !important;
padding: 17px 18px !important;
}
.bno-direct-price__row strong {
text-align: left !important;
font-size: 20px;
}
}
@media (max-width: 620px) {
.bno-direct-catalog__grid {
grid-template-columns: 1fr !important;
}
.bno-direct-card__body {
min-height: 220px;
}
.bno-direct-modal__gallery {
height: 66vw !important;
min-height: 270px !important;
max-height: 370px !important;
}
.bno-direct-modal__content {
padding:
28px
max(18px, env(safe-area-inset-right))
calc(48px + env(safe-area-inset-bottom))
max(18px, env(safe-area-inset-left)) !important;
}
.bno-direct-modal__content h3 {
font-size: 28px !important;
line-height: 1.12 !important;
}
.bno-direct-booking__heading h4 {
font-size: 24px !important;
line-height: 1.18 !important;
}
.bno-direct-form {
grid-template-columns: minmax(0, 1fr) !important;
}
.bno-direct-check-button {
grid-column: 1 !important;
}
.bno-direct-modal__arrow {
width: 44px !important;
min-width: 44px !important;
height: 44px !important;
min-height: 44px !important;
}
.bno-direct-modal__arrow--prev { left: 12px; }
.bno-direct-modal__arrow--next { right: 12px; }
.bno-direct-modal__counter { right: 12px; bottom: 12px; }
}
(function () {
"use strict";
const ROOT_SELECTOR = "[data-bno-direct-catalog]";
function cleanBase(value) {
return String(value || "").trim().replace(/\/+$/, "");
}
function apiUrl(base, path) {
const clean = cleanBase(base);
const route = String(path || "");
if (clean.endsWith("/api") && route.startsWith("/api/")) {
return clean + route.slice(4);
}
return clean + route;
}
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function stripHtml(value) {
const node = document.createElement("div");
node.innerHTML = String(value || "");
return String(node.textContent || node.innerText || "")
.replace(/\s+/g, " ")
.trim();
}
function firstValue(object, keys) {
for (const key of keys) {
if (object && object[key] != null && object[key] !== "") {
return object[key];
}
}
return "";
}
function parseIdSet(value) {
return new Set(
String(value || "")
.split(",")
.map(function (item) {
return item.trim();
})
.filter(Boolean)
);
}
function languageCode() {
return "nl";
}
function localYmd(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return year + "-" + month + "-" + day;
}
function addDays(date, days) {
const next = new Date(date.getTime());
next.setDate(next.getDate() + Number(days || 0));
return next;
}
function nightsBetween(from, to) {
const start = new Date(from + "T12:00:00");
const end = new Date(to + "T12:00:00");
return Math.round((end.getTime() - start.getTime()) / 86400000);
}
function formatDate(value) {
try {
return new Intl.DateTimeFormat("nl-NL", {
day: "numeric",
month: "short",
year: "numeric"
}).format(new Date(value + "T12:00:00"));
} catch (_error) {
return value;
}
}
function formatMoney(amount, currency) {
try {
return new Intl.NumberFormat("nl-NL", {
style: "currency",
currency: String(currency || "EUR").toUpperCase(),
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(Number(amount || 0));
} catch (_error) {
return Number(amount || 0).toFixed(2) + " " + String(currency || "EUR");
}
}
function uniqueImages(row) {
const images = [];
function add(value) {
if (!value) return;
if (typeof value === "string") {
const url = value.trim();
if (/^https?:\/\//i.test(url)) images.push(url);
return;
}
if (typeof value === "object") {
add(
value.url ||
value.Url ||
value.imageUrl ||
value.ImageUrl ||
value.source ||
value.Source ||
value.original ||
value.Original
);
}
}
[
row.images,
row.Images,
row.gallery,
row.Gallery,
row.imageUrls,
row.ImageUrls,
row.photos,
row.Photos
].forEach(function (items) {
if (Array.isArray(items)) items.forEach(add);
});
[
row.image,
row.Image,
row.coverImageUrl,
row.CoverImageUrl,
row.imageUrl,
row.ImageUrl,
row.thumbnailUrl,
row.ThumbnailUrl
].forEach(add);
return Array.from(new Set(images));
}
function extractRows(payload) {
const candidates = [
payload && payload.data && payload.data.resourceCategories,
payload && payload.data && payload.data.categories,
payload && payload.data && payload.data.items,
payload && payload.data,
payload && payload.resourceCategories,
payload && payload.categories,
payload && payload.items
];
for (const candidate of candidates) {
if (Array.isArray(candidate)) return candidate;
}
return [];
}
function normaliseUnit(row) {
const categoryId = String(
firstValue(row, [
"resourceCategoryId",
"ResourceCategoryId",
"categoryId",
"CategoryId",
"id",
"Id"
])
).trim();
return {
categoryId: categoryId,
name: String(
firstValue(row, [
"localizedName",
"LocalizedName",
"categoryName",
"CategoryName",
"name",
"Name",
"title",
"Title"
]) || "Vakantiehuis"
).trim(),
description: stripHtml(
firstValue(row, [
"localizedDescription",
"LocalizedDescription",
"description",
"Description",
"longDescription",
"LongDescription",
"shortDescription",
"ShortDescription"
])
),
capacity: Number(
firstValue(row, [
"capacity",
"Capacity",
"maxOccupancy",
"MaxOccupancy",
"maximumOccupancy",
"MaximumOccupancy"
])
) || 0,
images: uniqueImages(row),
published: firstValue(row, [
"isPublished",
"IsPublished",
"is_published",
"publishStatus",
"PublishStatus",
"publish_status",
"status",
"Status"
])
};
}
function isPublished(unit, hasPublicationFields) {
if (!hasPublicationFields) return true;
if (typeof unit.published === "boolean") return unit.published;
return [
"published",
"approved",
"reviewed",
"active",
"true",
"1"
].includes(String(unit.published || "").trim().toLowerCase());
}
function buildModal(destinationLabel, locationLabel) {
const modal = document.createElement("div");
modal.className = "bno-direct-modal";
modal.hidden = true;
modal.setAttribute("role", "dialog");
modal.setAttribute("aria-modal", "true");
modal.setAttribute("aria-label", "Vakantiehuis in " + destinationLabel);
modal.innerHTML = `
×
‹
›
${escapeHtml(locationLabel || destinationLabel)}
Controleer je verblijf
Controleer of het vakantiehuis beschikbaar is
Inchecken
Uitchecken
Gasten
Beschikbaarheid controleren
Totale prijs voor het verblijf
Nu te betalen aan BNO Travel
Resterend bedrag aan de accommodatie
Doorgaan naar boeken
Gastgegevens, voorwaarden en betaling worden afgerond in de beveiligde boekingsomgeving van BNO Travel.
`;
document.body.appendChild(modal);
return modal;
}
async function fetchJson(url, options) {
const controller = new AbortController();
const timeout = window.setTimeout(function () {
controller.abort();
}, 60000);
try {
const response = await fetch(
url,
Object.assign(
{
cache: "no-store",
signal: controller.signal,
headers: {
Accept: "application/json"
}
},
options || {}
)
);
const payload = await response.json().catch(function () {
return null;
});
if (!response.ok || !payload || payload.ok === false) {
const error = new Error(
payload && (payload.detail || payload.message || payload.error)
? payload.detail || payload.message || payload.error
: "De aanvraag kon niet worden voltooid."
);
error.code = payload && payload.error ? String(payload.error) : "";
throw error;
}
return payload;
} finally {
window.clearTimeout(timeout);
}
}
function init(root) {
if (!root || root.dataset.bnoInitialised === "1") return;
root.dataset.bnoInitialised = "1";
const apiBase = cleanBase(root.dataset.apiBase);
const bookingBase = String(
root.dataset.bookingBase ||
"https://booking.bno-travel.com/booking/complete"
).trim();
const catalogCredsKey = String(root.dataset.catalogCredsKey || "").trim();
const bookingPartnerKey = String(
root.dataset.bookingPartnerKey || catalogCredsKey
).trim();
const serviceId = String(root.dataset.serviceId || "").trim();
const areaKey = String(root.dataset.areaKey || "").trim();
const destinationLabel = String(
root.dataset.destinationLabel || "destinasjonen"
).trim();
const locationLabel = String(
root.dataset.locationLabel || destinationLabel
).trim();
const bookingOpenUntil = String(
root.dataset.bookingOpenUntil || "2027-05-01"
).trim();
const minimumStayNights = Math.max(
1,
Number(root.dataset.minimumStayNights || 1) || 1
);
const initialCount = Math.max(
1,
Number(root.dataset.initialCount || 12) || 12
);
const allowedIds = parseIdSet(root.dataset.allowedUnitIds);
const excludedIds = parseIdSet(root.dataset.excludedUnitIds);
const status = root.querySelector("[data-bno-status]");
const grid = root.querySelector("[data-bno-grid]");
const moreWrap = root.querySelector("[data-bno-more-wrap]");
const moreButton = root.querySelector("[data-bno-more]");
if (!apiBase || !catalogCredsKey || !serviceId || !areaKey) {
if (status) {
status.textContent = "Bulk 3 mist de vereiste configuratie.";
status.classList.add("is-error");
}
return;
}
if (!status || !grid || !moreWrap || !moreButton) return;
const modal = buildModal(destinationLabel, locationLabel);
const modalImage = modal.querySelector("[data-bno-modal-image]");
const modalTitle = modal.querySelector("[data-bno-modal-title]");
const modalCapacity = modal.querySelector("[data-bno-modal-capacity]");
const modalDescription = modal.querySelector("[data-bno-modal-description]");
const modalCounter = modal.querySelector("[data-bno-counter]");
const prevButton = modal.querySelector("[data-bno-prev]");
const nextButton = modal.querySelector("[data-bno-next]");
const closeButtons = modal.querySelectorAll("[data-bno-close]");
const availabilityForm = modal.querySelector("[data-bno-availability-form]");
const checkinInput = modal.querySelector("[data-bno-checkin]");
const checkoutInput = modal.querySelector("[data-bno-checkout]");
const guestsInput = modal.querySelector("[data-bno-guests]");
const checkButton = modal.querySelector("[data-bno-check-button]");
const messageNode = modal.querySelector("[data-bno-message]");
const priceBox = modal.querySelector("[data-bno-price]");
const totalNode = modal.querySelector("[data-bno-total]");
const payNowNode = modal.querySelector("[data-bno-pay-now]");
const payLaterNode = modal.querySelector("[data-bno-pay-later]");
const continueWrap = modal.querySelector("[data-bno-continue-wrap]");
const continueButton = modal.querySelector("[data-bno-continue]");
let units = [];
let visibleCount = initialCount;
let currentUnit = null;
let currentImageIndex = 0;
let currentQuote = null;
let previousBodyOverflow = "";
function setStatus(message, isError) {
status.textContent = message || "";
status.hidden = !message;
status.classList.toggle("is-error", Boolean(isError));
}
function setMessage(message, type) {
messageNode.textContent = message || "";
messageNode.hidden = !message;
messageNode.classList.remove("is-error", "is-success");
if (type) messageNode.classList.add("is-" + type);
}
function resetQuote() {
currentQuote = null;
priceBox.hidden = true;
continueWrap.hidden = true;
setMessage("", "");
}
function cardHtml(unit, index) {
const image = unit.images[0];
const capacity = unit.capacity > 0
? "Tot " + unit.capacity + " gasten"
: "Bekijk details en foto's";
const description = unit.description ||
"Bekijk foto's, capaciteit en beschikbaarheid van dit vakantiehuis.";
return `
${escapeHtml(unit.name)}
${escapeHtml(capacity)}
${escapeHtml(description)}
Bekijk vakantiehuis →
`;
}
function renderUnits() {
const visible = units.slice(0, visibleCount);
grid.innerHTML = visible.map(cardHtml).join("");
grid.setAttribute("aria-busy", "false");
grid.querySelectorAll("[data-bno-unit-index]").forEach(function (button) {
button.addEventListener("click", function () {
openModal(units[Number(button.dataset.bnoUnitIndex)]);
});
});
moreWrap.hidden = visibleCount >= units.length;
setStatus(
"Toont " + visible.length + " van " + units.length +
" ferieboliger i " + destinationLabel + ".",
false
);
}
function updateModalImage() {
if (!currentUnit || !currentUnit.images.length) return;
currentImageIndex =
(currentImageIndex + currentUnit.images.length) % currentUnit.images.length;
modalImage.src = currentUnit.images[currentImageIndex];
modalImage.alt = currentUnit.name + " – afbeelding " + (currentImageIndex + 1);
modalCounter.textContent =
currentImageIndex + 1 + " / " + currentUnit.images.length;
prevButton.hidden = currentUnit.images.length < 2;
nextButton.hidden = currentUnit.images.length 0
? "Tot " + unit.capacity + " gasten"
: "";
modalDescription.textContent = unit.description ||
"Bekijk beschikbaarheid en prijs van dit vakantiehuis.";
const today = new Date();
const checkin = addDays(today, 1);
const checkout = addDays(checkin, Math.max(7, minimumStayNights));
checkinInput.min = localYmd(today);
checkoutInput.min = localYmd(addDays(checkin, minimumStayNights));
checkinInput.value = localYmd(checkin);
checkoutInput.value = localYmd(checkout);
if (bookingOpenUntil) {
checkinInput.max = bookingOpenUntil;
checkoutInput.max = bookingOpenUntil;
}
guestsInput.max = String(unit.capacity > 0 ? unit.capacity : 20);
guestsInput.value = String(Math.min(2, unit.capacity > 0 ? unit.capacity : 2));
resetQuote();
updateModalImage();
previousBodyOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
document.body.classList.add("bno-direct-modal-open");
modal.hidden = false;
modal.scrollTop = 0;
const close = modal.querySelector(".bno-direct-modal__close");
if (close) close.focus();
}
function closeModal() {
modal.hidden = true;
document.body.style.overflow = previousBodyOverflow;
document.body.classList.remove("bno-direct-modal-open");
currentUnit = null;
currentQuote = null;
}
async function loadCatalog() {
try {
const query = new URLSearchParams({
serviceId: serviceId,
credsKey: catalogCredsKey,
lang: languageCode()
});
const payload = await fetchJson(
apiUrl(apiBase, "/api/mews/catalog?") + query.toString()
);
const rows = extractRows(payload);
const publicationKeys = [
"isPublished",
"IsPublished",
"is_published",
"publishStatus",
"PublishStatus",
"publish_status",
"status",
"Status"
];
const hasPublicationFields = rows.some(function (row) {
return publicationKeys.some(function (key) {
return Object.prototype.hasOwnProperty.call(row || {}, key);
});
});
const seen = new Set();
units = rows
.map(normaliseUnit)
.filter(function (unit) {
if (!unit.categoryId || seen.has(unit.categoryId)) return false;
seen.add(unit.categoryId);
if (allowedIds.size && !allowedIds.has(unit.categoryId)) return false;
if (excludedIds.has(unit.categoryId)) return false;
if (!isPublished(unit, hasPublicationFields)) return false;
return unit.images.length > 0;
})
.sort(function (a, b) {
return a.name.localeCompare(b.name, "nb", { sensitivity: "base" });
});
if (!units.length) {
grid.innerHTML = "";
grid.setAttribute("aria-busy", "false");
moreWrap.hidden = true;
setStatus(
"Er konden op dit moment geen gepubliceerde vakantiehuizen worden geladen.",
true
);
return;
}
renderUnits();
} catch (error) {
console.error("BNO destination catalog failed:", error);
grid.innerHTML = "";
grid.setAttribute("aria-busy", "false");
moreWrap.hidden = true;
setStatus(
"De vakantiehuizen konden niet worden geladen. Vernieuw de pagina en probeer het opnieuw.",
true
);
}
}
availabilityForm.addEventListener("submit", async function (event) {
event.preventDefault();
if (!currentUnit) return;
const from = String(checkinInput.value || "").trim();
const to = String(checkoutInput.value || "").trim();
const adults = Number(guestsInput.value || 0);
resetQuote();
if (!from || !to || to <= from) {
setMessage("Kies geldige data. Uitchecken moet na inchecken zijn.", "error");
return;
}
const nights = nightsBetween(from, to);
if (nights < minimumStayNights) {
setMessage(
"Het minimumverblijf is " + minimumStayNights +
(minimumStayNights === 1 ? " nacht." : " nachten."),
"error"
);
return;
}
if (!Number.isInteger(adults) || adults 0 && adults > currentUnit.capacity) {
setMessage(
"Dit vakantiehuis biedt plaats aan maximaal " +
currentUnit.capacity + " gasten.",
"error"
);
return;
}
checkButton.disabled = true;
checkButton.textContent = "Controleren …";
setMessage("Beschikbaarheid en prijs controleren …", "");
try {
const payload = await fetchJson(
apiUrl(apiBase, "/api/bno-direct/quote"),
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
partnerKey: bookingPartnerKey,
areaKey: areaKey,
resourceCategoryId: currentUnit.categoryId,
from: from,
to: to,
adults: adults
})
}
);
const quote = payload.data || payload;
const pricing = quote.pricing || {};
const currency = String(quote.currency || "EUR").toUpperCase();
const total = Number(pricing.guestTotalGross);
const payNow = Number(pricing.bnoPrepaidGross);
const payLater = Number(pricing.remainingGross);
if (!Number.isFinite(total) || total <= 0) {
throw new Error("De totale prijs kon niet worden opgehaald.");
}
currentQuote = {
from: from,
to: to,
adults: adults,
currency: currency,
total: total,
payNow: Number.isFinite(payNow) ? payNow : 0,
payLater: Number.isFinite(payLater)
? payLater
: Math.max(0, total - (Number.isFinite(payNow) ? payNow : 0))
};
totalNode.textContent = formatMoney(currentQuote.total, currency);
payNowNode.textContent = formatMoney(currentQuote.payNow, currency);
payLaterNode.textContent = formatMoney(currentQuote.payLater, currency);
priceBox.hidden = false;
continueWrap.hidden = false;
setMessage(
"Het vakantiehuis is beschikbaar van " + formatDate(from) +
" tot " + formatDate(to) + ".",
"success"
);
} catch (error) {
console.error("BNO destination quote failed:", error);
const code = String(error && error.code || "");
let message = error && error.message
? error.message
: "De beschikbaarheid kon niet worden gecontroleerd. Probeer het opnieuw.";
if (code === "booking_not_open") {
message = "Boeken is voor deze bestemming nog niet beschikbaar.";
} else if (code === "not_available") {
message = "Het vakantiehuis is niet beschikbaar in de gekozen periode.";
}
setMessage(message, "error");
} finally {
checkButton.disabled = false;
checkButton.textContent = "Beschikbaarheid controleren";
}
});
function invalidateQuote() {
resetQuote();
}
checkinInput.addEventListener("change", function () {
if (checkinInput.value) {
const minCheckout = localYmd(
addDays(new Date(checkinInput.value + "T12:00:00"), minimumStayNights)
);
checkoutInput.min = minCheckout;
if (!checkoutInput.value || checkoutInput.value < minCheckout) {
checkoutInput.value = minCheckout;
}
}
invalidateQuote();
});
checkoutInput.addEventListener("change", invalidateQuote);
guestsInput.addEventListener("change", invalidateQuote);
continueButton.addEventListener("click", function () {
if (!currentUnit || !currentQuote) return;
const url = new URL(bookingBase, window.location.origin);
url.searchParams.set("partnerKey", bookingPartnerKey);
url.searchParams.set("areaKey", areaKey);
url.searchParams.set("resourceCategoryId", currentUnit.categoryId);
url.searchParams.set("from", currentQuote.from);
url.searchParams.set("to", currentQuote.to);
url.searchParams.set("adults", String(currentQuote.adults));
url.searchParams.set("propertyName", currentUnit.name);
url.searchParams.set("lang", languageCode());
if (currentUnit.images[0]) {
url.searchParams.set("imageUrl", currentUnit.images[0]);
}
window.location.assign(url.toString());
});
prevButton.addEventListener("click", function () {
currentImageIndex -= 1;
updateModalImage();
});
nextButton.addEventListener("click", function () {
currentImageIndex += 1;
updateModalImage();
});
closeButtons.forEach(function (button) {
button.addEventListener("click", closeModal);
});
modal.addEventListener("keydown", function (event) {
if (event.key === "Escape") closeModal();
if (event.key === "ArrowLeft") {
currentImageIndex -= 1;
updateModalImage();
}
if (event.key === "ArrowRight") {
currentImageIndex += 1;
updateModalImage();
}
});
moreButton.addEventListener("click", function () {
visibleCount = Math.min(visibleCount + initialCount, units.length);
renderUnits();
});
loadCatalog();
}
function mountAll(scope) {
const context = scope && scope.querySelectorAll ? scope : document;
context.querySelectorAll(ROOT_SELECTOR).forEach(init);
}
window.BNODirectDestinationCatalog = {
mountAll: mountAll
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
mountAll(document);
}, { once: true });
} else {
mountAll(document);
}
window.addEventListener("load", function () {
mountAll(document);
}, { once: true });
})();