{"id":3100,"date":"2023-09-28T13:41:51","date_gmt":"2023-09-28T11:41:51","guid":{"rendered":"https:\/\/www.design-villa-ottilienruh.de\/book-now\/"},"modified":"2026-11-06T08:56:29","modified_gmt":"2026-11-06T07:56:29","slug":"book-now","status":"publish","type":"page","link":"https:\/\/www.design-villa-ottilienruh.de\/en\/book-now\/","title":{"rendered":"Book Now"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"3100\" class=\"elementor elementor-3100 elementor-1339\" data-elementor-post-type=\"page\">\n\t\t\t\t<section class=\"elementor-element elementor-element-9ef613f e-con-full e-flex e-con e-parent\" data-id=\"9ef613f\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;,&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t<div class=\"elementor-element elementor-element-3a79b7e e-con-full e-flex e-con e-child\" data-id=\"3a79b7e\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5616fde elementor-widget elementor-widget-heading\" data-id=\"5616fde\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Booking<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-a09d892 e-flex e-con-boxed e-con e-parent\" data-id=\"a09d892\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-dc6347e elementor-widget elementor-widget-html\" data-id=\"dc6347e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!--\nILSENBURG BUCHUNGSALTERNATIVEN V10\nVilla Ottilienruh | Chalets zum Ilsetal | Landh\u00e4user Ilsenburg\n\nNEU:\n- Weiterleitung zur jeweiligen eigenen Buchungsseite\n- \u00dcbergabe von Anreise, Abreise, Erwachsenen, Kindern\n- Automatisches Bef\u00fcllen der Ziel-Buchungsmaske\n- Fallback zum getesteten easybooking Direktlink\n\nWEITERHIN:\n- Liveabfrage der Alternativen\n- Beschreibung und Personenanzahl\n- Bilder aus der easybooking Galerie\n- Voraussichtlicher Gesamtpreis\n- Scrollen nach erfolgloser Suche\n-->\n\n<div id=\"ils-live-check\" hidden=\"\">\n  <div class=\"ils-check-box\">\n    <h2>Discover our other accommodations<\/h2>\n    <p id=\"ils-check-intro\" class=\"ils-check-intro\"><\/p>\n    <div id=\"ils-check-dates\"><\/div>\n    <div id=\"ils-check-results\"><\/div>\n    <p class=\"ils-check-note\">\n      The offers and prices shown are taken from the\n      easybooking search. Availability and the total price\n      in the respective booking system are\n      binding.\n    <\/p> \n  <\/div>\n<\/div>\n\n<div id=\"ils-transfer-box\" hidden=\"\">\n  <strong>Your travel dates have been applied<\/strong>\n  <p id=\"ils-transfer-text\"><\/p>\n  <p id=\"ils-transfer-status\">\n    The booking mask is being prepared ...\n  <\/p>\n  <a id=\"ils-transfer-fallback\" href=\"#\" target=\"_blank\" rel=\"noopener noreferrer\">\n    Open booking search directly in easybooking\n  <\/a> \n<\/div>\n\n<style>\n#ils-live-check[hidden],\n#ils-transfer-box[hidden],\n#ils-live-check [hidden] {\n  display: none !important;\n}\n\n#ils-live-check {\n  width: 100%;\n  max-width: 1100px;\n  margin: 25px auto;\n  color: #252525;\n  font-family: inherit;\n  scroll-margin-top: 105px;\n}\n\n#ils-live-check *,\n#ils-transfer-box * {\n  box-sizing: border-box;\n}\n\n#ils-live-check .ils-check-box {\n  padding: 28px;\n  background: #fff;\n  border: 1px solid #e2e6e3;\n  border-radius: 14px;\n}\n\n#ils-live-check h2 {\n  font-size: 25px;\n  line-height: 1.35;\n  margin: 0 0 12px;\n  color: #222;\n}\n\n#ils-live-check .ils-check-intro {\n  font-size: 15px;\n  line-height: 1.7;\n  color: #555;\n  margin: 0 0 20px;\n}\n\n#ils-live-check .ils-check-dates {\n  background: #edf3ef;\n  border-radius: 9px;\n  padding: 15px 18px;\n  margin-bottom: 20px;\n  color: #283b35;\n  font-size: 14px;\n  line-height: 1.7;\n}\n\n#ils-live-check .ils-check-card {\n  border: 1px solid #e4e8e5;\n  background: #fafbfa;\n  border-radius: 12px;\n  padding: 20px;\n  margin-bottom: 15px;\n}\n\n#ils-live-check .ils-check-card-inner {\n  display: flex;\n  align-items: flex-start;\n  gap: 22px;\n}\n\n#ils-live-check .ils-check-image-wrap {\n  width: 225px;\n  height: 190px;\n  flex-shrink: 0;\n  background: #eef1ee;\n  overflow: hidden;\n  border-radius: 9px;\n}\n\n#ils-live-check .ils-check-image-wrap[hidden] {\n  display: none !important;\n}\n\n#ils-live-check .ils-check-image {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  display: block;\n}\n\n#ils-live-check .ils-check-info {\n  flex: 1;\n  min-width: 0;\n}\n\n#ils-live-check .ils-check-card h3 {\n  font-size: 21px;\n  line-height: 1.35;\n  color: #222;\n  margin: 0 0 9px;\n}\n\n#ils-live-check .ils-check-badge {\n  display: inline-block;\n  padding: 6px 11px;\n  border-radius: 20px;\n  background: #eee;\n  color: #555;\n  font-size: 12px;\n  font-weight: 600;\n}\n\n#ils-live-check .ils-check-badge.available {\n  background: #e3f3e7;\n  color: #22643a;\n}\n\n#ils-live-check .ils-check-badge.unavailable {\n  background: #f7e9e9;\n  color: #963838;\n}\n\n#ils-live-check .ils-check-category {\n  margin: 13px 0 8px;\n  font-size: 16px;\n  font-weight: 600;\n}\n\n#ils-live-check .ils-check-persons {\n  color: #555;\n  font-size: 13px;\n  margin-bottom: 8px;\n}\n\n#ils-live-check .ils-check-price {\n  margin-top: 14px;\n  font-size: 25px;\n  line-height: 1.25;\n  font-weight: 700;\n  color: #283b35;\n}\n\n#ils-live-check .ils-check-price-note {\n  display: block;\n  color: #777;\n  font-size: 11px;\n  margin-top: 3px;\n}\n\n#ils-live-check .ils-check-description {\n  font-size: 13px;\n  line-height: 1.7;\n  color: #555;\n  margin-top: 12px;\n}\n\n#ils-live-check .ils-check-more {\n  display: inline-block;\n  margin-top: 7px;\n  padding: 0;\n  color: #283b35;\n  background: none;\n  border: 0;\n  text-decoration: underline;\n  font: inherit;\n  font-size: 13px;\n  font-weight: 600;\n  cursor: pointer;\n}\n\n#ils-live-check .ils-check-details {\n  margin-top: 12px;\n  color: #666;\n  font-size: 13px;\n  line-height: 1.6;\n}\n\n#ils-live-check .ils-check-button {\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  padding: 13px 20px;\n  margin-top: 17px;\n  border-radius: 7px;\n  background: #283b35;\n  color: #fff !important;\n  font-size: 14px;\n  font-weight: 600;\n  text-decoration: none !important;\n}\n\n#ils-live-check .ils-check-button:hover {\n  background: #425b51;\n}\n\n#ils-live-check .ils-check-note {\n  font-size: 12px;\n  line-height: 1.6;\n  color: #777;\n  margin: 16px 0 0;\n}\n\n\/* Hinweis auf der Zielwebseite *\/\n\n#ils-transfer-box {\n  max-width: 1100px;\n  width: 100%;\n  margin: 15px auto;\n  padding: 17px 20px;\n  background: #edf3ef;\n  border: 1px solid #cfdfd4;\n  border-radius: 10px;\n  color: #283b35;\n  font-family: inherit;\n  font-size: 14px;\n  line-height: 1.6;\n}\n\n#ils-transfer-box p {\n  margin: 7px 0;\n}\n\n#ils-transfer-box a {\n  color: #283b35;\n  text-decoration: underline;\n  font-weight: 600;\n}\n\n@media(max-width:700px) {\n  #ils-live-check {\n    scroll-margin-top: 75px;\n  }\n\n  #ils-live-check .ils-check-box {\n    padding: 18px;\n  }\n\n  #ils-live-check h2 {\n    font-size: 21px;\n  }\n\n  #ils-live-check .ils-check-card {\n    padding: 16px;\n  }\n\n  #ils-live-check .ils-check-card-inner {\n    display: block;\n  }\n\n  #ils-live-check .ils-check-image-wrap {\n    width: 100%;\n    height: 210px;\n    margin-bottom: 16px;\n  }\n\n  #ils-live-check .ils-check-button {\n    width: 100%;\n    text-align: center;\n  }\n}\n<\/style>\n\n<script>\n(function () {\n  \"use strict\";\n\n  \/* =====================================\n     EINSTELLUNGEN\n  ===================================== *\/\n\n  const AUTO_SCROLL = true;\n  const SCROLL_DELAY = 900;\n  const CHECK_DELAY = 350;\n\n  const SHOW_IMAGES = true;\n  const SHOW_PRICES = true;\n  const DESCRIPTION_LENGTH = 250;\n\n  const DEBUG = false;\n\n  const API =\n    \"https:\/\/www.easy-booking.at\/easybooking\/index.php\/api\/searchCatAvailability\/\";\n\n  const IMAGE_HOST =\n    \"https:\/\/images.cdn-easybooking.cloud\";\n\n  \/* =====================================\n     UNTERK\u00dcNFTE\n  ===================================== *\/\n\n  const properties = [\n    {\n      id: \"villa\",\n      name: \"Villa Ottilienruh\",\n      domain: \"design-villa-ottilienruh.de\",\n      customerId: 10632,\n      serialNo: \"6154-7728-1794\",\n      url: \"https:\/\/www.design-villa-ottilienruh.de\/buchen\/\"\n    },\n    {\n      id: \"chalets\",\n      name: \"Chalets zum Ilsetal\",\n      domain: \"ferienhaus-ilsenburg-harz.de\",\n      customerId: 10633,\n      serialNo: \"8771-3107-9221\",\n      url: \"https:\/\/ferienhaus-ilsenburg-harz.de\/buchen\/\"\n    },\n    {\n      id: \"landhaeuser\",\n      name: \"Landh\u00e4user Ilsenburg\",\n      domain: \"landhaeuser-ilsenburg-harz.de\",\n      customerId: 11170,\n      serialNo: \"6788-8518-2224\",\n      url: \"https:\/\/www.landhaeuser-ilsenburg-harz.de\/buchen\/\"\n    }\n  ];\n\n  const root = document.getElementById(\n    \"ils-live-check\"\n  );\n\n  if (!root) return;\n\n  const hostname = location.hostname\n    .toLowerCase()\n    .replace(\/^www\\.\/, \"\");\n\n  const current = properties.find(\n    p => p.domain === hostname\n  );\n\n  if (!current) return;\n\n  const alternatives = properties.filter(\n    p => p.id !== current.id\n  );\n\n  function log() {\n    if (DEBUG) {\n      console.log(\"[Ilsenburg V10]\", ...arguments);\n    }\n  }\n\n  function make(tag, className, text) {\n    const el = document.createElement(tag);\n\n    if (className) el.className = className;\n    if (text !== undefined) el.textContent = text;\n\n    return el;\n  }\n\n  \/* =====================================\n     DATUM UND REISEDATEN\n  ===================================== *\/\n\n  function toISO(value) {\n    const v = String(value || \"\").trim();\n\n    let match = v.match(\n      \/^(\\d{2})\\.(\\d{2})\\.(\\d{4})$\/\n    );\n\n    if (match) {\n      return validISO(\n        match[3] + \"-\" + match[2] + \"-\" + match[1]\n      );\n    }\n\n    if (\/^\\d{4}-\\d{2}-\\d{2}$\/.test(v)) {\n      return validISO(v);\n    }\n\n    return \"\";\n  }\n\n  function validISO(iso) {\n    const d = new Date(iso + \"T12:00:00\");\n\n    if (isNaN(d.getTime())) return \"\";\n\n    const formatted =\n      d.getFullYear() + \"-\" +\n      String(d.getMonth() + 1).padStart(2, \"0\") +\n      \"-\" +\n      String(d.getDate()).padStart(2, \"0\");\n\n    return formatted === iso ? iso : \"\";\n  }\n\n  function toGerman(iso) {\n    if (!validISO(iso)) return \"\";\n\n    const a = iso.split(\"-\");\n\n    return a[2] + \".\" + a[1] + \".\" + a[0];\n  }\n\n  function bookingContainer() {\n    return document.getElementById(\n      \"ebFrontEndFrame\"\n    );\n  }\n\n  function readBookingData() {\n    const booking = bookingContainer();\n\n    if (!booking) return null;\n\n    function field(name) {\n      const el = booking.querySelector(\n        '[name=\"' + name + '\"]'\n      );\n\n      return el ? el.value : \"\";\n    }\n\n    const arrivalISO = toISO(\n      field(\"arrivalDate\")\n    );\n\n    const departureISO = toISO(\n      field(\"departureDate\")\n    );\n\n    const adults = Number(field(\"numAdults\"));\n    const children = Number(field(\"numChildren\"));\n    const rooms = Number(field(\"numRooms\"));\n\n    if (\n      !arrivalISO ||\n      !departureISO ||\n      departureISO <= arrivalISO ||\n      !Number.isInteger(adults) ||\n      adults < 1 ||\n      !Number.isInteger(children) ||\n      children < 0 ||\n      !Number.isInteger(rooms) ||\n      rooms < 1\n    ) {\n      return null;\n    }\n\n    return {\n      arrivalISO,\n      departureISO,\n      adults,\n      children,\n      rooms\n    };\n  }\n\n  \/* =====================================\n     NEU: WEBSEITEN WEITERLEITUNG\n  ===================================== *\/\n\n  function websiteBookingLink(property, data) {\n    if (!data) return property.url;\n\n    const url = new URL(property.url);\n\n    url.searchParams.set(\n      \"ils_arrival\",\n      data.arrivalISO\n    );\n\n    url.searchParams.set(\n      \"ils_departure\",\n      data.departureISO\n    );\n\n    url.searchParams.set(\n      \"ils_adults\",\n      String(data.adults)\n    );\n\n    url.searchParams.set(\n      \"ils_children\",\n      String(data.children)\n    );\n\n    url.searchParams.set(\n      \"ils_rooms\",\n      String(data.rooms)\n    );\n\n    return url.href;\n  }\n\n  \/* Bereits getesteter Direktlink als Fallback *\/\n\n  function easybookingDirectLink(property, data) {\n    if (!data || data.rooms !== 1) {\n      return property.url;\n    }\n\n    return (\n      \"https:\/\/www.easy-booking.at\/bookingengine2\/#\" +\n      property.customerId + \"\/2\/\" +\n      property.serialNo + \"\/\" +\n      data.arrivalISO + \"\/\" +\n      data.departureISO + \"\/\" +\n      data.adults + \"\/\" +\n      data.children\n    );\n  }\n\n  function readTransferredData() {\n    const params = new URLSearchParams(\n      location.search\n    );\n\n    if (!params.has(\"ils_arrival\")) {\n      return null;\n    }\n\n    const arrivalISO = toISO(\n      params.get(\"ils_arrival\")\n    );\n\n    const departureISO = toISO(\n      params.get(\"ils_departure\")\n    );\n\n    const adults = Number(\n      params.get(\"ils_adults\")\n    );\n\n    const children = Number(\n      params.get(\"ils_children\")\n    );\n\n    const rooms = Number(\n      params.get(\"ils_rooms\") || \"1\"\n    );\n\n    if (\n      !arrivalISO ||\n      !departureISO ||\n      departureISO <= arrivalISO ||\n      !Number.isInteger(adults) ||\n      adults < 1 ||\n      !Number.isInteger(children) ||\n      children < 0 ||\n      !Number.isInteger(rooms) ||\n      rooms < 1\n    ) {\n      return null;\n    }\n\n    return {\n      arrivalISO,\n      departureISO,\n      adults,\n      children,\n      rooms\n    };\n  }\n\n  \/* =====================================\n     NEU: DATEN IN EASYBOOKING EINTRAGEN\n  ===================================== *\/\n\n  function setFieldValue(field, value) {\n    if (!field) return false;\n\n    const text = String(value);\n\n    if (field.value === text) return true;\n\n    \/*\n      Native Setter verwenden, damit\n      auch dynamisch verwaltete Eingaben\n      eine normale Wert\u00e4nderung erhalten.\n    *\/\n\n    const prototype =\n      field instanceof HTMLSelectElement\n        ? HTMLSelectElement.prototype\n        : HTMLInputElement.prototype;\n\n    const setter = Object.getOwnPropertyDescriptor(\n      prototype,\n      \"value\"\n    )?.set;\n\n    if (setter) {\n      setter.call(field, text);\n    } else {\n      field.value = text;\n    }\n\n    field.dispatchEvent(\n      new Event(\"input\", { bubbles: true })\n    );\n\n    field.dispatchEvent(\n      new Event(\"change\", { bubbles: true })\n    );\n\n    return field.value === text;\n  }\n\n  function transferToBooking(data) {\n    const booking = bookingContainer();\n\n    if (!booking) return false;\n\n    const arrival = booking.querySelector(\n      '[name=\"arrivalDate\"]'\n    );\n\n    const departure = booking.querySelector(\n      '[name=\"departureDate\"]'\n    );\n\n    const adults = booking.querySelector(\n      '[name=\"numAdults\"]'\n    );\n\n    const children = booking.querySelector(\n      '[name=\"numChildren\"]'\n    );\n\n    const rooms = booking.querySelector(\n      '[name=\"numRooms\"]'\n    );\n\n    if (\n      !arrival ||\n      !departure ||\n      !adults ||\n      !children\n    ) {\n      return false;\n    }\n\n    function dateValue(field, iso) {\n      return field.type === \"date\"\n        ? iso\n        : toGerman(iso);\n    }\n\n    const okArrival = setFieldValue(\n      arrival,\n      dateValue(arrival, data.arrivalISO)\n    );\n\n    const okDeparture = setFieldValue(\n      departure,\n      dateValue(departure, data.departureISO)\n    );\n\n    const okAdults = setFieldValue(\n      adults,\n      data.adults\n    );\n\n    const okChildren = setFieldValue(\n      children,\n      data.children\n    );\n\n    const okRooms = rooms\n      ? setFieldValue(rooms, data.rooms)\n      : data.rooms === 1;\n\n    return (\n      okArrival &&\n      okDeparture &&\n      okAdults &&\n      okChildren &&\n      okRooms\n    );\n  }\n\n  function initializeTransferredBooking() {\n    const data = readTransferredData();\n\n    if (!data) return;\n\n    const box = document.getElementById(\n      \"ils-transfer-box\"\n    );\n\n    const text = document.getElementById(\n      \"ils-transfer-text\"\n    );\n\n    const status = document.getElementById(\n      \"ils-transfer-status\"\n    );\n\n    const fallback = document.getElementById(\n      \"ils-transfer-fallback\"\n    );\n\n    if (!box || !text || !status || !fallback) {\n      return;\n    }\n\n    \/*\n      Hinweis vor dem Buchungstool anzeigen,\n      damit der Gast die \u00fcbernommenen Daten\n      direkt sehen kann.\n    *\/\n\n    const booking = bookingContainer();\n\n    if (booking && booking.parentElement) {\n      booking.parentElement.insertBefore(\n        box,\n        booking\n      );\n    }\n\n    box.hidden = false;\n\n    text.textContent =\n      toGerman(data.arrivalISO) +\n      \" bis \" +\n      toGerman(data.departureISO) +\n      \" | \" + data.adults + \" Erwachsene\" +\n      \" | \" + data.children + \" Kinder\";\n\n    fallback.href = easybookingDirectLink(\n      current,\n      data\n    );\n\n    let attempts = 0;\n    let completed = false;\n\n    function apply() {\n      if (completed) return;\n\n      attempts++;\n\n      const success = transferToBooking(data);\n\n      if (success) {\n        completed = true;\n\n        status.textContent =\n          \"Die Reisedaten sind eingetragen. \" +\n          \"Bitte pr\u00fcfen Sie die Verf\u00fcgbarkeit \" +\n          \"im Buchungstool.\";\n\n        log(\"Reisedaten \u00fcbertragen\", data);\n\n        return;\n      }\n\n      if (attempts >= 40) {\n        completed = true;\n\n        status.textContent =\n          \"Die automatische \u00dcbernahme konnte \" +\n          \"nicht vollst\u00e4ndig best\u00e4tigt werden. \" +\n          \"Bitte kontrollieren Sie die Angaben \" +\n          \"oder \u00f6ffnen Sie die direkte Buchungssuche.\";\n      }\n    }\n\n    apply();\n\n    const interval = setInterval(function () {\n      if (completed) {\n        clearInterval(interval);\n        return;\n      }\n\n      apply();\n\n      if (completed) {\n        clearInterval(interval);\n      }\n    }, 300);\n  }\n\n  \/* =====================================\n     TEXTE JE UNTERKUNFT\n  ===================================== *\/\n\n  const introTexts = {\n    villa:\n      \"Leider haben wir in der Villa Ottilienruh \" +\n      \"f\u00fcr Ihren gew\u00fcnschten Reisezeitraum kein \" +\n      \"passendes Angebot gefunden. Vielleicht \" +\n      \"sind die Chalets zum Ilsetal oder die \" +\n      \"Landh\u00e4user Ilsenburg eine Alternative. \" +\n      \"Wir pr\u00fcfen gerne die M\u00f6glichkeiten f\u00fcr Sie.\",\n\n    chalets:\n      \"Leider haben wir in unseren Chalets zum \" +\n      \"Ilsetal f\u00fcr Ihren gew\u00fcnschten Reisezeitraum \" +\n      \"kein passendes Angebot gefunden. Vielleicht \" +\n      \"finden Sie in der Villa Ottilienruh oder \" +\n      \"den Landh\u00e4usern Ilsenburg eine passende \" +\n      \"Unterkunft.\",\n\n    landhaeuser:\n      \"Leider haben wir in unseren Landh\u00e4usern \" +\n      \"Ilsenburg f\u00fcr Ihren gew\u00fcnschten Reisezeitraum \" +\n      \"kein passendes Angebot gefunden. Vielleicht \" +\n      \"sind die Villa Ottilienruh oder die Chalets \" +\n      \"zum Ilsetal eine passende Alternative.\"\n  };\n\n  const intro = document.getElementById(\n    \"ils-check-intro\"\n  );\n\n  if (intro) {\n    intro.textContent =\n      introTexts[current.id] || \"\";\n  }\n\n  \/* =====================================\n     PREIS UND BESCHREIBUNG\n  ===================================== *\/\n\n  function formatPrice(price) {\n    return new Intl.NumberFormat(\"de-DE\", {\n      style: \"currency\",\n      currency: \"EUR\"\n    }).format(price);\n  }\n\n  function getPrice(cat) {\n    const raw = cat?.priceDetails?.totalPrice;\n\n    if (\n      raw === null ||\n      raw === undefined ||\n      raw === \"\"\n    ) {\n      return null;\n    }\n\n    const price = Number(raw);\n\n    return Number.isFinite(price) && price > 0\n      ? price\n      : null;\n  }\n\n  function cleanDescription(html) {\n    if (typeof html !== \"string\") return \"\";\n\n    const doc = new DOMParser().parseFromString(\n      html,\n      \"text\/html\"\n    );\n\n    doc.querySelectorAll(\n      \"script, style, iframe, object, embed, template\"\n    ).forEach(el => el.remove());\n\n    return (doc.body.textContent || \"\")\n      .replace(\/\\s+\/g, \" \")\n      .trim();\n  }\n\n  \/* =====================================\n     GALERIEBILDER\n  ===================================== *\/\n\n  function safeUrl(value) {\n    if (typeof value !== \"string\") return \"\";\n\n    try {\n      const url = new URL(value);\n\n      if (url.protocol !== \"https:\") {\n        return \"\";\n      }\n\n      const host = url.hostname.toLowerCase();\n\n      if (\n        host === \"images.cdn-easybooking.cloud\" ||\n        host.endsWith(\".cdn-easybooking.cloud\") ||\n        host === \"easy-booking.at\" ||\n        host.endsWith(\".easy-booking.at\")\n      ) {\n        return url.href;\n      }\n    } catch (e) {}\n\n    return \"\";\n  }\n\n  function imageCandidates(path) {\n    if (typeof path !== \"string\") return [];\n\n    const value = path.trim();\n    if (!value) return [];\n\n    const urls = [];\n\n    function add(url) {\n      const safe = safeUrl(url);\n\n      if (safe && !urls.includes(safe)) {\n        urls.push(safe);\n      }\n    }\n\n    if (value.startsWith(\"https:\/\/\")) {\n      add(value);\n      return urls;\n    }\n\n    if (value.startsWith(\"\/\/\")) {\n      add(\"https:\" + value);\n      return urls;\n    }\n\n    if (value.startsWith(\"\/cdn-migration\/\")) {\n      add(\n        IMAGE_HOST +\n        value.replace(\/^\\\/cdn-migration\/, \"\")\n      );\n    }\n\n    if (value.startsWith(\"\/\")) {\n      add(IMAGE_HOST + value);\n    } else {\n      add(IMAGE_HOST + \"\/\" + value);\n    }\n\n    return urls;\n  }\n\n  function categoryImages(cat) {\n    if (!SHOW_IMAGES || !cat) return [];\n\n    const galleries = [];\n\n    if (Array.isArray(cat.catPic)) {\n      galleries.push(cat.catPic);\n    }\n\n    if (Array.isArray(cat.rooms)) {\n      cat.rooms.forEach(room => {\n        if (Array.isArray(room?.catPic)) {\n          galleries.push(room.catPic);\n        }\n      });\n    }\n\n    const urls = [];\n\n    galleries.forEach(gallery => {\n      const pictures = gallery.slice().sort(\n        (a, b) =>\n          Number(a.egp_sortOrder || 0) -\n          Number(b.egp_sortOrder || 0)\n      );\n\n      pictures.forEach(pic => {\n        if (!pic) return;\n\n        [\n          pic.egp_cdnFullUrl,\n          pic.egp_picCdnFullUrl,\n          pic.egp_picPath\n        ].forEach(path => {\n          imageCandidates(path).forEach(url => {\n            if (!urls.includes(url)) {\n              urls.push(url);\n            }\n          });\n        });\n      });\n    });\n\n    return urls;\n  }\n\n  \/* =====================================\n     LIVE ANTWORT AUSWERTEN\n  ===================================== *\/\n\n  function analyzeResponse(response, rooms) {\n    if (!response || !Array.isArray(response.catArr)) {\n      return {\n        status: \"unknown\",\n        message: \"Antwort konnte nicht ausgewertet werden.\"\n      };\n    }\n\n    if (rooms !== 1) {\n      return {\n        status: \"unknown\",\n        message:\n          \"Mehrere Zimmer bitte direkt \" +\n          \"im Buchungssystem pr\u00fcfen.\"\n      };\n    }\n\n    if (response.errorMsg) {\n      return {\n        status: \"unavailable\",\n        message:\n          \"Kein passendes Angebot f\u00fcr diesen Zeitraum.\"\n      };\n    }\n\n    const offers = response.catArr\n      .filter(cat =>\n        Number(cat.roomCount) > 0 &&\n        Array.isArray(cat.rooms) &&\n        cat.rooms.length > 0\n      )\n      .map(cat => ({\n        name: cat.catName || \"Unterkunft\",\n        price: getPrice(cat),\n        minPersons: Number(cat.minPersons),\n        maxPersons: Number(cat.maxPersons),\n        description: cleanDescription(cat.desc),\n        images: categoryImages(cat)\n      }));\n\n    if (!offers.length) {\n      return {\n        status: \"unknown\",\n        message:\n          \"Keine eindeutig auswertbaren \" +\n          \"Buchungsangebote gefunden.\"\n      };\n    }\n\n    offers.sort((a, b) => {\n      if (a.price === null && b.price === null) {\n        return 0;\n      }\n      if (a.price === null) return 1;\n      if (b.price === null) return -1;\n      return a.price - b.price;\n    });\n\n    return {\n      status: \"available\",\n      count: offers.length,\n      cheapest: offers[0]\n    };\n  }\n\n  async function fetchAvailability(\n    property,\n    data,\n    signal\n  ) {\n    const params = new URLSearchParams({\n      customerId: String(property.customerId),\n      numAdults: String(data.adults),\n      numChildren: String(data.children),\n      arrivalDate: data.arrivalISO,\n      departureDate: data.departureISO,\n      localeId: \"2\",\n      catId: \"false\",\n      roomId: \"false\"\n    });\n\n    const response = await fetch(\n      API + property.customerId + \"\/2\",\n      {\n        method: \"POST\",\n        mode: \"cors\",\n        credentials: \"omit\",\n        headers: {\n          \"Content-Type\":\n            \"application\/x-www-form-urlencoded;charset=UTF-8\"\n        },\n        body: params.toString(),\n        signal\n      }\n    );\n\n    if (!response.ok) {\n      throw new Error(\"HTTP \" + response.status);\n    }\n\n    return analyzeResponse(\n      await response.json(),\n      data.rooms\n    );\n  }\n\n  \/* =====================================\n     KARTEN ERSTELLEN\n  ===================================== *\/\n\n  const cards = new Map();\n\n  function createCards() {\n    const container = document.getElementById(\n      \"ils-check-results\"\n    );\n\n    if (!container) return;\n\n    container.replaceChildren();\n    cards.clear();\n\n    alternatives.forEach(property => {\n      const article = make(\n        \"article\",\n        \"ils-check-card\"\n      );\n\n      const inner = make(\n        \"div\",\n        \"ils-check-card-inner\"\n      );\n\n      const imageWrap = make(\n        \"div\",\n        \"ils-check-image-wrap\"\n      );\n\n      imageWrap.hidden = true;\n\n      const image = make(\n        \"img\",\n        \"ils-check-image\"\n      );\n\n      image.alt = \"\";\n      image.decoding = \"async\";\n\n      imageWrap.appendChild(image);\n\n      const info = make(\n        \"div\",\n        \"ils-check-info\"\n      );\n\n      const title = make(\n        \"h3\",\n        \"\",\n        property.name\n      );\n\n      const badge = make(\n        \"span\",\n        \"ils-check-badge\",\n        \"Wird gepr\u00fcft ...\"\n      );\n\n      const category = make(\n        \"div\",\n        \"ils-check-category\"\n      );\n\n      const persons = make(\n        \"div\",\n        \"ils-check-persons\"\n      );\n\n      const price = make(\n        \"div\",\n        \"ils-check-price\"\n      );\n\n      const priceNote = make(\n        \"span\",\n        \"ils-check-price-note\"\n      );\n\n      const description = make(\n        \"div\",\n        \"ils-check-description\"\n      );\n\n      description.hidden = true;\n\n      const more = make(\n        \"button\",\n        \"ils-check-more\",\n        \"Mehr anzeigen\"\n      );\n\n      more.type = \"button\";\n      more.hidden = true;\n\n      more.addEventListener(\"click\", function () {\n        const expanded =\n          more.getAttribute(\"aria-expanded\") === \"true\";\n\n        more.setAttribute(\n          \"aria-expanded\",\n          String(!expanded)\n        );\n\n        description.textContent = expanded\n          ? more.dataset.shortText\n          : more.dataset.fullText;\n\n        more.textContent = expanded\n          ? \"Mehr anzeigen\"\n          : \"Weniger anzeigen\";\n      });\n\n      const details = make(\n        \"div\",\n        \"ils-check-details\"\n      );\n\n      const button = make(\n        \"a\",\n        \"ils-check-button\",\n        \"Unterkunft ansehen & buchen \u2192\"\n      );\n\n      button.href = property.url;\n      button.target = \"_self\";\n\n      info.append(\n        title,\n        badge,\n        category,\n        persons,\n        price,\n        priceNote,\n        description,\n        more,\n        details,\n        button\n      );\n\n      inner.append(imageWrap, info);\n      article.appendChild(inner);\n      container.appendChild(article);\n\n      cards.set(property.id, {\n        property,\n        imageWrap,\n        image,\n        badge,\n        category,\n        persons,\n        price,\n        priceNote,\n        description,\n        more,\n        details,\n        button,\n        imageToken: 0\n      });\n    });\n  }\n\n  function clearCard(card) {\n    card.imageToken++;\n    card.imageWrap.hidden = true;\n    card.image.removeAttribute(\"src\");\n\n    card.category.textContent = \"\";\n    card.persons.textContent = \"\";\n    card.price.textContent = \"\";\n    card.priceNote.textContent = \"\";\n\n    card.description.textContent = \"\";\n    card.description.hidden = true;\n\n    card.more.hidden = true;\n    card.more.textContent = \"Mehr anzeigen\";\n    card.more.setAttribute(\"aria-expanded\", \"false\");\n  }\n\n  function loadImage(card, urls) {\n    const token = ++card.imageToken;\n\n    if (!SHOW_IMAGES || !urls?.length) return;\n\n    let index = 0;\n\n    function tryNext() {\n      if (token !== card.imageToken) return;\n\n      if (index >= urls.length) return;\n\n      const url = urls[index++];\n      const probe = new Image();\n\n      probe.onload = function () {\n        if (token !== card.imageToken) return;\n\n        card.image.src = url;\n        card.imageWrap.hidden = false;\n      };\n\n      probe.onerror = tryNext;\n      probe.src = url;\n    }\n\n    tryNext();\n  }\n\n  function renderDescription(card, text) {\n    if (!text) return;\n\n    card.description.hidden = false;\n\n    if (text.length <= DESCRIPTION_LENGTH) {\n      card.description.textContent = text;\n      return;\n    }\n\n    let short = text.slice(0, DESCRIPTION_LENGTH);\n\n    const space = short.lastIndexOf(\" \");\n\n    if (space > 100) {\n      short = short.slice(0, space);\n    }\n\n    short += \" \u2026\";\n\n    card.description.textContent = short;\n    card.more.dataset.shortText = short;\n    card.more.dataset.fullText = text;\n    card.more.hidden = false;\n    card.more.setAttribute(\"aria-expanded\", \"false\");\n  }\n\n  function setLoading(card) {\n    clearCard(card);\n\n    card.badge.className = \"ils-check-badge\";\n    card.badge.textContent = \"Wird gepr\u00fcft ...\";\n\n    card.details.textContent =\n      \"Wir suchen nach passenden Angeboten.\";\n  }\n\n  function showResult(card, result) {\n    clearCard(card);\n\n    card.badge.className = \"ils-check-badge\";\n\n    if (result.status === \"available\") {\n      const offer = result.cheapest;\n\n      card.badge.classList.add(\"available\");\n      card.badge.textContent = \"Angebote gefunden\";\n\n      card.category.textContent = offer.name;\n\n      if (\n        Number.isFinite(offer.minPersons) &&\n        Number.isFinite(offer.maxPersons) &&\n        offer.maxPersons > 0\n      ) {\n        card.persons.textContent =\n          \"F\u00fcr \" + offer.minPersons +\n          \" bis \" + offer.maxPersons + \" Personen\";\n      }\n\n      if (SHOW_PRICES && offer.price !== null) {\n        card.price.textContent =\n          formatPrice(offer.price);\n\n        card.priceNote.textContent =\n          \"Voraussichtlicher Gesamtpreis\";\n      }\n\n      renderDescription(card, offer.description);\n\n      card.details.textContent =\n        result.count +\n        \" passende Unterkunftskategorien gefunden.\";\n\n      loadImage(card, offer.images);\n\n    } else if (result.status === \"unavailable\") {\n      card.badge.classList.add(\"unavailable\");\n      card.badge.textContent = \"Kein passendes Angebot\";\n      card.details.textContent = result.message;\n\n    } else {\n      card.badge.textContent = \"Bitte pr\u00fcfen\";\n      card.details.textContent = result.message;\n    }\n  }\n\n  function showError(card) {\n    clearCard(card);\n\n    card.badge.className = \"ils-check-badge\";\n    card.badge.textContent = \"Pr\u00fcfung nicht m\u00f6glich\";\n\n    card.details.textContent =\n      \"Bitte die Angebote direkt im \" +\n      \"Buchungssystem pr\u00fcfen.\";\n  }\n\n  \/* =====================================\n     REISEDATEN ANZEIGEN\n  ===================================== *\/\n\n  function renderDates(data) {\n    const target = document.getElementById(\n      \"ils-check-dates\"\n    );\n\n    if (!target) return;\n\n    const text = data\n      ? \"Anreise: \" + toGerman(data.arrivalISO) +\n        \" | Abreise: \" + toGerman(data.departureISO) +\n        \" | \" + data.adults + \" Erwachsene\" +\n        \" | \" + data.children + \" Kinder\"\n      : \"Reisedaten konnten nicht \u00fcbernommen werden.\";\n\n    target.replaceChildren(\n      make(\"div\", \"ils-check-dates\", text)\n    );\n  }\n\n  \/* =====================================\n     AUTO SCROLL\n  ===================================== *\/\n\n  let lastScrolledSearch = \"\";\n  let scrollTimer = null;\n\n  function autoScroll(searchKey) {\n    if (\n      !AUTO_SCROLL ||\n      !searchKey ||\n      searchKey === lastScrolledSearch\n    ) {\n      return;\n    }\n\n    if (scrollTimer !== null) {\n      clearTimeout(scrollTimer);\n    }\n\n    scrollTimer = setTimeout(function () {\n      scrollTimer = null;\n\n      if (root.hidden || !root.isConnected) return;\n\n      lastScrolledSearch = searchKey;\n\n      const reduced = window.matchMedia(\n        \"(prefers-reduced-motion: reduce)\"\n      ).matches;\n\n      const offset =\n        window.innerWidth <= 700 ? 75 : 105;\n\n      const top =\n        root.getBoundingClientRect().top +\n        window.scrollY - offset;\n\n      window.scrollTo({\n        top: Math.max(0, top),\n        behavior: reduced ? \"auto\" : \"smooth\"\n      });\n    }, SCROLL_DELAY);\n  }\n\n  \/* =====================================\n     LIVE SUCHABLAUF\n  ===================================== *\/\n\n  let controller = null;\n  let lastKey = \"\";\n  let timer = null;\n\n  async function runSearch(data) {\n    if (controller) controller.abort();\n\n    controller = new AbortController();\n    const signal = controller.signal;\n\n    for (const card of cards.values()) {\n      setLoading(card);\n\n      card.button.href = websiteBookingLink(\n        card.property,\n        data\n      );\n    }\n\n    await Promise.all(\n      Array.from(cards.values()).map(async card => {\n        try {\n          const result = await fetchAvailability(\n            card.property,\n            data,\n            signal\n          );\n\n          if (!signal.aborted) {\n            showResult(card, result);\n          }\n\n        } catch (error) {\n          if (!signal.aborted) {\n            log(error);\n            showError(card);\n          }\n        }\n      })\n    );\n  }\n\n  function hasNoOfferMessage() {\n    const booking = bookingContainer();\n\n    if (!booking) return false;\n\n    return (\n      (booking.innerText || \"\")\n        .replace(\/\\s+\/g, \" \")\n        .toLowerCase()\n        .includes(\n          \"leider wird zu ihrem wunschtermin kein passendes angebot gefunden\"\n        )\n    );\n  }\n\n  function check() {\n    const noOffer = hasNoOfferMessage();\n\n    if (!noOffer) {\n      root.hidden = true;\n      lastKey = \"\";\n\n      if (scrollTimer !== null) {\n        clearTimeout(scrollTimer);\n        scrollTimer = null;\n      }\n\n      if (controller) {\n        controller.abort();\n        controller = null;\n      }\n\n      return;\n    }\n\n    const data = readBookingData();\n    const key = data ? JSON.stringify(data) : \"\";\n\n    const wasHidden = root.hidden;\n    root.hidden = false;\n\n    if (key !== lastKey || wasHidden) {\n      lastKey = key;\n\n      renderDates(data);\n\n      if (data) {\n        runSearch(data);\n      } else {\n        if (controller) {\n          controller.abort();\n          controller = null;\n        }\n\n        for (const card of cards.values()) {\n          showError(card);\n          card.button.href = card.property.url;\n        }\n      }\n    }\n\n    if (data && (wasHidden || key !== lastScrolledSearch)) {\n      autoScroll(key);\n    }\n  }\n\n  function scheduleCheck() {\n    if (timer !== null) clearTimeout(timer);\n\n    timer = setTimeout(function () {\n      timer = null;\n      check();\n    }, CHECK_DELAY);\n  }\n\n  \/* =====================================\n     INITIALISIERUNG\n  ===================================== *\/\n\n  function init() {\n    createCards();\n\n    \/*\n      Zuerst \u00fcbertragene Reisedaten\n      von der vorherigen Webseite\n      verarbeiten.\n    *\/\n\n    initializeTransferredBooking();\n\n    const booking = bookingContainer();\n\n    if (!booking) {\n      log(\"easybooking Container fehlt\");\n      return;\n    }\n\n    const observer = new MutationObserver(\n      scheduleCheck\n    );\n\n    observer.observe(booking, {\n      subtree: true,\n      childList: true,\n      characterData: true,\n      attributes: true,\n      attributeFilter: [\n        \"class\",\n        \"style\",\n        \"hidden\"\n      ]\n    });\n\n    document.addEventListener(\n      \"change\",\n      function (event) {\n        if (\n          event.target.closest &&\n          event.target.closest(\"#ebFrontEndFrame\")\n        ) {\n          scheduleCheck();\n        }\n      }\n    );\n\n    check();\n\n    log(\"Ilsenburg V10 aktiv:\", current.name);\n  }\n\n  if (document.readyState === \"loading\") {\n    document.addEventListener(\n      \"DOMContentLoaded\",\n      init,\n      { once: true }\n    );\n  } else {\n    init();\n  }\n\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f1e8f0f elementor-widget elementor-widget-html\" data-id=\"f1e8f0f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t <div id=\"ebFrontEndFrame\"><\/div>\n <script src=\"\/\/www.easy-booking.at\/bookingengine2\/js\/jquery.easybooking.frontend.js\"><\/script>\n <script>\n \tfunction ebFrontendLoadedCheck() {\n \t\tif (typeof jQuery != \"undefined\") {\n \t\t\tif (jQuery.fn.ebFrontEnd) {\n \t\t\t\tjQuery(\"#ebFrontEndFrame\").ebFrontEnd({\n \t\t\t\t\tcustomerId: 10632,\n \t\t\t\t\tserialNo: \"6154-7728-1794\",\n \t\t\t\t\tlocaleId: 2,\n \t\t\t\t\tframeId: \"ebFrontEndPlugin\",\n \t\t\t\t\tscrollOffset: 0,\n \t\t\t\t\tadultOnly: false,\n \t\t\t\t\tgtmDL: \"dataLayer\",\n \t\t\t\t\thideFilters: \"on\",\n \t\t\t\t\tdisableScroll: false,\n \t\t\t\t\tsource: \"\",\n \t\t\t\t\tcollapsedExtras: false\n \t\t\t\t});\n \t\t\t} else {\n \t\t\t\tsetTimeout(ebFrontendLoadedCheck, 200);\n \t\t\t}\n \t\t} else {\n \t\t\tsetTimeout(ebFrontendLoadedCheck, 200);\n \t\t}\n \t}\n \tebFrontendLoadedCheck();\n <\/script> \t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Buchen Entdecken Sie unsere weiteren Unterk\u00fcnfte Die angezeigten Angebote und Preise stammen aus der easybooking Suche. Verbindlich sind die Buchbarkeit und der Gesamtpreis im jeweiligen Buchungssystem. Ihre Reisedaten wurden \u00fcbernommen Die Buchungsmaske wird vorbereitet &#8230; Buchungssuche direkt in easybooking \u00f6ffnen #ils-live-check[hidden], #ils-transfer-box[hidden], #ils-live-check [hidden] { display: none !important; } #ils-live-check { width: 100%; max-width: 1100px; [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-3100","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/pages\/3100","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/comments?post=3100"}],"version-history":[{"count":3,"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/pages\/3100\/revisions"}],"predecessor-version":[{"id":3122,"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/pages\/3100\/revisions\/3122"}],"wp:attachment":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/en\/wp-json\/wp\/v2\/media?parent=3100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}