{"id":3171,"date":"2023-09-28T13:41:51","date_gmt":"2023-09-28T11:41:51","guid":{"rendered":"https:\/\/www.design-villa-ottilienruh.de\/book\/"},"modified":"2026-11-06T08:59:26","modified_gmt":"2026-11-06T07:59:26","slug":"book","status":"publish","type":"page","link":"https:\/\/www.design-villa-ottilienruh.de\/da\/book\/","title":{"rendered":"Book"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"3171\" class=\"elementor elementor-3171 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\">Book<\/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>Oplev vores \u00f8vrige overnatningsmuligheder<\/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      De viste tilbud og priser stammer fra\n      easybooking-s\u00f8gningen. Det er tilg\u00e6ngeligheden\n      og den samlede pris i det respektive\n      bookingsystem, der er bindende.\n    <\/p> \n  <\/div>\n<\/div>\n\n<div id=\"ils-transfer-box\" hidden=\"\">\n  <strong>Deres rejsedatoer er blevet overf\u00f8rt<\/strong>\n  <p id=\"ils-transfer-text\"><\/p>\n  <p id=\"ils-transfer-status\">\n    Bookingmodulet forberedes ...\n  <\/p>\n  <a id=\"ils-transfer-fallback\" href=\"#\" target=\"_blank\" rel=\"noopener noreferrer\">\n    \u00c5bn bookings\u00f8gningen direkte i 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>Book Oplev vores \u00f8vrige overnatningsmuligheder De viste tilbud og priser stammer fra easybooking-s\u00f8gningen. Det er tilg\u00e6ngeligheden og den samlede pris i det respektive bookingsystem, der er bindende. Deres rejsedatoer er blevet overf\u00f8rt Bookingmodulet forberedes &#8230; \u00c5bn bookings\u00f8gningen direkte i easybooking<\/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-3171","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/pages\/3171","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/comments?post=3171"}],"version-history":[{"count":1,"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/pages\/3171\/revisions"}],"predecessor-version":[{"id":3172,"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/pages\/3171\/revisions\/3172"}],"wp:attachment":[{"href":"https:\/\/www.design-villa-ottilienruh.de\/da\/wp-json\/wp\/v2\/media?parent=3171"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}