{"id":22644,"date":"2026-08-12T13:04:29","date_gmt":"2026-08-12T06:04:29","guid":{"rendered":"https:\/\/vietnamdetox.com\/retreat-ueberblick\/"},"modified":"2026-10-18T16:42:53","modified_gmt":"2026-10-18T09:42:53","slug":"retreat-ueberblick","status":"publish","type":"page","link":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/","title":{"rendered":"Retreat-\u00dcberblick"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"22644\" class=\"elementor elementor-22644 elementor-19050\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0c6112f e-flex e-con-boxed e-con e-parent\" data-id=\"0c6112f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-55437c4 elementor-widget elementor-widget-html\" data-id=\"55437c4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Elementor setup\n  1. Paste this entire file into ONE Elementor HTML widget.\n  2. Paste elementor-retreat-overview-hero.css into that widget's\n     Advanced > Custom CSS field.\n  3. Keep the parent container full width with zero padding and gap.\n\n  The hero images, retreat links and retreat names are loaded from the REST API.\n  When the dynamic retreat-list widget is on the same page, both widgets share\n  one page-level request promise and make only one normal API request together.\n-->\n\n<div class=\"vroh-root\" data-vroh-hero=\"\" data-api-url=\"\/wp-json\/retreats\/v1\/all\">\n\n<header class=\"vroh\" id=\"top\" data-vroh-hero-section=\"\" aria-label=\"Vietnam Detox retreat overview\">\n  <div class=\"vroh__background\" data-vroh-background=\"\" aria-hidden=\"true\">\n    <div class=\"vroh__media-status\" data-vroh-media-status=\"\">Retreat-Bilder werden geladen\u2026<\/div>\n  <\/div>\n\n  <div class=\"vroh__overlay\" data-vroh-overlay=\"\" aria-hidden=\"true\"><\/div>\n\n  <div class=\"vroh__inner\">\n    <div class=\"vroh__left\">\n      <div class=\"vroh__panel\" data-vroh-panel=\"\">\n        <button class=\"vroh__panel-toggle\" data-vroh-panel-toggle=\"\" type=\"button\" aria-label=\"Hide introduction\" aria-expanded=\"true\">\n          <span class=\"vroh__panel-toggle-label\" data-vroh-toggle-label=\"\">Box ausblenden<\/span>\n<span class=\"vroh__panel-toggle-icon\" data-vroh-toggle-icon=\"\" aria-hidden=\"true\">\u00d7<\/span>\n        <\/button>\n\n        <div class=\"vroh__panel-surface\" data-vroh-panel-surface=\"\">\n          <div class=\"vroh__panel-body\" data-vroh-panel-body=\"\">\n            <div class=\"vroh__copy\">\n              <p class=\"vroh__kicker\">Vietnam Detox \u00b7 Gef\u00fchrte Fasten-Retreats in Vietnam<\/p>\n\n              <h1 class=\"vroh__title\">\n Retreats f\u00fcr Menschen, die <span>zu viel tragen.<\/span>\n              <\/h1>\n\n              <p class=\"vroh__location\" data-vroh-location=\"\">\n Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.\n              <\/p>\n\n              <p class=\"vroh__lede\">Gleiche bew\u00e4hrte Methode. Unterschiedliche Wege, endlich aufzuh\u00f6ren. <\/p>\n\n              <p class=\"vroh__support\">\n F\u00fcr Menschen, die Verantwortung f\u00fcr Arbeit, Team oder Familie tragen und hinter einer stabilen Fassade auf dem Zahnfleisch gehen. F\u00fcnf gef\u00fchrte Tage \u2013 in dem Setting, das am besten zu Ihnen passt. \n              <\/p>\n\n              <div class=\"vroh__actions\">\n                <a href=\"#retreats\" class=\"vroh__button vroh__button--primary\">Retreats vergleichen<\/a>\n                <a href=\"https:\/\/vietnamdetox.com\/de\/faq\/\" class=\"vroh__button vroh__button--outline\">FAQ lesen<\/a>\n              <\/div>\n\n            <\/div>\n\n            <div class=\"vroh__slider-ui\" data-vroh-slider-ui=\"\" aria-label=\"Hero image controls\" hidden=\"\">\n              <div class=\"vroh__slider-progress\" aria-hidden=\"true\">\n                <span class=\"vroh__slider-progress-fill\" data-vroh-progress=\"\"><\/span>\n              <\/div>\n              <div class=\"vroh__slider-dots\" data-vroh-dots=\"\" role=\"tablist\" aria-label=\"Hero slides\"><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"vroh__right\" aria-hidden=\"true\"><\/div>\n  <\/div>\n<\/header>\n\n<div class=\"vroh-shortcuts\">\n  <nav class=\"vroh__retreat-links\" data-vroh-retreat-links=\"\" aria-label=\"Individual retreat pages\">\n    <span class=\"vroh__retreat-status\" data-vroh-retreat-status=\"\">Retreats werden geladen\u2026<\/span>\n  <\/nav>\n<\/div>\n\n<\/div>\n\n<script>\n(function() {\n  'use strict';\n\n  function getSharedRetreatApi() {\n    var shared = window.VietnamDetoxRetreatAPI = window.VietnamDetoxRetreatAPI || {};\n    shared.entries = shared.entries || {};\n    shared.version = shared.version || 0;\n\n    if (typeof shared.key !== 'function') {\n      shared.key = function(url) {\n        return new URL(url, window.location.href).href;\n      };\n    }\n\n    if (typeof shared.load !== 'function') {\n      shared.load = function(url, force) {\n        var key = shared.key(url);\n        if (!force && shared.entries[key]) return shared.entries[key].promise;\n\n        var controller = typeof AbortController === 'function' ? new AbortController() : null;\n        var timedOut = false;\n        var timeoutId = window.setTimeout(function() {\n          timedOut = true;\n          if (controller) controller.abort();\n        }, 15000);\n\n        var entry = {};\n        entry.promise = fetch(key, {\n          method: 'GET',\n          credentials: 'same-origin',\n          headers: { Accept: 'application\/json' },\n          signal: controller ? controller.signal : undefined\n        }).then(function(response) {\n          if (!response.ok) throw new Error('API request failed with status ' + response.status);\n          return response.json();\n        }).then(function(payload) {\n          var items = Array.isArray(payload)\n            ? payload\n            : Array.isArray(payload.retreats)\n              ? payload.retreats\n              : Array.isArray(payload.data)\n                ? payload.data\n                : null;\n\n          if (!items) throw new Error('The API returned an unexpected response shape.');\n\n          var result = { key: key, items: items, version: ++shared.version };\n          entry.result = result;\n          window.dispatchEvent(new CustomEvent('vietnamdetox:retreats-loaded', { detail: result }));\n          return result;\n        }).catch(function(error) {\n          if (timedOut) error.vietnamDetoxTimedOut = true;\n          throw error;\n        }).finally(function() {\n          window.clearTimeout(timeoutId);\n        });\n\n        shared.entries[key] = entry;\n        return entry.promise;\n      };\n    }\n\n    return shared;\n  }\n\n  function cleanText(value) {\n    if (value === null || typeof value === 'undefined') return '';\n    var parser = new DOMParser();\n    var parsed = parser.parseFromString(String(value), 'text\/html');\n    return (parsed.body.textContent || '').replace(\/\\s+\/g, ' ').trim();\n  }\n\n  function safeUrl(value, allowImageData) {\n    if (!value) return '';\n    try {\n      var parsed = new URL(String(value), window.location.href);\n      var allowed = parsed.protocol === 'http:' || parsed.protocol === 'https:';\n      if (allowImageData && parsed.protocol === 'data:') allowed = \/^data:image\\\/\/i.test(String(value));\n      return allowed ? parsed.href : '';\n    } catch (error) {\n      return '';\n    }\n  }\n\n  function createElement(tag, className, text) {\n    var element = document.createElement(tag);\n    if (className) element.className = className;\n    if (typeof text === 'string') element.textContent = text;\n    return element;\n  }\n\n  var roots = document.querySelectorAll('[data-vroh-hero]');\n\n  roots.forEach(function(root) {\n    if (root.dataset.vrohReady === 'true') return;\n    root.dataset.vrohReady = 'true';\n\n    var heroSection = root.querySelector('[data-vroh-hero-section]');\n    var panel = root.querySelector('[data-vroh-panel]');\n    var panelSurface = root.querySelector('[data-vroh-panel-surface]');\n    var panelBody = root.querySelector('[data-vroh-panel-body]');\n    var panelToggle = root.querySelector('[data-vroh-panel-toggle]');\n    var toggleLabel = root.querySelector('[data-vroh-toggle-label]');\n    var toggleIcon = root.querySelector('[data-vroh-toggle-icon]');\n    var background = root.querySelector('[data-vroh-background]');\n    var overlay = root.querySelector('[data-vroh-overlay]');\n    var locationCopy = root.querySelector('[data-vroh-location]');\n    var mediaStatus = root.querySelector('[data-vroh-media-status]');\n    var retreatLinks = root.querySelector('[data-vroh-retreat-links]');\n    var retreatStatus = root.querySelector('[data-vroh-retreat-status]');\n    var sliderUi = root.querySelector('[data-vroh-slider-ui]');\n    var dotsContainer = root.querySelector('[data-vroh-dots]');\n    var progress = root.querySelector('[data-vroh-progress]');\n    var endpoint = new URL(root.getAttribute('data-api-url') || '\/wp-json\/retreats\/v1\/all', window.location.href);\n    var language = String(endpoint.searchParams.get('lang') || document.documentElement.lang || 'en').toLowerCase().split(\/[-_]\/)[0];\n    if (language === 'vn') language = 'vi';\n    if (['en', 'vi', 'de'].indexOf(language) < 0) language = 'en';\n    endpoint.searchParams.set('lang', language);\n    var apiUrl = endpoint.href;\n    var translations = {\n      en: {\n        regionLabel: 'Vietnam Detox retreat overview', linksLabel: 'Individual retreat pages',\n        controlsLabel: 'Hero image controls', slidesLabel: 'Hero slides',\n        loadingImages: 'Loading retreat images\u2026', loadingRetreats: 'Loading retreats\u2026',\n        show: 'Show info', hide: 'Hide box', showIntroduction: 'Show introduction', hideIntroduction: 'Hide introduction',\n        noImages: 'No retreat images are currently available.', newRetreats: 'New retreats will be announced soon.',\n        retreatName: 'Vietnam Detox Retreat', showImage: 'Show image for ',\n        timeout: 'Retreat images took too long to load.', unavailable: 'Retreat images are temporarily unavailable.',\n        errorHelp: 'Please use the retreat list below or refresh the page.'\n      },\n      vi: {\n        regionLabel: 'T\u1ed5ng quan c\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng Vietnam Detox', linksLabel: 'Trang th\u00f4ng tin t\u1eebng kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng',\n        controlsLabel: '\u0110i\u1ec1u khi\u1ec3n h\u00ecnh \u1ea3nh gi\u1edbi thi\u1ec7u', slidesLabel: 'H\u00ecnh \u1ea3nh c\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng',\n        loadingImages: '\u0110ang t\u1ea3i h\u00ecnh \u1ea3nh kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng\u2026', loadingRetreats: '\u0110ang t\u1ea3i c\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng\u2026',\n        show: 'Hi\u1ec7n th\u00f4ng tin', hide: '\u1ea8n h\u1ed9p', showIntroduction: 'Hi\u1ec7n ph\u1ea7n gi\u1edbi thi\u1ec7u', hideIntroduction: '\u1ea8n ph\u1ea7n gi\u1edbi thi\u1ec7u',\n        noImages: 'Hi\u1ec7n ch\u01b0a c\u00f3 h\u00ecnh \u1ea3nh kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng.', newRetreats: 'C\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng m\u1edbi s\u1ebd s\u1edbm \u0111\u01b0\u1ee3c c\u00f4ng b\u1ed1.',\n        retreatName: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng Vietnam Detox', showImage: 'Hi\u1ec7n h\u00ecnh \u1ea3nh c\u1ee7a ',\n        timeout: 'T\u1ea3i h\u00ecnh \u1ea3nh kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng m\u1ea5t qu\u00e1 nhi\u1ec1u th\u1eddi gian.', unavailable: 'H\u00ecnh \u1ea3nh kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng t\u1ea1m th\u1eddi kh\u00f4ng kh\u1ea3 d\u1ee5ng.',\n        errorHelp: 'Vui l\u00f2ng xem danh s\u00e1ch kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng b\u00ean d\u01b0\u1edbi ho\u1eb7c t\u1ea3i l\u1ea1i trang.'\n      },\n      de: {\n        regionLabel: 'Retreat-\u00dcberblick von Vietnam Detox', linksLabel: 'Seiten der einzelnen Retreats',\n        controlsLabel: 'Steuerung der Titelbilder', slidesLabel: 'Titelbilder',\n        loadingImages: 'Retreat-Bilder werden geladen\u2026', loadingRetreats: 'Retreats werden geladen\u2026',\n        show: 'Info anzeigen', hide: 'Box ausblenden', showIntroduction: 'Einleitung anzeigen', hideIntroduction: 'Einleitung ausblenden',\n        noImages: 'Derzeit sind keine Retreat-Bilder verf\u00fcgbar.', newRetreats: 'Neue Retreats werden bald bekannt gegeben.',\n        retreatName: 'Vietnam Detox Retreat', showImage: 'Bild anzeigen f\u00fcr ',\n        timeout: 'Das Laden der Retreat-Bilder hat zu lange gedauert.', unavailable: 'Retreat-Bilder sind vor\u00fcbergehend nicht verf\u00fcgbar.',\n        errorHelp: 'Bitte nutzen Sie die Retreat-Liste weiter unten oder laden Sie die Seite neu.'\n      }\n    };\n    var copy = translations[language] || translations.en;\n    heroSection.setAttribute('aria-label', copy.regionLabel);\n    retreatLinks.setAttribute('aria-label', copy.linksLabel);\n    sliderUi.setAttribute('aria-label', copy.controlsLabel);\n    dotsContainer.setAttribute('aria-label', copy.slidesLabel);\n    if (mediaStatus) mediaStatus.textContent = copy.loadingImages;\n    if (retreatStatus) retreatStatus.textContent = copy.loadingRetreats;\n    var sharedApi = getSharedRetreatApi();\n    var apiKey = sharedApi.key(apiUrl);\n    var mobileQuery = window.matchMedia('(max-width: 767px)');\n    var reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');\n    var duration = 8200;\n    var timerId = null;\n    var current = 0;\n    var slides = [];\n    var dots = [];\n    var links = [];\n    var lastVersion = 0;\n\n    \/\/ A translation can drop the label span and leave bare text in the button; rebuild it.\n    if (panelToggle && !toggleLabel) {\n      Array.prototype.slice.call(panelToggle.childNodes).forEach(function(node) {\n        if (node.nodeType === 3) panelToggle.removeChild(node);\n      });\n      toggleLabel = document.createElement('span');\n      toggleLabel.className = 'vroh__panel-toggle-label';\n      toggleLabel.setAttribute('data-vroh-toggle-label', '');\n      panelToggle.insertBefore(toggleLabel, toggleIcon && toggleIcon.parentNode === panelToggle ? toggleIcon : null);\n    }\n\n    function syncPanel() {\n      if (!panel || !panelSurface || !panelToggle || !toggleLabel || !toggleIcon) return;\n      var isCollapsed = panel.classList.contains('is-collapsed');\n      toggleLabel.textContent = isCollapsed ? copy.show : copy.hide;\n      toggleIcon.textContent = isCollapsed ? '+' : '\\u00d7';\n      panelToggle.setAttribute('aria-label', isCollapsed ? copy.showIntroduction : copy.hideIntroduction);\n      panelToggle.setAttribute('aria-expanded', String(!isCollapsed));\n      panelSurface.setAttribute('aria-hidden', String(isCollapsed));\n    }\n\n    function syncMediaLayout() {\n      if (!background || !overlay || !panelBody || !locationCopy) return;\n\n      if (mobileQuery.matches) {\n        if (background.parentElement !== panelBody) {\n          var mobileAnchor = locationCopy.parentElement === panelBody ? locationCopy : sliderUi;\n          panelBody.insertBefore(background, mobileAnchor);\n        }\n      } else if (background.parentElement !== heroSection) {\n        heroSection.insertBefore(background, overlay);\n      }\n    }\n\n    function preloadSlide(index) {\n      var slide = slides[index];\n      if (!slide || !slide.dataset.imageUrl) return;\n      var image = new Image();\n      image.src = slide.dataset.imageUrl;\n    }\n\n    function syncSlides() {\n      slides.forEach(function(slide, index) {\n        slide.classList.toggle('is-active', index === current);\n      });\n\n      dots.forEach(function(dot, index) {\n        var isActive = index === current;\n        dot.classList.toggle('is-active', isActive);\n        dot.setAttribute('aria-selected', String(isActive));\n        dot.tabIndex = isActive ? 0 : -1;\n      });\n\n      links.forEach(function(link, index) {\n        link.classList.toggle('is-active', index === current);\n      });\n    }\n\n    function restartProgress() {\n      if (!progress) return;\n      progress.classList.remove('is-animating');\n\n      if (reducedMotionQuery.matches || slides.length < 2) {\n        progress.style.transform = 'scaleX(1)';\n        return;\n      }\n\n      progress.style.transform = 'scaleX(0)';\n      void progress.offsetWidth;\n      progress.classList.add('is-animating');\n    }\n\n    function clearTimer() {\n      if (timerId === null) return;\n      window.clearTimeout(timerId);\n      timerId = null;\n    }\n\n    function queueNextSlide() {\n      clearTimer();\n      if (reducedMotionQuery.matches || document.hidden || slides.length < 2) return;\n      timerId = window.setTimeout(function() {\n        goToSlide(current + 1);\n      }, duration);\n    }\n\n    function goToSlide(index) {\n      if (!slides.length) return;\n      current = (index + slides.length) % slides.length;\n      syncSlides();\n      restartProgress();\n      preloadSlide((current + 1) % slides.length);\n      queueNextSlide();\n    }\n\n    function bindDynamicControls() {\n      dots.forEach(function(dot) {\n        dot.addEventListener('click', function() {\n          goToSlide(Number(dot.dataset.slideIndex));\n        });\n\n        dot.addEventListener('keydown', function(event) {\n          if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;\n          event.preventDefault();\n          goToSlide(current + (event.key === 'ArrowRight' ? 1 : -1));\n          dots[current].focus();\n        });\n      });\n\n      links.forEach(function(link) {\n        link.addEventListener('mouseenter', function() {\n          goToSlide(Number(link.dataset.slideIndex));\n        });\n        link.addEventListener('focus', function() {\n          goToSlide(Number(link.dataset.slideIndex));\n        });\n      });\n    }\n\n    function renderRetreats(result) {\n      if (!result || result.key !== apiKey || result.version === lastVersion) return;\n      lastVersion = result.version;\n      clearTimer();\n      background.replaceChildren();\n      retreatLinks.replaceChildren();\n      dotsContainer.replaceChildren();\n\n      var items = result.items.filter(function(item) {\n        return item && typeof item === 'object';\n      });\n\n      if (!items.length) {\n        background.appendChild(createElement('div', 'vroh__media-status', copy.noImages));\n        retreatLinks.appendChild(createElement('span', 'vroh__retreat-status', copy.newRetreats));\n        sliderUi.hidden = true;\n        slides = [];\n        dots = [];\n        links = [];\n        return;\n      }\n\n      items.forEach(function(item, index) {\n        var name = cleanText(item.name) || copy.retreatName;\n        var location = cleanText(item.location) || 'Vietnam Detox';\n        var imageUrl = safeUrl(item.image, true);\n        var pageUrl = safeUrl(item.url, false);\n        var slide = createElement('div', 'vroh__slide');\n        slide.dataset.vrohSlide = '';\n        slide.dataset.imageUrl = imageUrl;\n        if (imageUrl) slide.style.backgroundImage = 'url(\"' + imageUrl.replace(\/\"\/g, '%22') + '\")';\n        else slide.classList.add('is-missing');\n        background.appendChild(slide);\n\n        var dot = createElement('button', 'vroh__slider-dot');\n        dot.type = 'button';\n        dot.setAttribute('role', 'tab');\n        dot.setAttribute('aria-label', copy.showImage + name);\n        dot.setAttribute('aria-selected', 'false');\n        dot.dataset.slideIndex = String(index);\n        dotsContainer.appendChild(dot);\n\n        var link = createElement(pageUrl ? 'a' : 'div', 'vroh__retreat-link');\n        if (pageUrl) link.href = pageUrl;\n        link.dataset.slideIndex = String(index);\n        link.appendChild(createElement('strong', '', name));\n        link.appendChild(createElement('span', '', location));\n        retreatLinks.appendChild(link);\n      });\n\n      slides = Array.prototype.slice.call(background.querySelectorAll('.vroh__slide'));\n      dots = Array.prototype.slice.call(dotsContainer.querySelectorAll('.vroh__slider-dot'));\n      links = Array.prototype.slice.call(retreatLinks.querySelectorAll('.vroh__retreat-link'));\n      current = 0;\n      sliderUi.hidden = slides.length < 2;\n      bindDynamicControls();\n      syncMediaLayout();\n      syncSlides();\n      restartProgress();\n      preloadSlide(slides.length > 1 ? 1 : 0);\n      queueNextSlide();\n    }\n\n    function showLoadError(error) {\n      if (lastVersion) return;\n      var message = error && error.vietnamDetoxTimedOut\n        ? copy.timeout\n        : copy.unavailable;\n\n      if (mediaStatus && mediaStatus.parentElement) mediaStatus.textContent = message;\n      if (retreatStatus && retreatStatus.parentElement) retreatStatus.textContent = copy.errorHelp;\n      sliderUi.hidden = true;\n    }\n\n    if (panelToggle) {\n      panelToggle.addEventListener('click', function() {\n        panel.classList.toggle('is-collapsed');\n        syncPanel();\n      });\n    }\n\n    window.addEventListener('vietnamdetox:retreats-loaded', function(event) {\n      renderRetreats(event.detail);\n    });\n\n    document.addEventListener('visibilitychange', function() {\n      if (document.hidden) clearTimer();\n      else {\n        restartProgress();\n        queueNextSlide();\n      }\n    });\n\n    function handleMotionPreference() {\n      restartProgress();\n      queueNextSlide();\n    }\n\n    if (typeof mobileQuery.addEventListener === 'function') {\n      mobileQuery.addEventListener('change', syncMediaLayout);\n      reducedMotionQuery.addEventListener('change', handleMotionPreference);\n    } else {\n      mobileQuery.addListener(syncMediaLayout);\n      reducedMotionQuery.addListener(handleMotionPreference);\n    }\n\n    syncPanel();\n    syncMediaLayout();\n\n    if (typeof window.fetch !== 'function') {\n      showLoadError(new Error('Fetch is unavailable.'));\n      return;\n    }\n\n    sharedApi.load(apiUrl, false).then(renderRetreats).catch(showLoadError);\n  });\n})();\n<\/script>\n\n<script>\n(function vdHeroHeaderClearance() {\n  \"use strict\";\n  \/\/ The global header is fixed over the hero. Measure how far it reaches into the hero and\n  \/\/ expose that as --vd-hero-clearance, so the hero panel always starts below the header.\n  var hero = document.querySelector(\".vroh\");\n  var headerRoot = document.querySelector(\".elementor-location-header\");\n  if (!hero || !headerRoot || hero.dataset.vdClearance === \"ready\") return;\n  hero.dataset.vdClearance = \"ready\";\n  var measuredWidth = null;\n\n  function update() {\n    var scrollTop = window.scrollY || window.pageYOffset || 0;\n    \/\/ The sticky header shrinks after scrolling; keep the measurement taken at the top.\n    if (scrollTop > 0 && measuredWidth === window.innerWidth) return;\n    var bottom = 0;\n    Array.prototype.forEach.call(headerRoot.children, function (el) {\n      if (el.classList.contains(\"elementor-sticky__spacer\")) return;\n      var style = window.getComputedStyle(el);\n      if (style.display === \"none\" || style.visibility === \"hidden\") return;\n      var rect = el.getBoundingClientRect();\n      if (!rect.height) return;\n      bottom = Math.max(bottom, rect.bottom + (style.position === \"fixed\" ? 0 : scrollTop));\n    });\n    var heroTop = hero.getBoundingClientRect().top + scrollTop;\n    measuredWidth = window.innerWidth;\n    hero.style.setProperty(\"--vd-hero-clearance\", Math.max(0, Math.round(bottom - heroTop)) + \"px\");\n  }\n\n  update();\n  document.addEventListener(\"DOMContentLoaded\", update, { once: true });\n  window.addEventListener(\"load\", update, { once: true });\n  window.addEventListener(\"resize\", update, { passive: true });\n  \/\/ Elementor makes the header sticky after load; re-measure when its classes change.\n  if (window.MutationObserver) {\n    new MutationObserver(update).observe(headerRoot, { attributes: true, attributeFilter: [\"class\"], subtree: true });\n  }\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-cb05801 e-flex e-con-boxed e-con e-parent\" data-id=\"cb05801\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0a87492 elementor-widget elementor-widget-html\" data-id=\"0a87492\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Elementor setup\n  1. Paste this entire file into ONE Elementor HTML widget.\n  2. Paste elementor-retreat-overview-intro.css into the same widget's\n     Advanced > Custom CSS field.\n\n  This component is static, so no JavaScript is required.\n-->\n\n<section class=\"vroi\" aria-labelledby=\"vroi-title\">\n  <div class=\"vroi__inner\">\n    <span class=\"vroi__label\">Kommende Retreats<\/span>\n\n    <h2 class=\"vroi__title\" id=\"vroi-title\">\n Verschiedene Formen von Ersch\u00f6pfung brauchen unterschiedliche Arten der Erholung.\n    <\/h2>\n\n    <p class=\"vroi__copy\">\n Manche G\u00e4ste brauchen tiefe Ruhe, andere eine ruhigere Struktur oder Unterst\u00fctzung rund um Ess- und Energierhythmen. Erholung sieht von Mensch zu Mensch anders aus. \n    <\/p>\n  <\/div>\n<\/section>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6a936df e-flex e-con-boxed e-con e-parent\" data-id=\"6a936df\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bc933a5 elementor-widget elementor-widget-html\" data-id=\"bc933a5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Elementor setup\n  1. Paste this entire file into ONE Elementor HTML widget.\n  2. Paste retreat-calendar.css into the same widget's\n     Advanced > Custom CSS field.\n  3. Place this widget between retreat-overview-intro and\n     dynamic-retreat-list.\n  4. The REST endpoint can be changed with the data-api-url attribute below.\n\n  The \"view retreats\" link points at #retreats, which is the section id the\n  dynamic-retreat-list widget renders on this page.\n-->\n\n<section class=\"vrc\" data-vrc=\"\" data-api-url=\"\/wp-json\/retreats\/v1\/all\" aria-labelledby=\"vrc-title\">\n  <div class=\"vrc__wrap\">\n    <div class=\"vrc__panel\">\n\n      <div class=\"vrc__header\">\n        <div>\n          <span class=\"vrc__eyebrow\" data-vrc-eyebrow=\"\">RETREAT-TERMINE<\/span>\n          <h2 class=\"vrc__title\" id=\"vrc-title\" data-vrc-heading=\"\">W\u00e4hlen Sie Ihren Reisemonat<\/h2>\n          <p class=\"vrc__copy\" data-vrc-intro=\"\">\n  Sehen Sie, welche Retreats in dem Monat verf\u00fcgbar sind, der zu Ihren Pl\u00e4nen passt.\n          <\/p>\n        <\/div>\n\n        <p class=\"vrc__note\" data-vrc-note=\"\">\n  Termine k\u00f6nnen sich \u00e4ndern. Bitte best\u00e4tigen Sie diese vor der Buchung. \n        <\/p>\n      <\/div>\n\n      <div class=\"vrc__divider\" aria-hidden=\"true\"><\/div>\n\n      <div class=\"vrc__months\" data-vrc-months=\"\" aria-label=\"Choose a travel month\"><\/div>\n\n      <div class=\"vrc__results\">\n        <h3 class=\"vrc__results-title\" data-vrc-results-title=\"\">Verf\u00fcgbare Retreats<\/h3>\n\n        <div class=\"vrc__list\" data-vrc-list=\"\" aria-live=\"polite\"><\/div>\n\n        <p class=\"vrc__message is-visible\" data-vrc-message=\"\">Retreat-Termine werden geladen\u2026<\/p>\n\n        <a href=\"#retreats\" class=\"vrc__action\" data-vrc-action=\"\">Retreat-Optionen ansehen<\/a>\n\n        <p class=\"vrc__planning-note\" data-vrc-planning=\"\">\n Geplante Retreats dauern f\u00fcnf Tage und vier N\u00e4chte. Private Retreats werden individuell arrangiert. \n        <\/p>\n      <\/div>\n\n    <\/div>\n  <\/div>\n\n  <noscript>\n    <p class=\"vrc__message is-visible\">Zum Laden des Retreat-Kalenders ist JavaScript erforderlich.<\/p>\n  <\/noscript>\n<\/section>\n\n<script>\n(function () {\n  'use strict';\n\n  var TEXT = {\n    en: {\n      locale: 'en-GB',\n      eyebrow: 'RETREAT DATES',\n      heading: 'Choose your travel month',\n      intro: 'See which retreats are available during the month that fits your plans.',\n      note: 'Dates may change. Please confirm before booking.',\n      planning: 'Scheduled retreats run for five days and four nights. Private Retreats are arranged individually.',\n      action: 'View retreat options',\n      date: 'date',\n      dates: 'dates',\n      noDates: 'No dates yet',\n      loading: 'Loading retreat dates\u2026',\n      noMonth: 'No upcoming retreats are currently available for this month.',\n      error: 'Could not load retreat information. Please try again later.',\n      limited: 'Limited places',\n      available: 'Available retreats'\n    },\n\n    vi: {\n      locale: 'vi-VN',\n      eyebrow: 'L\u1ecaCH RETREAT',\n      heading: 'Ch\u1ecdn th\u00e1ng b\u1ea1n mu\u1ed1n \u0111i',\n      intro: 'Xem nh\u1eefng retreat \u0111ang m\u1edf trong th\u00e1ng ph\u00f9 h\u1ee3p v\u1edbi k\u1ebf ho\u1ea1ch c\u1ee7a b\u1ea1n.',\n      note: 'L\u1ecbch c\u00f3 th\u1ec3 thay \u0111\u1ed5i. Vui l\u00f2ng x\u00e1c nh\u1eadn tr\u01b0\u1edbc khi \u0111\u1eb7t ch\u1ed7.',\n      planning: 'C\u00e1c retreat theo l\u1ecbch k\u00e9o d\u00e0i n\u0103m ng\u00e0y b\u1ed1n \u0111\u00eam. Retreat ri\u00eang t\u01b0 \u0111\u01b0\u1ee3c s\u1eafp x\u1ebfp ri\u00eang theo y\u00eau c\u1ea7u.',\n      action: 'Xem c\u00e1c l\u1ef1a ch\u1ecdn retreat',\n      date: 'l\u1ecbch',\n      dates: 'l\u1ecbch',\n      noDates: 'Ch\u01b0a c\u00f3 l\u1ecbch',\n      loading: '\u0110ang t\u1ea3i l\u1ecbch retreat\u2026',\n      noMonth: 'Kh\u00f4ng c\u00f3 l\u1ecbch retreat trong th\u00e1ng n\u00e0y.',\n      error: 'Kh\u00f4ng th\u1ec3 t\u1ea3i th\u00f4ng tin retreat. Vui l\u00f2ng th\u1eed l\u1ea1i sau.',\n      limited: 'S\u1ed1 ch\u1ed7 c\u00f3 h\u1ea1n',\n      available: 'Retreat hi\u1ec7n c\u00f3'\n    },\n\n    de: {\n      locale: 'de-DE',\n      eyebrow: 'RETREAT-TERMINE',\n      heading: 'W\u00e4hlen Sie Ihren Reisemonat',\n      intro: 'Sehen Sie, welche Retreats in dem Monat verf\u00fcgbar sind, der zu Ihrer Planung passt.',\n      note: 'Termine k\u00f6nnen sich \u00e4ndern. Bitte vor der Buchung best\u00e4tigen.',\n      planning: 'Geplante Retreats dauern f\u00fcnf Tage und vier N\u00e4chte. Private Retreats werden individuell vereinbart.',\n      action: 'Retreats ansehen',\n      date: 'Termin',\n      dates: 'Termine',\n      noDates: 'Noch keine Termine',\n      loading: 'Retreat-Termine werden geladen\u2026',\n      noMonth: 'In diesem Monat sind keine Retreats verf\u00fcgbar.',\n      error: 'Die Retreat-Informationen konnten nicht geladen werden.',\n      limited: 'Begrenzte Pl\u00e4tze',\n      available: 'Verf\u00fcgbare Retreats'\n    }\n  };\n\n  var privateCache = {};\n\n  function toArray(payload) {\n    if (Array.isArray(payload)) return payload;\n    if (payload && Array.isArray(payload.items)) return payload.items;\n    if (payload && Array.isArray(payload.retreats)) return payload.retreats;\n    if (payload && Array.isArray(payload.data)) return payload.data;\n    throw new Error('Unexpected retreat API response');\n  }\n\n  function privateLoad(apiUrl) {\n    if (privateCache[apiUrl]) return privateCache[apiUrl];\n\n    var controller = typeof AbortController === 'function' ? new AbortController() : null;\n    var timeoutId = window.setTimeout(function () {\n      if (controller) controller.abort();\n    }, 15000);\n\n    privateCache[apiUrl] = fetch(apiUrl, {\n      credentials: 'same-origin',\n      headers: { Accept: 'application\/json' },\n      signal: controller ? controller.signal : undefined\n    }).then(function (response) {\n      if (!response.ok) throw new Error('Retreat API returned HTTP ' + response.status);\n      return response.json();\n    }).then(function (payload) {\n      window.clearTimeout(timeoutId);\n      return toArray(payload);\n    }).catch(function (error) {\n      window.clearTimeout(timeoutId);\n      delete privateCache[apiUrl];\n      throw error;\n    });\n\n    return privateCache[apiUrl];\n  }\n\n  \/*\n   * Other Vietnam Detox widgets publish window.VietnamDetoxRetreatAPI, but the\n   * homepage and retreat-list builds use different, incompatible signatures.\n   * Detect the shape before reusing it so one shared request serves both\n   * widgets, and fall back to a private fetch when it is something else.\n   *\/\n  function loadRetreats(apiUrl, language) {\n    var shared = window.VietnamDetoxRetreatAPI;\n\n    if (shared && typeof shared.load === 'function') {\n      try {\n        \/\/ dynamic-retreat-list shape: load(url, force) -> { items: [...] }\n        if (typeof shared.key === 'function' && shared.entries) {\n          return Promise.resolve(shared.load(apiUrl, false)).then(toArray);\n        }\n\n        \/\/ homepage calendar shape: load(path, language) -> [...]\n        if (shared.requests) {\n          return Promise.resolve(shared.load(apiUrl, language)).then(toArray);\n        }\n      } catch (error) {\n        \/\/ Fall through to the private loader.\n      }\n    }\n\n    return privateLoad(apiUrl);\n  }\n\n  function initCalendar(root) {\n    if (root.dataset.vrcReady === 'true') return;\n    root.dataset.vrcReady = 'true';\n\n    var endpoint = new URL(\n      root.getAttribute('data-api-url') || '\/wp-json\/retreats\/v1\/all',\n      window.location.href\n    );\n\n    var language = String(\n      endpoint.searchParams.get('lang') || document.documentElement.lang || 'en'\n    ).toLowerCase().split(\/[-_]\/)[0];\n\n    if (language === 'vn') language = 'vi';\n    if (['en', 'vi', 'de'].indexOf(language) < 0) language = 'en';\n\n    endpoint.searchParams.set('lang', language);\n\n    var apiUrl = endpoint.href;\n    var text = TEXT[language];\n    var locale = text.locale;\n\n    var monthStrip = root.querySelector('[data-vrc-months]');\n    var list = root.querySelector('[data-vrc-list]');\n    var resultsTitle = root.querySelector('[data-vrc-results-title]');\n    var message = root.querySelector('[data-vrc-message]');\n\n    \/\/ Apply the translated static copy.\n    root.querySelector('[data-vrc-eyebrow]').textContent = text.eyebrow;\n    root.querySelector('[data-vrc-heading]').textContent = text.heading;\n    root.querySelector('[data-vrc-intro]').textContent = text.intro;\n    root.querySelector('[data-vrc-note]').textContent = text.note;\n    root.querySelector('[data-vrc-planning]').textContent = text.planning;\n    root.querySelector('[data-vrc-action]').textContent = text.action;\n    resultsTitle.textContent = text.available;\n\n    var today = new Date();\n    today.setHours(0, 0, 0, 0);\n\n    function parseApiDate(value) {\n      if (!value) return null;\n\n      var match = String(value).match(\/^(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})$\/);\n\n      if (match) {\n        var month = Number(match[1]);\n        var day = Number(match[2]);\n        var year = Number(match[3]);\n        var result = new Date(year, month - 1, day);\n\n        if (\n          result.getFullYear() === year &&\n          result.getMonth() === month - 1 &&\n          result.getDate() === day\n        ) {\n          return result;\n        }\n\n        return null;\n      }\n\n      match = String(value).match(\/^(\\d{4})-(\\d{2})-(\\d{2})$\/);\n\n      if (match) {\n        return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n      }\n\n      return null;\n    }\n\n    function monthKey(date) {\n      return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0');\n    }\n\n    function safeUrl(value) {\n      if (!value || typeof value !== 'string') return null;\n\n      try {\n        var url = new URL(value, window.location.origin);\n        return ['http:', 'https:'].indexOf(url.protocol) >= 0 ? url.href : null;\n      } catch (error) {\n        return null;\n      }\n    }\n\n    function showMessage(copy) {\n      message.textContent = copy || '';\n      message.classList.toggle('is-visible', Boolean(copy));\n    }\n\n    function getUpcomingDates(retreat) {\n      if (!Array.isArray(retreat.dates)) return [];\n\n      return retreat.dates.map(function (schedule) {\n        var start = parseApiDate(schedule.start);\n        return { schedule: schedule, start: start, end: parseApiDate(schedule.end) || start };\n      }).filter(function (item) {\n        return item.start && item.end && item.end >= today;\n      }).sort(function (a, b) {\n        return a.start - b.start;\n      });\n    }\n\n    function render(retreats) {\n      var schedules = [];\n\n      retreats.forEach(function (retreat) {\n        getUpcomingDates(retreat).forEach(function (item) {\n          schedules.push({\n            name: retreat.name || '',\n            location: retreat.location || '',\n            url: retreat.url || '',\n            start: item.start,\n            end: item.end,\n            limited: item.schedule.limited === true\n          });\n        });\n      });\n\n      schedules.sort(function (a, b) {\n        return a.start - b.start;\n      });\n\n      var planningMonths = [];\n\n      for (var index = 0; index < 9; index += 1) {\n        planningMonths.push(\n          monthKey(new Date(today.getFullYear(), today.getMonth() + index, 1))\n        );\n      }\n\n      var counts = {};\n\n      schedules.forEach(function (schedule) {\n        var key = monthKey(schedule.start);\n        counts[key] = (counts[key] || 0) + 1;\n      });\n\n      var selectedMonth = planningMonths.filter(function (key) {\n        return counts[key] > 0;\n      })[0] || null;\n\n      var monthFormatter = new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' });\n      var shortMonthFormatter = new Intl.DateTimeFormat(locale, { month: 'short' });\n      var dayFormatter = new Intl.DateTimeFormat(locale, { day: '2-digit' });\n\n      function renderMonthButtons() {\n        monthStrip.replaceChildren();\n\n        planningMonths.forEach(function (key) {\n          var count = counts[key] || 0;\n          var parts = key.split('-');\n          var monthDate = new Date(Number(parts[0]), Number(parts[1]) - 1, 1);\n\n          var button = document.createElement('button');\n          button.type = 'button';\n          button.className = 'vrc__month';\n          button.disabled = count === 0;\n          button.setAttribute('aria-pressed', String(selectedMonth === key));\n\n          if (selectedMonth === key) button.classList.add('is-active');\n\n          var monthName = document.createElement('span');\n          monthName.className = 'vrc__month-name';\n          monthName.textContent = shortMonthFormatter.format(monthDate).toLocaleUpperCase(locale);\n\n          var year = document.createElement('span');\n          year.className = 'vrc__month-year';\n          year.textContent = parts[0];\n\n          var countElement = document.createElement('span');\n          countElement.className = 'vrc__month-count';\n          countElement.textContent = count\n            ? count + ' ' + (count === 1 ? text.date : text.dates)\n            : text.noDates;\n\n          button.append(monthName, year, countElement);\n\n          if (count) {\n            button.addEventListener('click', function () {\n              selectedMonth = key;\n              renderMonthButtons();\n              renderRows();\n            });\n          }\n\n          monthStrip.appendChild(button);\n        });\n      }\n\n      function renderRows() {\n        list.replaceChildren();\n\n        if (!selectedMonth) {\n          resultsTitle.textContent = text.available.toLocaleUpperCase(locale);\n          showMessage(text.noMonth);\n          return;\n        }\n\n        var matches = schedules.filter(function (schedule) {\n          return monthKey(schedule.start) === selectedMonth;\n        });\n\n        var parts = selectedMonth.split('-');\n        var monthDate = new Date(Number(parts[0]), Number(parts[1]) - 1, 1);\n\n        resultsTitle.textContent = monthFormatter.format(monthDate).toLocaleUpperCase(locale);\n        showMessage(matches.length ? '' : text.noMonth);\n\n        matches.forEach(function (schedule) {\n          var row = document.createElement('article');\n          row.className = 'vrc__item';\n\n          var date = document.createElement('span');\n          date.className = 'vrc__date';\n\n          var day = document.createElement('span');\n          day.className = 'vrc__day';\n          day.textContent = dayFormatter.format(schedule.start);\n\n          var month = document.createElement('span');\n          month.className = 'vrc__item-month';\n          month.textContent = shortMonthFormatter.format(schedule.start).toLocaleUpperCase(locale);\n\n          date.append(day, month);\n\n          var retreatUrl = safeUrl(schedule.url);\n          var name = document.createElement(retreatUrl ? 'a' : 'span');\n          name.className = 'vrc__name';\n          name.textContent = schedule.name;\n          if (retreatUrl) name.href = retreatUrl;\n\n          var location = document.createElement('span');\n          location.className = 'vrc__location';\n          location.textContent = schedule.location;\n\n          row.append(date, name, location);\n\n          if (schedule.limited) {\n            var badge = document.createElement('span');\n            badge.className = 'vrc__status';\n            badge.textContent = text.limited;\n            row.appendChild(badge);\n          }\n\n          list.appendChild(row);\n        });\n      }\n\n      renderMonthButtons();\n      renderRows();\n    }\n\n    showMessage(text.loading);\n\n    loadRetreats(apiUrl, language).then(render).catch(function (error) {\n      console.error('Retreat calendar:', error);\n      monthStrip.replaceChildren();\n      list.replaceChildren();\n      showMessage(text.error);\n    });\n  }\n\n  function init() {\n    Array.prototype.forEach.call(document.querySelectorAll('[data-vrc]'), initCalendar);\n  }\n\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', init);\n  } else {\n    init();\n  }\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-2cc28ec e-flex e-con-boxed e-con e-parent\" data-id=\"2cc28ec\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-218e7d3 elementor-widget elementor-widget-html\" data-id=\"218e7d3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Elementor setup\n  1. Paste this entire file into ONE Elementor HTML widget.\n  2. Paste elementor-dynamic-retreat-list.css into the same widget's\n     Advanced > Custom CSS field.\n  3. The REST endpoint can be changed with the data-api-url attribute below.\n-->\n\n<section class=\"vdrl\" id=\"retreats\" data-vdrl=\"\" data-api-url=\"\/wp-json\/retreats\/v1\/all\" aria-label=\"Upcoming Vietnam Detox retreats\">\n  <div class=\"vdrl__wrap\">\n    <div class=\"vdrl__status\" data-vdrl-status=\"\" role=\"status\" aria-live=\"polite\">\n      <span class=\"vdrl__spinner\" aria-hidden=\"true\"><\/span>\n      <span data-vdrl-status-text=\"\">Kommende Retreats werden geladen\u2026<\/span>\n    <\/div>\n\n    <div class=\"vdrl__grid\" data-vdrl-grid=\"\" aria-busy=\"true\" aria-label=\"Retreats\">\n      <article class=\"vdrl__skeleton\" aria-hidden=\"true\">\n        <div class=\"vdrl__skeleton-image\"><\/div>\n        <div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div>\n      <\/article>\n      <article class=\"vdrl__skeleton\" aria-hidden=\"true\">\n        <div class=\"vdrl__skeleton-image\"><\/div>\n        <div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div>\n      <\/article>\n      <article class=\"vdrl__skeleton\" aria-hidden=\"true\">\n        <div class=\"vdrl__skeleton-image\"><\/div>\n        <div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div>\n      <\/article>\n    <\/div>\n\n    <noscript>\n      <p class=\"vdrl__noscript\">Zum Laden des aktuellen Retreat-Plans ist JavaScript erforderlich.<\/p>\n    <\/noscript>\n  <\/div>\n<\/section>\n\n<script>\n(function() {\n  'use strict';\n\n  function getSharedRetreatApi() {\n    var shared = window.VietnamDetoxRetreatAPI = window.VietnamDetoxRetreatAPI || {};\n    shared.entries = shared.entries || {};\n    shared.version = shared.version || 0;\n\n    if (typeof shared.key !== 'function') {\n      shared.key = function(url) {\n        return new URL(url, window.location.href).href;\n      };\n    }\n\n    if (typeof shared.load !== 'function') {\n      shared.load = function(url, force) {\n        var key = shared.key(url);\n        if (!force && shared.entries[key]) return shared.entries[key].promise;\n\n        var controller = typeof AbortController === 'function' ? new AbortController() : null;\n        var timedOut = false;\n        var timeoutId = window.setTimeout(function() {\n          timedOut = true;\n          if (controller) controller.abort();\n        }, 15000);\n\n        var entry = {};\n        entry.promise = fetch(key, {\n          method: 'GET',\n          credentials: 'same-origin',\n          headers: { Accept: 'application\/json' },\n          signal: controller ? controller.signal : undefined\n        }).then(function(response) {\n          if (!response.ok) throw new Error('API request failed with status ' + response.status);\n          return response.json();\n        }).then(function(payload) {\n          var items = Array.isArray(payload)\n            ? payload\n            : Array.isArray(payload.retreats)\n              ? payload.retreats\n              : Array.isArray(payload.data)\n                ? payload.data\n                : null;\n\n          if (!items) throw new Error('The API returned an unexpected response shape.');\n\n          var result = { key: key, items: items, version: ++shared.version };\n          entry.result = result;\n          window.dispatchEvent(new CustomEvent('vietnamdetox:retreats-loaded', { detail: result }));\n          return result;\n        }).catch(function(error) {\n          if (timedOut) error.vietnamDetoxTimedOut = true;\n          throw error;\n        }).finally(function() {\n          window.clearTimeout(timeoutId);\n        });\n\n        shared.entries[key] = entry;\n        return entry.promise;\n      };\n    }\n\n    return shared;\n  }\n\n  var roots = document.querySelectorAll('[data-vdrl]');\n\n  var profiles = {\n    ANLYT: {\n      accent: '#C43010',\n      tagFallback: 'Y\u00ean T\u1eed',\n      chips: ['Quiet', 'Elevated', 'Deeply reflective'],\n      summary: 'A mountain retreat for guests seeking peace, structure and inner clarity.',\n      bestFor: 'Reflection, mindfulness and a more contemplative reset.',\n      buttonLabel: 'View An Retreat'\n    },\n    AWVH: {\n      accent: '#E06420',\n      tagFallback: 'Hu\u1ebf',\n      chips: ['Thermal', 'Restorative', 'Nature-led'],\n      summary: 'A restorative Hu\u1ebf retreat shaped by mineral springs, quiet nature and a softer pace of recovery.',\n      bestFor: 'Guests seeking deep rest, warm water therapies and a calmer nervous system.',\n      buttonLabel: 'View Y\u00ean Retreat'\n    },\n    'HOI-FRVDAD': {\n      accent: '#D75A20',\n      tagFallback: 'Da Nang',\n      chips: ['Coastal', 'Structured', 'Supported return'],\n      summary: 'A five-day guided fasting and coastal wellness retreat designed as a supported pause with a clear purpose.',\n      bestFor: 'Guests seeking steadier energy, practical guidance and a calmer relationship with food, rest and attention.',\n      buttonLabel: 'View H\u1ed3i Retreat'\n    },\n    PRIVATE: {\n      accent: '#1E3D20',\n      tagFallback: 'Private',\n      chips: ['Flexible', 'Discreet', 'Individual support'],\n      summary: 'A bespoke fasting retreat for guests who need privacy, flexibility and a more individual rhythm.',\n      bestFor: 'Couples, families, private groups and guests who prefer a premium private setting.',\n      buttonLabel: 'Explore Private Retreats',\n      byRequest: true\n    },\n    DEFAULT: {\n      accent: '#1E3D20',\n      tagFallback: 'Vietnam Detox',\n      chips: [],\n      summary: '',\n      bestFor: '',\n      buttonLabel: 'View Retreat'\n    }\n  };\n\n  var translations = {\n    en: {\n      locale: 'en-GB', regionLabel: 'Upcoming Vietnam Detox retreats', gridLabel: 'Retreats',\n      loading: 'Loading upcoming retreats\u2026', retry: 'Try again',\n      empty: 'No retreats are currently available. Please check back soon.',\n      timeout: 'The retreat schedule took too long to load. Please try again.',\n      error: 'We could not load the current retreat schedule. Please try again.',\n      unsupported: 'This browser cannot load the current retreat schedule.',\n      retreatName: 'Vietnam Detox Retreat', summary: 'A guided Vietnam Detox fasting retreat.',\n      datePending: 'Date to be confirmed', limited: 'Limited places', open: 'Open',\n      customDates: 'Custom dates and setting', pricingPage: 'Pricing on retreat page',\n      fromPrice: 'From VND {price}', perPerson: 'per person', byRequest: 'By Request',\n      multipleDates: 'Multiple dates available', nextAvailable: 'Next date available', datesPending: 'Dates to be announced',\n      bestFor: 'Best for', availability: 'Availability', availableByRequest: 'Available by request.',\n      nextDate: 'Next date', otherDate: 'View other date', otherDates: 'View other dates', hideDates: 'Hide other dates',\n      newDates: 'New dates will be announced soon.', profiles: {}\n    },\n    vi: {\n      locale: 'vi-VN', regionLabel: 'C\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng Vietnam Detox s\u1eafp t\u1edbi', gridLabel: 'C\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng',\n      loading: '\u0110ang t\u1ea3i c\u00e1c kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng s\u1eafp t\u1edbi\u2026', retry: 'Th\u1eed l\u1ea1i',\n      empty: 'Hi\u1ec7n ch\u01b0a c\u00f3 kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng n\u00e0o. Vui l\u00f2ng quay l\u1ea1i sau.',\n      timeout: 'T\u1ea3i l\u1ecbch ngh\u1ec9 d\u01b0\u1ee1ng m\u1ea5t qu\u00e1 nhi\u1ec1u th\u1eddi gian. Vui l\u00f2ng th\u1eed l\u1ea1i.',\n      error: 'Kh\u00f4ng th\u1ec3 t\u1ea3i l\u1ecbch ngh\u1ec9 d\u01b0\u1ee1ng hi\u1ec7n t\u1ea1i. Vui l\u00f2ng th\u1eed l\u1ea1i.',\n      unsupported: 'Tr\u00ecnh duy\u1ec7t n\u00e0y kh\u00f4ng th\u1ec3 t\u1ea3i l\u1ecbch ngh\u1ec9 d\u01b0\u1ee1ng hi\u1ec7n t\u1ea1i.',\n      retreatName: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng Vietnam Detox', summary: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng nh\u1ecbn \u0103n c\u00f3 h\u01b0\u1edbng d\u1eabn c\u1ee7a Vietnam Detox.',\n      datePending: 'Ng\u00e0y s\u1ebd \u0111\u01b0\u1ee3c x\u00e1c nh\u1eadn', limited: 'S\u1ed1 ch\u1ed7 c\u00f3 h\u1ea1n', open: 'C\u00f2n ch\u1ed7',\n      customDates: 'Ng\u00e0y v\u00e0 \u0111\u1ecba \u0111i\u1ec3m theo y\u00eau c\u1ea7u', pricingPage: 'Xem gi\u00e1 t\u1ea1i trang kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng',\n      fromPrice: 'T\u1eeb {price} VND', perPerson: 'm\u1ed7i ng\u01b0\u1eddi', byRequest: 'Theo y\u00eau c\u1ea7u',\n      multipleDates: 'C\u00f3 nhi\u1ec1u l\u1ecbch kh\u1edfi h\u00e0nh', nextAvailable: '\u0110\u00e3 c\u00f3 l\u1ecbch ti\u1ebfp theo', datesPending: 'L\u1ecbch s\u1ebd \u0111\u01b0\u1ee3c c\u00f4ng b\u1ed1',\n      bestFor: 'Ph\u00f9 h\u1ee3p v\u1edbi', availability: 'L\u1ecbch t\u1ed5 ch\u1ee9c', availableByRequest: 'T\u1ed5 ch\u1ee9c theo y\u00eau c\u1ea7u.',\n      nextDate: 'L\u1ecbch ti\u1ebfp theo', otherDate: 'Xem l\u1ecbch kh\u00e1c', otherDates: 'Xem c\u00e1c l\u1ecbch kh\u00e1c', hideDates: '\u1ea8n c\u00e1c l\u1ecbch kh\u00e1c',\n      newDates: 'L\u1ecbch m\u1edbi s\u1ebd s\u1edbm \u0111\u01b0\u1ee3c c\u00f4ng b\u1ed1.',\n      profiles: {\n        ANLYT: {\n          chips: ['Y\u00ean t\u0129nh', 'Gi\u1eefa n\u00fai r\u1eebng', 'L\u1eafng \u0111\u1ecdng'],\n          summary: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng gi\u1eefa n\u00fai r\u1eebng d\u00e0nh cho nh\u1eefng ai t\u00ecm ki\u1ebfm s\u1ef1 b\u00ecnh y\u00ean, n\u1ebfp s\u1ed1ng \u0111i\u1ec1u \u0111\u1ed9 v\u00e0 t\u00e2m tr\u00ed s\u00e1ng su\u1ed1t.',\n          bestFor: 'Chi\u00eam nghi\u1ec7m, th\u1ef1c h\u00e0nh ch\u00e1nh ni\u1ec7m v\u00e0 t\u00ecm l\u1ea1i s\u1ef1 c\u00e2n b\u1eb1ng trong t\u0129nh l\u1eb7ng.',\n          buttonLabel: 'Xem kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng An'\n        },\n        AWVH: {\n          chips: ['Su\u1ed1i kho\u00e1ng n\u00f3ng', 'Ph\u1ee5c h\u1ed3i', 'G\u1ea7n g\u0169i thi\u00ean nhi\u00ean'],\n          summary: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng ph\u1ee5c h\u1ed3i t\u1ea1i Hu\u1ebf v\u1edbi su\u1ed1i kho\u00e1ng, thi\u00ean nhi\u00ean y\u00ean b\u00ecnh v\u00e0 nh\u1ecbp ngh\u1ec9 ng\u01a1i nh\u1eb9 nh\u00e0ng.',\n          bestFor: 'Nh\u1eefng ai c\u1ea7n ngh\u1ec9 ng\u01a1i s\u00e2u, t\u1eadn h\u01b0\u1edfng li\u1ec7u ph\u00e1p n\u01b0\u1edbc \u1ea5m v\u00e0 th\u01b0 gi\u00e3n h\u1ec7 th\u1ea7n kinh.',\n          buttonLabel: 'Xem kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng Y\u00ean'\n        },\n        'HOI-FRVDAD': {\n          tagFallback: '\u0110\u00e0 N\u1eb5ng', chips: ['Ven bi\u1ec3n', 'C\u00f3 l\u1ed9 tr\u00ecnh', '\u0110\u1ed3ng h\u00e0nh sau kh\u00f3a'],\n          summary: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng nh\u1ecbn \u0103n c\u00f3 h\u01b0\u1edbng d\u1eabn k\u00e9o d\u00e0i n\u0103m ng\u00e0y b\u00ean b\u1edd bi\u1ec3n, \u0111\u01b0\u1ee3c thi\u1ebft k\u1ebf nh\u01b0 m\u1ed9t kho\u1ea3ng d\u1eebng c\u00f3 ch\u1ee7 \u0111\u00edch.',\n          bestFor: 'Nh\u1eefng ai mu\u1ed1n l\u1ea5y l\u1ea1i n\u0103ng l\u01b0\u1ee3ng \u1ed5n \u0111\u1ecbnh, c\u00f3 h\u01b0\u1edbng d\u1eabn th\u1ef1c t\u1ebf v\u00e0 m\u1ed1i quan h\u1ec7 nh\u1eb9 nh\u00e0ng h\u01a1n v\u1edbi \u0103n u\u1ed1ng, ngh\u1ec9 ng\u01a1i v\u00e0 s\u1ef1 t\u1eadp trung.',\n          buttonLabel: 'Xem kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng H\u1ed3i'\n        },\n        PRIVATE: {\n          tagFallback: 'Ri\u00eang t\u01b0', chips: ['Linh ho\u1ea1t', 'K\u00edn \u0111\u00e1o', 'H\u1ed7 tr\u1ee3 c\u00e1 nh\u00e2n'],\n          summary: 'Kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng nh\u1ecbn \u0103n \u0111\u01b0\u1ee3c thi\u1ebft k\u1ebf ri\u00eang cho nh\u1eefng ai c\u1ea7n s\u1ef1 ri\u00eang t\u01b0, linh ho\u1ea1t v\u00e0 nh\u1ecbp sinh ho\u1ea1t ph\u00f9 h\u1ee3p.',\n          bestFor: 'C\u1eb7p \u0111\u00f4i, gia \u0111\u00ecnh, nh\u00f3m ri\u00eang v\u00e0 nh\u1eefng ai y\u00eau th\u00edch kh\u00f4ng gian ngh\u1ec9 d\u01b0\u1ee1ng ri\u00eang t\u01b0 cao c\u1ea5p.',\n          buttonLabel: 'Kh\u00e1m ph\u00e1 kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng ri\u00eang t\u01b0'\n        },\n        DEFAULT: { buttonLabel: 'Xem kh\u00f3a ngh\u1ec9 d\u01b0\u1ee1ng' }\n      }\n    },\n    de: {\n      locale: 'de-DE', regionLabel: 'Kommende Retreats von Vietnam Detox', gridLabel: 'Retreats',\n      loading: 'Kommende Retreats werden geladen\u2026', retry: 'Erneut versuchen',\n      empty: 'Derzeit sind keine Retreats verf\u00fcgbar. Bitte schauen Sie bald wieder vorbei.',\n      timeout: 'Der Retreat-Kalender hat zu lange zum Laden gebraucht. Bitte versuchen Sie es erneut.',\n      error: 'Der aktuelle Retreat-Kalender konnte nicht geladen werden. Bitte versuchen Sie es erneut.',\n      unsupported: 'Dieser Browser kann den aktuellen Retreat-Kalender nicht laden.',\n      retreatName: 'Vietnam Detox Retreat', summary: 'Ein begleitetes Fasten-Retreat von Vietnam Detox.',\n      datePending: 'Termin wird noch best\u00e4tigt', limited: 'Begrenzte Pl\u00e4tze', open: 'Pl\u00e4tze frei',\n      customDates: 'Termine und Ort nach Wunsch', pricingPage: 'Preise auf der Retreat-Seite',\n      fromPrice: 'Ab {price} VND', perPerson: 'pro Person', byRequest: 'Auf Anfrage',\n      multipleDates: 'Mehrere Termine verf\u00fcgbar', nextAvailable: 'N\u00e4chster Termin verf\u00fcgbar', datesPending: 'Termine folgen',\n      bestFor: 'Ideal f\u00fcr', availability: 'Verf\u00fcgbarkeit', availableByRequest: 'Auf Anfrage verf\u00fcgbar.',\n      nextDate: 'N\u00e4chster Termin', otherDate: 'Weiteren Termin ansehen', otherDates: 'Weitere Termine ansehen', hideDates: 'Weitere Termine ausblenden',\n      newDates: 'Neue Termine werden bald bekannt gegeben.',\n      profiles: {\n        ANLYT: {\n          chips: ['Ruhig', 'In den Bergen', 'Tief besinnlich'],\n          summary: 'Ein Retreat in den Bergen f\u00fcr G\u00e4ste, die Ruhe, Struktur und innere Klarheit suchen.',\n          bestFor: 'Besinnung, Achtsamkeit und einen kontemplativeren Neustart.',\n          buttonLabel: 'Zum An Retreat'\n        },\n        AWVH: {\n          chips: ['Thermalquellen', 'Erholsam', 'Naturnah'],\n          summary: 'Ein erholsames Retreat bei Hu\u1ebf mit Mineralquellen, stiller Natur und einem sanfteren Erholungstempo.',\n          bestFor: 'G\u00e4ste, die tiefe Ruhe, warme Wasseranwendungen und ein entspannteres Nervensystem suchen.',\n          buttonLabel: 'Zum Y\u00ean Retreat'\n        },\n        'HOI-FRVDAD': {\n          chips: ['An der K\u00fcste', 'Strukturiert', 'Begleitete R\u00fcckkehr'],\n          summary: 'Ein f\u00fcnft\u00e4giges, begleitetes Fasten- und Wellness-Retreat an der K\u00fcste, gedacht als unterst\u00fctzte Auszeit mit klarem Ziel.',\n          bestFor: 'G\u00e4ste, die stabilere Energie, praktische Anleitung und einen entspannteren Umgang mit Essen, Ruhe und Aufmerksamkeit suchen.',\n          buttonLabel: 'Zum H\u1ed3i Retreat'\n        },\n        PRIVATE: {\n          tagFallback: 'Privat', chips: ['Flexibel', 'Diskret', 'Individuelle Betreuung'],\n          summary: 'Ein ma\u00dfgeschneidertes Fasten-Retreat f\u00fcr G\u00e4ste, die Privatsph\u00e4re, Flexibilit\u00e4t und einen individuelleren Rhythmus brauchen.',\n          bestFor: 'Paare, Familien, private Gruppen und G\u00e4ste, die ein exklusives privates Umfeld bevorzugen.',\n          buttonLabel: 'Private Retreats entdecken'\n        },\n        DEFAULT: { buttonLabel: 'Retreat ansehen' }\n      }\n    }\n  };\n\n  function createElement(tag, className, text) {\n    var element = document.createElement(tag);\n    if (className) element.className = className;\n    if (typeof text === 'string') element.textContent = text;\n    return element;\n  }\n\n  function cleanText(value) {\n    if (value === null || typeof value === 'undefined') return '';\n    var parser = new DOMParser();\n    var documentValue = parser.parseFromString(String(value), 'text\/html');\n    return (documentValue.body.textContent || '').replace(\/\\s+\/g, ' ').trim();\n  }\n\n  function summarize(value, maximum) {\n    var text = cleanText(value);\n    if (!text || text.length <= maximum) return text;\n    var shortened = text.slice(0, maximum + 1).replace(\/\\s+\\S*$\/, '').trim();\n    return shortened + '\u2026';\n  }\n\n  function safeUrl(value, allowImageData) {\n    if (!value) return '';\n    try {\n      var parsed = new URL(String(value), window.location.href);\n      var allowed = parsed.protocol === 'http:' || parsed.protocol === 'https:';\n      if (allowImageData && parsed.protocol === 'data:') allowed = \/^data:image\\\/\/i.test(String(value));\n      return allowed ? parsed.href : '';\n    } catch (error) {\n      return '';\n    }\n  }\n\n  function parseApiDate(value) {\n    if (!value) return null;\n    var parts = String(value).match(\/^(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})$\/);\n    if (!parts) return null;\n    var date = new Date(Number(parts[3]), Number(parts[1]) - 1, Number(parts[2]), 12, 0, 0, 0);\n    return Number.isNaN(date.getTime()) ? null : date;\n  }\n\n  function formatDateRange(item, copy) {\n    var start = item._start;\n    var end = item._end;\n    if (!start || !end) return copy.datePending;\n    var formatter = new Intl.DateTimeFormat(copy.locale, { day: 'numeric', month: 'long', year: 'numeric' });\n    return typeof formatter.formatRange === 'function' && start <= end\n      ? formatter.formatRange(start, end)\n      : formatter.format(start) + '\u2013' + formatter.format(end);\n  }\n\n  function availabilityText(item, copy) {\n    return item && item.limited === true ? copy.limited : copy.open;\n  }\n\n  \/\/ Card order follows the design, not the order the API happens to return.\n  var displayOrder = ['ANLYT', 'HOI-FRVDAD', 'AWVH', 'PRIVATE'];\n\n  function profileKeyFor(item) {\n    var sku = cleanText(item && item.retreat_sku).toUpperCase();\n    var name = cleanText(item && item.name);\n    return item && item.is_private === true ? 'PRIVATE'\n      : profiles[sku] ? sku : \/private|corporate\/i.test(name) ? 'PRIVATE' : 'DEFAULT';\n  }\n\n  function profileFor(item, copy) {\n    var key = profileKeyFor(item);\n    return Object.assign({}, profiles[key], copy.profiles[key] || {});\n  }\n\n  \/\/ Anything not named in displayOrder keeps its API order, after the known cards.\n  function orderItems(items) {\n    return items.map(function(item, index) {\n      var rank = displayOrder.indexOf(profileKeyFor(item));\n      return { item: item, index: index, rank: rank < 0 ? displayOrder.length : rank };\n    }).sort(function(a, b) {\n      return a.rank - b.rank || a.index - b.index;\n    }).map(function(entry) {\n      return entry.item;\n    });\n  }\n\n  function upcomingDates(item, byRequest) {\n    if (byRequest || !Array.isArray(item.dates)) return [];\n\n    var today = new Date();\n    today.setHours(0, 0, 0, 0);\n\n    return item.dates.map(function(date) {\n      var normalized = Object.assign({}, date);\n      normalized._start = parseApiDate(date.start);\n      normalized._end = parseApiDate(date.end) || normalized._start;\n      return normalized;\n    }).filter(function(date) {\n      return !date._end || date._end >= today;\n    }).sort(function(a, b) {\n      if (!a._start && !b._start) return 0;\n      if (!a._start) return 1;\n      if (!b._start) return -1;\n      return a._start - b._start;\n    });\n  }\n\n  function priceDetails(item, byRequest, copy) {\n    if (byRequest) return { main: copy.customDates, suffix: '' };\n    if (!Array.isArray(item.packages) || !item.packages.length) {\n      return { main: copy.pricingPage, suffix: '' };\n    }\n\n    var perPersonPrices = item.packages.map(function(packageItem) {\n      var price = Number(packageItem.price);\n      var people = Math.max(1, Number(packageItem.person) || 1);\n      return Number.isFinite(price) && price > 0 ? price \/ people : null;\n    }).filter(function(price) {\n      return price !== null;\n    });\n\n    if (!perPersonPrices.length) return { main: copy.pricingPage, suffix: '' };\n\n    var lowest = Math.round(Math.min.apply(Math, perPersonPrices));\n    return {\n      main: copy.fromPrice.replace('{price}', new Intl.NumberFormat(copy.locale, { maximumFractionDigits: 0 }).format(lowest)),\n      suffix: copy.perPerson\n    };\n  }\n\n  function appendLabelValue(parent, labelText, valueText) {\n    var line = createElement('div', 'vdrl__date-line');\n    line.appendChild(createElement('span', 'vdrl__meta-label', labelText));\n    line.appendChild(createElement('span', 'vdrl__date-value', valueText));\n    parent.appendChild(line);\n  }\n\n  function createCard(item, instanceId, cardIndex, copy) {\n    var profile = profileFor(item, copy);\n    var byRequest = profile.byRequest === true;\n    var dates = upcomingDates(item, byRequest);\n    var name = cleanText(item.name) || copy.retreatName;\n    var location = cleanText(item.location) || profile.tagFallback;\n    var linkUrl = safeUrl(item.url, false);\n    var imageUrl = safeUrl(item.image, true);\n    var summary = summarize(item.description, 210) || profile.summary || copy.summary;\n    var price = priceDetails(item, byRequest, copy);\n    var card = createElement('article', 'vdrl__card');\n    var media = createElement('div', 'vdrl__media');\n\n    card.style.setProperty('--vdrl-card-accent', profile.accent);\n\n    if (imageUrl) {\n      var image = createElement('img', 'vdrl__image');\n      image.src = imageUrl;\n      image.alt = name;\n      image.loading = 'lazy';\n      image.decoding = 'async';\n      image.addEventListener('error', function() {\n        media.classList.add('is-missing');\n        image.remove();\n      }, { once: true });\n      media.appendChild(image);\n    } else {\n      media.classList.add('is-missing');\n    }\n\n    var badgeText = byRequest\n      ? copy.byRequest\n      : dates.length > 1\n        ? copy.multipleDates\n        : dates.length === 1\n          ? copy.nextAvailable\n          : copy.datesPending;\n\n    media.appendChild(createElement('div', 'vdrl__badge', badgeText));\n    card.appendChild(media);\n    card.appendChild(createElement('div', 'vdrl__accent'));\n\n    var body = createElement('div', 'vdrl__body');\n    var top = createElement('div', 'vdrl__top');\n    top.appendChild(createElement('span', 'vdrl__tag', location));\n\n    var heading = createElement('h3', 'vdrl__title');\n    if (linkUrl) {\n      var headingLink = createElement('a', '', name);\n      headingLink.href = linkUrl;\n      heading.appendChild(headingLink);\n    } else {\n      heading.textContent = name;\n    }\n    top.appendChild(heading);\n\n    if (profile.chips.length) {\n      var chipRow = createElement('div', 'vdrl__chips');\n      profile.chips.forEach(function(chipText) {\n        chipRow.appendChild(createElement('span', 'vdrl__chip', chipText));\n      });\n      top.appendChild(chipRow);\n    }\n    body.appendChild(top);\n\n    var summaryBlock = createElement('div', 'vdrl__summary');\n    summaryBlock.appendChild(createElement('p', '', summary));\n    body.appendChild(summaryBlock);\n\n    if (profile.bestFor) {\n      var meta = createElement('div', 'vdrl__meta');\n      meta.appendChild(createElement('span', 'vdrl__meta-label', copy.bestFor));\n      meta.appendChild(createElement('p', 'vdrl__meta-value', profile.bestFor));\n      body.appendChild(meta);\n    }\n\n    var dateBlock = createElement('div', 'vdrl__dates');\n    if (byRequest) {\n      appendLabelValue(dateBlock, copy.availability, copy.availableByRequest);\n    } else if (dates.length) {\n      appendLabelValue(dateBlock, copy.nextDate, formatDateRange(dates[0], copy) + ' \u00b7 ' + availabilityText(dates[0], copy));\n\n      var earlyBirdNote = cleanText(item.earlyBirdNote);\n      if (earlyBirdNote) dateBlock.appendChild(createElement('p', 'vdrl__early-bird', earlyBirdNote));\n\n      if (dates.length > 1) {\n        var itemId = String(item.id || cardIndex).replace(\/[^a-zA-Z0-9_-]\/g, '');\n        var datePanelId = 'vdrl-dates-' + instanceId + '-' + itemId;\n        var extras = createElement('div', 'vdrl__extra-dates is-collapsed');\n        extras.id = datePanelId;\n        extras.setAttribute('aria-hidden', 'true');\n        var list = createElement('ul');\n\n        dates.slice(1).forEach(function(date) {\n          list.appendChild(createElement('li', '', formatDateRange(date, copy) + ' \u00b7 ' + availabilityText(date, copy)));\n        });\n\n        extras.appendChild(list);\n        dateBlock.appendChild(extras);\n\n        var toggleLabel = dates.length === 2 ? copy.otherDate : copy.otherDates;\n        var toggle = createElement('button', 'vdrl__dates-toggle', toggleLabel);\n        toggle.type = 'button';\n        toggle.setAttribute('aria-controls', datePanelId);\n        toggle.setAttribute('aria-expanded', 'false');\n        toggle.addEventListener('click', function() {\n          var isCollapsed = extras.classList.toggle('is-collapsed');\n          extras.setAttribute('aria-hidden', String(isCollapsed));\n          toggle.setAttribute('aria-expanded', String(!isCollapsed));\n          toggle.firstChild.nodeValue = isCollapsed ? toggleLabel : copy.hideDates;\n        });\n        dateBlock.appendChild(toggle);\n      }\n    } else {\n      appendLabelValue(dateBlock, copy.availability, copy.newDates);\n    }\n    body.appendChild(dateBlock);\n\n    var footer = createElement('div', 'vdrl__footer');\n    var priceElement = createElement('div', 'vdrl__price', price.main);\n    if (price.suffix) priceElement.appendChild(createElement('span', '', ' ' + price.suffix));\n    footer.appendChild(priceElement);\n\n    if (linkUrl) {\n      var button = createElement('a', 'vdrl__button', profile.buttonLabel);\n      button.href = linkUrl;\n      footer.appendChild(button);\n    }\n\n    body.appendChild(footer);\n    card.appendChild(body);\n    return card;\n  }\n\n  roots.forEach(function(root) {\n    if (root.dataset.vdrlReady === 'true') return;\n    root.dataset.vdrlReady = 'true';\n\n    window.__vdrlInstanceCounter = (window.__vdrlInstanceCounter || 0) + 1;\n    var instanceId = window.__vdrlInstanceCounter;\n    var grid = root.querySelector('[data-vdrl-grid]');\n    var status = root.querySelector('[data-vdrl-status]');\n    var endpoint = new URL(root.getAttribute('data-api-url') || '\/wp-json\/retreats\/v1\/all', window.location.href);\n    var language = String(endpoint.searchParams.get('lang') || document.documentElement.lang || 'en').toLowerCase().split(\/[-_]\/)[0];\n    if (language === 'vn') language = 'vi';\n    if (['en', 'vi', 'de'].indexOf(language) < 0) language = 'en';\n    endpoint.searchParams.set('lang', language);\n    var apiUrl = endpoint.href;\n    var copy = translations[language] || translations.en;\n    root.setAttribute('aria-label', copy.regionLabel);\n    grid.setAttribute('aria-label', copy.gridLabel);\n    var sharedApi = getSharedRetreatApi();\n    var apiKey = sharedApi.key(apiUrl);\n    var lastVersion = 0;\n\n    function showLoading() {\n      status.hidden = false;\n      status.className = 'vdrl__status';\n      status.setAttribute('role', 'status');\n      status.replaceChildren();\n      status.appendChild(createElement('span', 'vdrl__spinner'));\n      status.appendChild(createElement('span', '', copy.loading));\n      grid.hidden = false;\n      grid.setAttribute('aria-busy', 'true');\n      grid.innerHTML = '<article class=\"vdrl__skeleton\" aria-hidden=\"true\"><div class=\"vdrl__skeleton-image\"><\/div><div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div><\/article><article class=\"vdrl__skeleton\" aria-hidden=\"true\"><div class=\"vdrl__skeleton-image\"><\/div><div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div><\/article><article class=\"vdrl__skeleton\" aria-hidden=\"true\"><div class=\"vdrl__skeleton-image\"><\/div><div class=\"vdrl__skeleton-body\"><i><\/i><i><\/i><i><\/i><i><\/i><\/div><\/article>';\n    }\n\n    function showMessage(message, type, allowRetry) {\n      grid.replaceChildren();\n      grid.hidden = true;\n      grid.removeAttribute('aria-busy');\n      status.hidden = false;\n      status.className = 'vdrl__status vdrl__status--' + type;\n      status.setAttribute('role', type === 'error' ? 'alert' : 'status');\n      status.replaceChildren(createElement('p', '', message));\n\n      if (allowRetry) {\n        var retry = createElement('button', 'vdrl__retry', copy.retry);\n        retry.type = 'button';\n        retry.addEventListener('click', function() {\n          loadRetreats(true);\n        });\n        status.appendChild(retry);\n      }\n    }\n\n    function renderResult(result) {\n      if (!result || result.key !== apiKey || result.version === lastVersion) return;\n      lastVersion = result.version;\n      var items = orderItems(result.items || []);\n\n      if (!items.length) {\n        showMessage(copy.empty, 'empty', false);\n        return;\n      }\n\n      var fragment = document.createDocumentFragment();\n      items.forEach(function(item, index) {\n        fragment.appendChild(createCard(item || {}, instanceId, index, copy));\n      });\n\n      grid.replaceChildren(fragment);\n      grid.hidden = false;\n      grid.removeAttribute('aria-busy');\n      status.hidden = true;\n    }\n\n    function showLoadError(error) {\n      if (lastVersion) return;\n      if (error && error.vietnamDetoxTimedOut) {\n        showMessage(copy.timeout, 'error', true);\n        return;\n      }\n      showMessage(copy.error, 'error', true);\n    }\n\n    function loadRetreats(force) {\n      showLoading();\n\n      if (typeof window.fetch !== 'function') {\n        showMessage(copy.unsupported, 'error', false);\n        return;\n      }\n\n      sharedApi.load(apiUrl, force === true).then(renderResult).catch(showLoadError);\n    }\n\n    window.addEventListener('vietnamdetox:retreats-loaded', function(event) {\n      renderResult(event.detail);\n    });\n\n    loadRetreats(false);\n  });\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-69d4d43 e-flex e-con-boxed e-con e-parent\" data-id=\"69d4d43\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e2d25d2 elementor-widget elementor-widget-html\" data-id=\"e2d25d2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Copy this entire file into ONE Elementor HTML widget.\n  Put retreat-quiz.css into that widget's Advanced > Custom CSS field.\n  All visitor-facing text is HTML in this file, including the hidden quiz text near the\n  end, so WPML translates the whole quiz with the page. The script only holds the scoring.\n-->\n\n<div class=\"rdq\" data-retreat-quiz=\"\" data-api=\"\/wp-json\/retreats\/v1\/all\">\n  <div class=\"rdq-transition\">Nicht sicher, welches Retreat passt?<\/div>\n\n  <section class=\"rdq-intro\" aria-labelledby=\"rdq-intro-title\">\n    <div class=\"rdq-intro-grid\">\n      <div class=\"rdq-intro-copy\">\n        <span class=\"rdq-eyebrow\">RETREAT-ORIENTIERUNGSQUIZ<\/span>\n        <h3 id=\"rdq-intro-title\">\n Finden Sie das Retreat, das <em>zu Ihrer aktuellen Situation passt<\/em>.\n        <\/h3>\n        <p>\n Beantworten Sie ein paar ruhige Fragen zu Ihren aktuellen Vorlieben, dem gew\u00fcnschten\nRetreat-Rhythmus und der Art von Unterst\u00fctzung, die sich f\u00fcr Sie richtig anf\u00fchlt.\n        <\/p>\n        <p class=\"rdq-intro-note\">\n  Dauert etwa zwei Minuten. Keine Diagnose. Nur Orientierung.  \n        <\/p>\n      <\/div>\n\n      <div class=\"rdq-intro-divider\" aria-hidden=\"true\"><\/div>\n\n      <div class=\"rdq-intro-side\">\n        <span class=\"rdq-kicker\">IN ZWEI MINUTEN<\/span>\n        <ul class=\"rdq-list\">\n          <li><span class=\"rdq-bullet\" aria-hidden=\"true\"><\/span><span>Beantworten Sie 8 Fragen zu Ihren Pr\u00e4ferenzen<\/span><\/li>\n          <li><span class=\"rdq-bullet\" aria-hidden=\"true\"><\/span><span>Vergleichen Sie die verf\u00fcgbaren Retreat-Optionen<\/span><\/li>\n          <li><span class=\"rdq-bullet\" aria-hidden=\"true\"><\/span><span>Sehen Sie Live-Termine, Orte und Preise<\/span><\/li>\n        <\/ul>\n\n        <button class=\"rdq-btn rdq-btn-ember\" type=\"button\" data-rdq-open=\"\" aria-haspopup=\"dialog\" aria-controls=\"rdq-modal\" aria-expanded=\"false\">\n Retreat-Quiz starten\n        <\/button>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <div class=\"rdq-modal\" id=\"rdq-modal\" data-rdq-modal=\"\" aria-hidden=\"true\" hidden=\"\">\n\n    <div class=\"rdq-backdrop\" data-rdq-close=\"\" aria-hidden=\"true\"><\/div>\n\n    <section class=\"rdq-dialog\" data-rdq-dialog=\"\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"rdq-modal-title\" aria-describedby=\"rdq-modal-note\" tabindex=\"-1\">\n\n      <header class=\"rdq-topbar\">\n        <div>\n          <span class=\"rdq-modal-eyebrow\">RETREAT-QUIZ<\/span>\n          <h3 class=\"rdq-modal-title\" id=\"rdq-modal-title\">Finden Sie Ihr am besten passendes Retreat<\/h3>\n          <p class=\"rdq-modal-note\" id=\"rdq-modal-note\">Ein paar ruhige Fragen, die Ihnen helfen, mit mehr Klarheit zu w\u00e4hlen.<\/p>\n        <\/div>\n\n        <button type=\"button\" class=\"rdq-close\" data-rdq-close=\"\" aria-label=\"Close quiz\">\n \u00d7\n        <\/button>\n      <\/header>\n\n      <div class=\"rdq-main\">\n        <div class=\"rdq-progress\" data-rdq-progress=\"\" aria-live=\"polite\">\n          <div class=\"rdq-progress-text\" data-rdq-progress-text=\"\">Frage 1 von 8<\/div>\n          <div class=\"rdq-progress-track\" aria-hidden=\"true\">\n            <div class=\"rdq-progress-fill\" data-rdq-progress-fill=\"\"><\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"rdq-stage\" data-rdq-stage=\"\"><\/div>\n        <div class=\"rdq-result\" data-rdq-result=\"\" hidden=\"\"><\/div>\n      <\/div>\n    <\/section>\n  <\/div>\n\n  <!--\n    Quiz text read by the script below. Hidden from visitors; WPML translates it with the\n    rest of the page. Keep the order: questions and answers match the scores in the script\n    by position. Keep {count}, {current}, {total} and {name}; the script fills them in.\n  -->\n  <div class=\"rdq-copy\" data-rdq-copy=\"\" hidden=\"\">\n    <ol>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Wonach suchen Sie im Moment vor allem?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Innere Klarheit, Besinnung und Zeit, wieder zu mir selbst zu finden<\/li>\n          <li data-rdq-answer-text=\"\">Einen strukturierten, begleiteten Neustart mit praktischen Werkzeugen f\u00fcr zu Hause<\/li>\n          <li data-rdq-answer-text=\"\">Einen sanften ersten Schritt mit hei\u00dfen Quellen in einem ruhigen Resort<\/li>\n          <li data-rdq-answer-text=\"\">Ein privates Retreat, abgestimmt auf meine eigenen Bed\u00fcrfnisse oder meine Gruppe<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Welche Atmosph\u00e4re spricht Sie am meisten an?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Stille Berge, spirituelles Erbe und Raum f\u00fcr Kontemplation<\/li>\n          <li data-rdq-answer-text=\"\">Das Meer, Strandspazierg\u00e4nge und ein ruhiges Resort an der K\u00fcste<\/li>\n          <li data-rdq-answer-text=\"\">Nat\u00fcrliche hei\u00dfe Quellen, Kr\u00e4uterb\u00e4der und ruhiger Resort-Komfort<\/li>\n          <li data-rdq-answer-text=\"\">Ein diskreter Rahmen, ausgew\u00e4hlt f\u00fcr mich, meine Begleitung oder meine Gruppe<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Welche Art von Begleitung w\u00e4re f\u00fcr Sie am angenehmsten?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Genug Anleitung, mit viel Raum f\u00fcr Stille und Besinnung<\/li>\n          <li data-rdq-answer-text=\"\">Ein unterst\u00fctzendes Gruppenprogramm mit klarer Tagesstruktur<\/li>\n          <li data-rdq-answer-text=\"\">Ein sanftes, leicht zug\u00e4ngliches Programm in einem komfortablen Wellness-Ambiente<\/li>\n          <li data-rdq-answer-text=\"\">Individuelle Aufmerksamkeit und ein Format, das auf meine Situation zugeschnitten ist<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Welcher Retreat-Rhythmus f\u00fchlt sich f\u00fcr Sie am besten an?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Weit, ruhig und nach innen gerichtet<\/li>\n          <li data-rdq-answer-text=\"\">Strukturiert, anregend und sanft motivierend<\/li>\n          <li data-rdq-answer-text=\"\">Sanft, flie\u00dfend und ohne Druck<\/li>\n          <li data-rdq-answer-text=\"\">Flexibel und auf mein bevorzugtes Tempo abgestimmt<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Wie m\u00f6chten Sie das Retreat am liebsten erleben?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Als pers\u00f6nliche Reise nach innen an einem spirituell bedeutsamen Ort<\/li>\n          <li data-rdq-answer-text=\"\">Gemeinsam mit anderen, mit praktischer Anleitung, die ich zu Hause weiter nutzen kann<\/li>\n          <li data-rdq-answer-text=\"\">Als sanfte Wellness-Auszeit mit hei\u00dfen Quellen und Resort-Komfort<\/li>\n          <li data-rdq-answer-text=\"\">Privat, allein, als Paar, Familie oder kleine Gruppe<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Haben Sie schon einmal gefastet?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Ja, und mich zieht es zu einer tieferen, kontemplativen Erfahrung<\/li>\n          <li data-rdq-answer-text=\"\">Ein- oder zweimal, und ich sch\u00e4tze ein klar angeleitetes Gruppenprogramm<\/li>\n          <li data-rdq-answer-text=\"\">Nein, ich w\u00fcnsche mir eine sanfte, leicht zug\u00e4ngliche erste Erfahrung<\/li>\n          <li data-rdq-answer-text=\"\">Ich m\u00f6chte zuerst \u00fcber einen individuell abgestimmten Ansatz sprechen<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Was ist Ihnen beim Vergleich der Retreat-Optionen am wichtigsten?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Ein Ort, der Stille und innere Einkehr f\u00f6rdert<\/li>\n          <li data-rdq-answer-text=\"\">Ein klares, strukturiertes Programm am Meer, mit Begleitung f\u00fcr die R\u00fcckkehr in den Alltag<\/li>\n          <li data-rdq-answer-text=\"\">Eine sanfte Resort-Umgebung mit Anwendungen aus nat\u00fcrlichen hei\u00dfen Quellen<\/li>\n          <li data-rdq-answer-text=\"\">Privatsph\u00e4re, flexible Termine und ein Format, das auf mich zugeschnitten ist<\/li>\n        <\/ul>\n      <\/li>\n      <li data-rdq-question=\"\">\n        <p data-rdq-question-text=\"\">Welcher Satz beschreibt Ihre aktuelle Situation am besten?<\/p>\n        <ul>\n          <li data-rdq-answer-text=\"\">Ich brauche Raum, um mich selbst wieder zu h\u00f6ren<\/li>\n          <li data-rdq-answer-text=\"\">Ich w\u00fcnsche mir stabilere Energie und einen entspannteren Umgang mit Essen und Ruhe<\/li>\n          <li data-rdq-answer-text=\"\">Ich w\u00fcnsche mir einen sanften Neustart, der sich angenehm und machbar anf\u00fchlt<\/li>\n          <li data-rdq-answer-text=\"\">Ich w\u00fcnsche mir ein Retreat, das auf meine pers\u00f6nliche Situation abgestimmt ist<\/li>\n        <\/ul>\n      <\/li>\n    <\/ol>\n\n    <div data-rdq-profile=\"an\">\n      <p data-rdq-profile-name=\"\">\u201eAn\u201c Retreat im Legacy Y\u00ean T\u1eed MGallery<\/p>\n      <p data-rdq-profile-reason=\"\">Ihre Antworten deuten auf Stille, Besinnung und innere Neuausrichtung hin. Diese Retreat-Richtung k\u00f6nnte zu Ihnen passen, wenn Sie dem L\u00e4rm des Alltags entkommen und einen begleiteten Neustart in einer kontemplativen Berglandschaft erleben m\u00f6chten. <\/p>\n    <\/div>\n    <div data-rdq-profile=\"hoi\">\n      <p data-rdq-profile-name=\"\">H\u1ed3i Retreat im Fusion Resort & Villas Da Nang<\/p>\n      <p data-rdq-profile-reason=\"\">Ihre Antworten deuten auf einen strukturierten, gut begleiteten Neustart mit praktischer Unterst\u00fctzung hin. Diese Retreat-Richtung k\u00f6nnte zu Ihnen passen, wenn Ihnen ein klarer Tagesrhythmus am Meer, stabilere Energie und eine Anleitung, die Sie zu Hause weiter nutzen k\u00f6nnen, am wichtigsten sind. <\/p>\n    <\/div>\n    <div data-rdq-profile=\"yen\">\n      <p data-rdq-profile-name=\"\">Y\u00ean Retreat \u2013 Sanftes Fasten mit Vietnam Detox<\/p>\n      <p data-rdq-profile-reason=\"\">Ihre Antworten deuten auf einen sanfteren, leicht zug\u00e4nglichen Neustart in einer ruhigen Resort-Umgebung hin. Diese Retreat-Richtung k\u00f6nnte zu Ihnen passen, wenn Sie sich von nat\u00fcrlichen hei\u00dfen Quellen, Kr\u00e4uterb\u00e4dern und einem angenehmen ersten Schritt ins Fasten angesprochen f\u00fchlen. <\/p>\n    <\/div>\n    <div data-rdq-profile=\"private\">\n      <p data-rdq-profile-name=\"\">Private & Firmen-Retreats<\/p>\n      <p data-rdq-profile-reason=\"\">Ihre Antworten deuten auf Privatsph\u00e4re, Flexibilit\u00e4t und ein Retreat hin, das auf Ihre Situation zugeschnitten ist. Ein privates Format k\u00f6nnte zu Ihnen passen, wenn Ihnen Zeitpunkt, Gruppenzusammensetzung oder pers\u00f6nliche Betreuung wichtiger sind als die Teilnahme an einem festen \u00f6ffentlichen Termin. <\/p>\n    <\/div>\n\n    <ul>\n      <li data-rdq-label=\"introListCount\">Vergleichen Sie alle {count} Retreat-Optionen<\/li>\n      <li data-rdq-label=\"closeQuiz\">Quiz schlie\u00dfen<\/li>\n      <li data-rdq-label=\"questionProgress\">Frage {current} von {total}<\/li>\n      <li data-rdq-label=\"chooseAnswer\">W\u00e4hlen Sie die Antwort, die am ehesten zutrifft.<\/li>\n      <li data-rdq-label=\"back\">Zur\u00fcck<\/li>\n      <li data-rdq-label=\"next\">Weiter<\/li>\n      <li data-rdq-label=\"seeResults\">Meine Ergebnisse ansehen<\/li>\n      <li data-rdq-label=\"byRequest\">Auf Anfrage<\/li>\n      <li data-rdq-label=\"detailsOnRequest\">Details auf Anfrage<\/li>\n      <li data-rdq-label=\"priceOnRequest\">Preis auf Anfrage<\/li>\n      <li data-rdq-label=\"nextDateBadge\">N\u00e4chster Termin:<\/li>\n      <li data-rdq-label=\"strongestEyebrow\">IHRE BESTE \u00dcBEREINSTIMMUNG<\/li>\n      <li data-rdq-label=\"strongestTitle\">Ihr am besten passendes Retreat ist {name}<\/li>\n      <li data-rdq-label=\"secondaryMatch\">Auch {name} k\u00f6nnte gut zu Ihnen passen, da einige Ihrer Vorlieben ebenfalls in diese Richtung weisen.<\/li>\n      <li data-rdq-label=\"location\">Ort<\/li>\n      <li data-rdq-label=\"nextAvailability\">N\u00e4chster Termin<\/li>\n      <li data-rdq-label=\"startingPrice\">Preis ab<\/li>\n      <li data-rdq-label=\"resultNote\">Dieser Vergleich bezieht sich nur auf Retreat-Stil und Vorlieben. Er ist weder eine medizinische Beratung noch eine Einsch\u00e4tzung Ihrer Fastentauglichkeit; die gesundheitliche Eignung muss separat mit dem Retreat-Team gekl\u00e4rt werden. <\/li>\n      <li data-rdq-label=\"percentagesLabel\">\u00dcbereinstimmung mit den Retreats in Prozent<\/li>\n      <li data-rdq-label=\"explore\">Entdecken Sie {name}<\/li>\n      <li data-rdq-label=\"exploreAll\">Alle Retreat-Termine ansehen<\/li>\n      <li data-rdq-label=\"restart\">Quiz neu starten<\/li>\n      <li data-rdq-label=\"noRetreatsTitle\">Retreat-Ergebnisse sind vor\u00fcbergehend nicht verf\u00fcgbar<\/li>\n      <li data-rdq-label=\"noRetreatsCopy\">Wir konnten die aktuelle Retreat-Liste nicht laden. Bitte schlie\u00dfen Sie das Quiz und versuchen Sie es in K\u00fcrze erneut. <\/li>\n    <\/ul>\n  <\/div>\n<\/div>\n\n<script>\n(function() {\n  'use strict';\n\n  var root = document.querySelector('[data-retreat-quiz]');\n  if (!root || root.dataset.rdqReady === 'true') return;\n  root.dataset.rdqReady = 'true';\n\n  \/\/ Scores per answer, in the order of the questions and answers in [data-rdq-copy].\n  \/\/ The text itself is HTML in that block, so WPML translates it with the page.\n  var answerScores = [\n    [\n      { an: 4, hoi: 1, yen: 1, private: 1 },\n      { an: 1, hoi: 4, yen: 1, private: 0 },\n      { an: 1, hoi: 1, yen: 4, private: 0 },\n      { an: 1, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 0, yen: 1, private: 0 },\n      { an: 0, hoi: 4, yen: 1, private: 0 },\n      { an: 0, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 1, yen: 1, private: 0 },\n      { an: 1, hoi: 4, yen: 1, private: 0 },\n      { an: 1, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 0, yen: 1, private: 0 },\n      { an: 0, hoi: 4, yen: 1, private: 0 },\n      { an: 1, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 1, yen: 1, private: 0 },\n      { an: 1, hoi: 4, yen: 1, private: 0 },\n      { an: 1, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 1, yen: 0, private: 1 },\n      { an: 1, hoi: 4, yen: 2, private: 0 },\n      { an: 0, hoi: 2, yen: 4, private: 1 },\n      { an: 0, hoi: 0, yen: 1, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 0, yen: 1, private: 0 },\n      { an: 0, hoi: 4, yen: 1, private: 0 },\n      { an: 0, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ],\n    [\n      { an: 4, hoi: 0, yen: 1, private: 0 },\n      { an: 0, hoi: 4, yen: 1, private: 0 },\n      { an: 1, hoi: 1, yen: 4, private: 0 },\n      { an: 0, hoi: 0, yen: 0, private: 4 }\n    ]\n  ];\n\n  function textOf(element) {\n    return element ? element.textContent.replace(\/\\s+\/g, ' ').trim() : '';\n  }\n\n  var copyRoot = root.querySelector('[data-rdq-copy]');\n  if (!copyRoot) return;\n\n  var labels = {};\n  copyRoot.querySelectorAll('[data-rdq-label]').forEach(function(item) {\n    labels[item.getAttribute('data-rdq-label')] = textOf(item);\n  });\n\n  \/\/ A label with its {placeholders} filled in, e.g. label('questionProgress', { current: 1, total: 8 }).\n  \/\/ WPML trims or keeps spaces at the edges of translated segments, so a placeholder can\n  \/\/ touch a word (\"b\u1ea1n l\u00e0{name}\") or gain a space (\"{current}\/ {total}\"); normalise both.\n  function label(key, values) {\n    return (labels[key] || '')\n      .replace(\/\\\/\\s+\\{\/g, '\/{')\n      .replace(\/\\}\\s+\\\/\/g, '}\/')\n      .replace(\/([\\p{L}\\p{N}])\\{\/gu, '$1 {')\n      .replace(\/\\}([\\p{L}\\p{N}])\/gu, '} $1')\n      .replace(\/\\{(\\w+)\\}\/g, function(match, name) {\n        return values && Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : match;\n      });\n  }\n\n  var questions = Array.prototype.map.call(copyRoot.querySelectorAll('[data-rdq-question]'), function(item, questionIndex) {\n    return {\n      question: textOf(item.querySelector('[data-rdq-question-text]')),\n      answers: Array.prototype.map.call(item.querySelectorAll('[data-rdq-answer-text]'), function(answer, answerIndex) {\n        return {\n          label: textOf(answer),\n          scores: (answerScores[questionIndex] || [])[answerIndex] || {}\n        };\n      })\n    };\n  });\n  if (!questions.length) return;\n\n  \/\/ These keys describe the quiz's scoring directions. The retreats shown in\n  \/\/ results are populated separately from the live API response.\n  var profileOrder = ['an', 'hoi', 'yen', 'private'];\n  var activeProfileOrder = [];\n  var profiles = {\n    an: { id: 13045, sku: 'ANLYT', name: '', reason: '', url: '#retreat-options', api: null },\n    hoi: { id: 21210, sku: 'HOI-FRVDAD', name: '', reason: '', url: '#retreat-options', api: null },\n    yen: { id: 2244, sku: 'AWVH', name: '', reason: '', url: '#retreat-options', api: null },\n    \/\/ No SKU, and the post ID differs per language, so match the API's is_private flag.\n    private: { id: 8905, sku: '', isPrivate: true, name: '', reason: '', url: '#retreat-options', api: null }\n  };\n\n  copyRoot.querySelectorAll('[data-rdq-profile]').forEach(function(item) {\n    var profile = profiles[item.getAttribute('data-rdq-profile')];\n    if (!profile) return;\n    profile.name = textOf(item.querySelector('[data-rdq-profile-name]'));\n    profile.reason = textOf(item.querySelector('[data-rdq-profile-reason]'));\n  });\n\n  var modal = root.querySelector('[data-rdq-modal]');\n  var dialog = root.querySelector('[data-rdq-dialog]');\n  var openButton = root.querySelector('[data-rdq-open]');\n  var progress = root.querySelector('[data-rdq-progress]');\n  var progressText = root.querySelector('[data-rdq-progress-text]');\n  var progressFill = root.querySelector('[data-rdq-progress-fill]');\n  var stage = root.querySelector('[data-rdq-stage]');\n  var result = root.querySelector('[data-rdq-result]');\n\n  var selectedAnswers = new Array(questions.length).fill(null);\n  var currentStep = 0;\n  var mode = 'question';\n  var lastFocused = null;\n  var previousBodyOverflow = '';\n\n  function escapeHtml(value) {\n    var node = document.createElement('div');\n    node.textContent = value == null ? '' : String(value);\n    return node.innerHTML;\n  }\n\n  function safeUrl(value) {\n    if (!value || typeof value !== 'string') return '';\n    if (value.charAt(0) === '#') return value;\n    try {\n      var url = new URL(value, window.location.origin);\n      return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : '';\n    } catch (error) {\n      return '';\n    }\n  }\n\n  function parseApiDate(value) {\n    var usDate = String(value || '').match(\/^(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})$\/);\n    var isoDate = String(value || '').match(\/^(\\d{4})-(\\d{2})-(\\d{2})$\/);\n    var parsed;\n\n    if (usDate) parsed = new Date(Number(usDate[3]), Number(usDate[1]) - 1, Number(usDate[2]));\n    else if (isoDate) parsed = new Date(Number(isoDate[1]), Number(isoDate[2]) - 1, Number(isoDate[3]));\n    else return null;\n\n    return Number.isNaN(parsed.getTime()) ? null : parsed;\n  }\n\n  function getLanguage() {\n    var detected = String(document.documentElement.lang || 'en')\n      .toLowerCase()\n      .split(\/[-_]\/)[0];\n\n    return ['en', 'vi', 'de'].indexOf(detected) !== -1\n      ? detected\n      : 'en';\n  }\n\n  \/\/ The language only selects the API translation and the date and price formats.\n  var language = getLanguage();\n  var locales = { en: 'en-GB', vi: 'vi-VN', de: 'de-DE' };\n  var locale = locales[language] || 'en-GB';\n\n  var closeButton = root.querySelector('.rdq-close');\n  if (closeButton && labels.closeQuiz) closeButton.setAttribute('aria-label', labels.closeQuiz);\n\n  function getUpcomingDates(retreat) {\n    var today = new Date();\n    today.setHours(0, 0, 0, 0);\n    if (!retreat || !Array.isArray(retreat.dates)) return [];\n\n    return retreat.dates.map(function(schedule) {\n      return {\n        schedule: schedule,\n        start: parseApiDate(schedule.start),\n        end: parseApiDate(schedule.end) || parseApiDate(schedule.start)\n      };\n    }).filter(function(item) {\n      return item.start && item.end && item.end >= today;\n    }).sort(function(a, b) {\n      return a.start - b.start;\n    });\n  }\n\n  function formatDateRange(item) {\n    if (!item || !item.start) return label('byRequest');\n    var formatter = new Intl.DateTimeFormat(locale, {\n      day: '2-digit',\n      month: 'short',\n      year: 'numeric'\n    });\n\n    if (item.end && typeof formatter.formatRange === 'function') {\n      return formatter.formatRange(item.start, item.end);\n    }\n\n    return item.end\n      ? formatter.format(item.start) + ' \u2013 ' + formatter.format(item.end)\n      : formatter.format(item.start);\n  }\n\n  function enrichProfiles(retreats) {\n    var availableProfiles = [];\n\n    retreats.forEach(function(retreat) {\n      var key = profileOrder.find(function(profileKey) {\n        var profile = profiles[profileKey];\n        return (profile.sku && retreat.retreat_sku === profile.sku) ||\n          (profile.isPrivate && retreat.is_private === true) ||\n          Number(retreat.id) === Number(profile.id);\n      });\n\n      if (!key || availableProfiles.indexOf(key) !== -1) return;\n\n      var profile = profiles[key];\n      profile.api = retreat;\n      profile.name = retreat.name || profile.name;\n      profile.url = safeUrl(retreat.url) || profile.url;\n      availableProfiles.push(key);\n    });\n\n    \/\/ Preserve the API order and expose only retreats returned by this request.\n    activeProfileOrder = availableProfiles;\n\n    var comparisonLabel = root.querySelector('.rdq-list li:nth-child(2) span:last-child');\n    if (comparisonLabel && labels.introListCount) {\n      comparisonLabel.textContent = label('introListCount', { count: activeProfileOrder.length });\n    }\n  }\n\n  function getSharedRetreatApi() {\n    if (window.VietnamDetoxRetreatAPI) {\n      return window.VietnamDetoxRetreatAPI;\n    }\n\n    window.VietnamDetoxRetreatAPI = {\n      requests: {},\n\n      load: function(apiPath, requestedLanguage) {\n        var apiUrl = new URL(apiPath || '\/wp-json\/retreats\/v1\/all', window.location.origin);\n        apiUrl.searchParams.set('lang', requestedLanguage || 'en');\n\n        var cacheKey = apiUrl.href;\n\n        if (!this.requests[cacheKey]) {\n          this.requests[cacheKey] = fetch(cacheKey, {\n            headers: { Accept: 'application\/json' }\n          }).then(function(response) {\n            if (!response.ok) {\n              throw new Error('Retreat API returned HTTP ' + response.status);\n            }\n            return response.json();\n          }).then(function(retreats) {\n            if (!Array.isArray(retreats)) {\n              throw new Error('Unexpected retreat API response');\n            }\n            return retreats;\n          }).catch(function(error) {\n            delete window.VietnamDetoxRetreatAPI.requests[cacheKey];\n            throw error;\n          });\n        }\n\n        return this.requests[cacheKey];\n      }\n    };\n\n    return window.VietnamDetoxRetreatAPI;\n  }\n\n  function loadRetreats() {\n    var apiPath = root.dataset.api || '\/wp-json\/retreats\/v1\/all';\n    var shared = window.VietnamDetoxRetreatAPI;\n\n    return Promise.resolve().then(function() {\n      \/\/ The Retreat Overview widgets publish an incompatible loader: load(url, force) -> { items }.\n      \/\/ Reuse its request for the same URL instead of forcing a second one.\n      if (shared && shared.entries && typeof shared.key === 'function' && typeof shared.load === 'function') {\n        var apiUrl = new URL(apiPath, window.location.origin);\n        apiUrl.searchParams.set('lang', language);\n        return shared.load(apiUrl.href, false).then(function(result) {\n          return result && result.items;\n        });\n      }\n      return getSharedRetreatApi().load(apiPath, language);\n    })\n      .then(function(retreats) {\n        if (!Array.isArray(retreats)) throw new Error('Unexpected retreat API response');\n        enrichProfiles(retreats);\n        return retreats;\n      })\n      .catch(function(error) {\n        console.warn('Retreat quiz is using fallback content:', error);\n        return [];\n      });\n  }\n\n  function currentSelection() {\n    return selectedAnswers[currentStep];\n  }\n\n  function canContinue() {\n    return currentSelection() !== null && currentSelection() !== undefined;\n  }\n\n  function scoreTotals() {\n    var totals = {};\n    activeProfileOrder.forEach(function(key) { totals[key] = 0; });\n\n    selectedAnswers.forEach(function(answerIndex, questionIndex) {\n      if (answerIndex === null || answerIndex === undefined) return;\n      var answer = questions[questionIndex].answers[answerIndex];\n      activeProfileOrder.forEach(function(key) {\n        totals[key] += answer.scores[key] || 0;\n      });\n    });\n\n    return totals;\n  }\n\n  function normalizedScores() {\n    var totals = scoreTotals();\n    var total = activeProfileOrder.reduce(function(sum, key) { return sum + totals[key]; }, 0);\n    var items = activeProfileOrder.map(function(key) {\n      return {\n        key: key,\n        name: profiles[key].name,\n        score: totals[key],\n        percent: total ? Math.round((totals[key] \/ total) * 100) : 0\n      };\n    });\n\n    var percentageTotal = items.reduce(function(sum, item) { return sum + item.percent; }, 0);\n    if (percentageTotal !== 100 && items.length) {\n      items.slice().sort(function(a, b) { return b.score - a.score; })[0].percent += 100 - percentageTotal;\n    }\n\n    return items.sort(function(a, b) {\n      return b.percent !== a.percent ? b.percent - a.percent : b.score - a.score;\n    });\n  }\n\n  function renderQuestion() {\n    var question = questions[currentStep];\n    mode = 'question';\n    progress.hidden = false;\n    progressText.textContent = label('questionProgress', { current: currentStep + 1, total: questions.length });\n    progressFill.style.width = (((currentStep + 1) \/ questions.length) * 100) + '%';\n    stage.hidden = false;\n    result.hidden = true;\n    result.innerHTML = '';\n\n    stage.innerHTML = [\n      '<article class=\"rdq-question-card\">',\n        '<h2 class=\"rdq-question\">' + escapeHtml(question.question) + '<\/h2>',\n        '<p class=\"rdq-question-note\">' + escapeHtml(label('chooseAnswer')) + '<\/p>',\n        '<div class=\"rdq-answers\" role=\"radiogroup\" aria-label=\"' + escapeHtml(question.question) + '\">',\n          question.answers.map(function(answer, index) {\n            var selected = currentSelection() === index;\n            return '<button type=\"button\" class=\"rdq-answer' + (selected ? ' is-selected' : '') + '\" data-rdq-answer=\"' + index + '\" aria-pressed=\"' + selected + '\">' +\n              '<span class=\"rdq-answer-mark\" aria-hidden=\"true\"><\/span>' +\n              '<span class=\"rdq-answer-text\">' + escapeHtml(answer.label) + '<\/span>' +\n            '<\/button>';\n          }).join(''),\n        '<\/div>',\n        '<div class=\"rdq-nav\">',\n          '<button type=\"button\" class=\"rdq-btn rdq-btn-outline\" data-rdq-back ' + (currentStep === 0 ? 'hidden' : '') + '>' + escapeHtml(label('back')) + '<\/button>',\n          '<button type=\"button\" class=\"rdq-btn rdq-btn-ember\" data-rdq-next ' + (canContinue() ? '' : 'disabled') + '>' +\n            escapeHtml(label(currentStep === questions.length - 1 ? 'seeResults' : 'next')) +\n          '<\/button>',\n        '<\/div>',\n      '<\/article>'\n    ].join('');\n\n    bindQuestionEvents();\n  }\n\n  function resultFacts(profile) {\n    var retreat = profile.api;\n    var dates = getUpcomingDates(retreat);\n    var packages = retreat && Array.isArray(retreat.packages) ? retreat.packages : [];\n    var prices = packages.map(function(item) { return Number(item.price); }).filter(function(price) {\n      return Number.isFinite(price) && price > 0;\n    });\n    var lowestPrice = prices.length ? Math.min.apply(null, prices) : null;\n\n    return {\n      image: retreat ? safeUrl(retreat.image) : '',\n      location: retreat && retreat.location ? retreat.location : label('detailsOnRequest'),\n      nextDate: dates.length ? formatDateRange(dates[0]) : label('byRequest'),\n      price: lowestPrice\n        ? new Intl.NumberFormat(locale, {\n            style: 'currency',\n            currency: 'VND',\n            maximumFractionDigits: 0\n          }).format(lowestPrice)\n        : label('priceOnRequest')\n    };\n  }\n\n  function renderResult() {\n    var scores = normalizedScores();\n\n    if (!scores.length) {\n      mode = 'result';\n      progress.hidden = true;\n      stage.hidden = true;\n      stage.innerHTML = '';\n      result.hidden = false;\n      result.innerHTML = [\n        '<article class=\"rdq-result-card\">',\n          '<span class=\"rdq-result-eyebrow\">' + escapeHtml(label('strongestEyebrow')) + '<\/span>',\n          '<h2 class=\"rdq-result-title\">' + escapeHtml(label('noRetreatsTitle')) + '<\/h2>',\n          '<p class=\"rdq-result-copy\">' + escapeHtml(label('noRetreatsCopy')) + '<\/p>',\n          '<div class=\"rdq-result-actions\">',\n            '<button type=\"button\" class=\"rdq-btn rdq-btn-outline\" data-rdq-restart>' + escapeHtml(label('restart')) + '<\/button>',\n          '<\/div>',\n        '<\/article>'\n      ].join('');\n      result.querySelector('[data-rdq-restart]').addEventListener('click', restartQuiz);\n      return;\n    }\n\n    var primary = scores[0];\n    var secondary = scores[1];\n    var profile = profiles[primary.key];\n    var secondaryProfile = secondary ? profiles[secondary.key] : null;\n    var facts = resultFacts(profile);\n    \/\/ The Retreat Overview page already lists the retreats (section[data-vdrl]),\n    \/\/ so there the links scroll to those cards instead of reloading the page.\n    var cards = document.querySelector('section[data-vdrl][id]');\n    var cardsLink = cards ? '#' + cards.id : '#retreat-options';\n    var allRetreatsLink = cards ? cardsLink : '\/retreat-overview';\n    var url = (profile.url !== '#retreat-options' && safeUrl(profile.url)) || cardsLink;\n    var closeMatch = secondary && primary.percent - secondary.percent <= 5 && secondary.percent > 0;\n\n    var hero = facts.image\n      ? '<a class=\"rdq-result-hero\" href=\"' + escapeHtml(url) + '\" data-rdq-link=\"' + escapeHtml(url) + '\">' +\n          '<img decoding=\"async\" src=\"' + escapeHtml(facts.image) + '\" alt=\"' + escapeHtml(profile.name) + '\">' +\n          '<span class=\"rdq-result-badge\">' + escapeHtml(label('nextDateBadge')) + ' ' + escapeHtml(facts.nextDate) + '<\/span>' +\n        '<\/a>'\n      : '';\n\n    mode = 'result';\n    progress.hidden = true;\n    stage.hidden = true;\n    stage.innerHTML = '';\n    result.hidden = false;\n\n    result.innerHTML = [\n      '<article class=\"rdq-result-card\">',\n        '<span class=\"rdq-result-eyebrow\">' + escapeHtml(label('strongestEyebrow')) + '<\/span>',\n        hero,\n        '<h2 class=\"rdq-result-title\">' + escapeHtml(label('strongestTitle', { name: profile.name })) + '<\/h2>',\n        '<p class=\"rdq-result-copy\">' + escapeHtml(profile.reason) + '<\/p>',\n        closeMatch && secondaryProfile\n          ? '<p class=\"rdq-result-secondary\">' + escapeHtml(label('secondaryMatch')).replace('{name}', '<strong>' + escapeHtml(secondaryProfile.name) + '<\/strong>') + '<\/p>'\n          : '',\n        '<div class=\"rdq-result-facts\">',\n          '<div class=\"rdq-result-fact\"><strong>' + escapeHtml(label('location')) + '<\/strong>' + escapeHtml(facts.location) + '<\/div>',\n          '<div class=\"rdq-result-fact\"><strong>' + escapeHtml(label('nextAvailability')) + '<\/strong>' + escapeHtml(facts.nextDate) + '<\/div>',\n          '<div class=\"rdq-result-fact\"><strong>' + escapeHtml(label('startingPrice')) + '<\/strong>' + escapeHtml(facts.price) + '<\/div>',\n        '<\/div>',\n        '<p class=\"rdq-result-note\">' + escapeHtml(label('resultNote')) + '<\/p>',\n        '<div class=\"rdq-result-bars\" aria-label=\"' + escapeHtml(label('percentagesLabel')) + '\">',\n          scores.map(function(item) {\n            return '<div class=\"rdq-result-bar\">' +\n              '<div class=\"rdq-result-bar-head\"><span>' + escapeHtml(item.name) + '<\/span><strong>' + item.percent + '%<\/strong><\/div>' +\n              '<div class=\"rdq-result-track\"><div class=\"rdq-result-fill ' + item.key + '\" style=\"width:' + item.percent + '%\"><\/div><\/div>' +\n            '<\/div>';\n          }).join(''),\n        '<\/div>',\n        '<div class=\"rdq-result-actions\">',\n          '<a class=\"rdq-btn rdq-btn-ember\" href=\"' + escapeHtml(url) + '\" data-rdq-link=\"' + escapeHtml(url) + '\">' + escapeHtml(label('explore', { name: profile.name })) + '<\/a>',\n          '<a class=\"rdq-btn rdq-btn-outline\" href=\"' + escapeHtml(allRetreatsLink) + '\" data-rdq-link=\"' + escapeHtml(allRetreatsLink) + '\">' + escapeHtml(label('exploreAll')) + '<\/a>',\n          '<button type=\"button\" class=\"rdq-btn rdq-btn-outline\" data-rdq-restart>' + escapeHtml(label('restart')) + '<\/button>',\n        '<\/div>',\n      '<\/article>'\n    ].join('');\n\n    result.querySelector('[data-rdq-restart]').addEventListener('click', restartQuiz);\n  }\n\n  function render() {\n    if (mode === 'result') renderResult();\n    else renderQuestion();\n  }\n\n  function bindQuestionEvents() {\n    var answerButtons = stage.querySelectorAll('[data-rdq-answer]');\n    var backButton = stage.querySelector('[data-rdq-back]');\n    var nextButton = stage.querySelector('[data-rdq-next]');\n\n    answerButtons.forEach(function(button) {\n      button.addEventListener('click', function() {\n        selectedAnswers[currentStep] = Number(button.dataset.rdqAnswer);\n        answerButtons.forEach(function(other) {\n          var selected = other === button;\n          other.classList.toggle('is-selected', selected);\n          other.setAttribute('aria-pressed', selected ? 'true' : 'false');\n        });\n        nextButton.disabled = false;\n      });\n    });\n\n    if (backButton) {\n      backButton.addEventListener('click', function() {\n        if (currentStep > 0) {\n          currentStep -= 1;\n          render();\n        }\n      });\n    }\n\n    nextButton.addEventListener('click', function() {\n      if (!canContinue()) return;\n      if (currentStep < questions.length - 1) currentStep += 1;\n      else mode = 'result';\n      render();\n    });\n  }\n\n  function restartQuiz() {\n    selectedAnswers = new Array(questions.length).fill(null);\n    currentStep = 0;\n    mode = 'question';\n    render();\n  }\n\n  function openModal() {\n    lastFocused = document.activeElement;\n    previousBodyOverflow = document.body.style.overflow;\n    document.body.style.overflow = 'hidden';\n    modal.hidden = false;\n    modal.setAttribute('aria-hidden', 'false');\n    openButton.setAttribute('aria-expanded', 'true');\n    window.requestAnimationFrame(function() {\n      modal.classList.add('is-open');\n      var closeButton = modal.querySelector('[data-rdq-close].rdq-close');\n      (closeButton || dialog).focus();\n    });\n  }\n\n  function closeModal() {\n    modal.classList.remove('is-open');\n    modal.setAttribute('aria-hidden', 'true');\n    openButton.setAttribute('aria-expanded', 'false');\n    document.body.style.overflow = previousBodyOverflow;\n    window.setTimeout(function() { modal.hidden = true; }, 220);\n    if (lastFocused && typeof lastFocused.focus === 'function') lastFocused.focus();\n  }\n\n  function navigate(href) {\n    if (!href) return;\n    if (href.charAt(0) === '#') {\n      closeModal();\n      window.setTimeout(function() {\n        var target = document.querySelector(href);\n        if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n      }, 240);\n      return;\n    }\n    window.location.href = href;\n  }\n\n  openButton.addEventListener('click', openModal);\n  root.querySelectorAll('[data-rdq-close]').forEach(function(button) {\n    button.addEventListener('click', closeModal);\n  });\n\n  root.addEventListener('click', function(event) {\n    var link = event.target.closest('[data-rdq-link]');\n    if (!link) return;\n    event.preventDefault();\n    navigate(link.getAttribute('data-rdq-link'));\n  });\n\n  dialog.addEventListener('keydown', function(event) {\n    if (event.key !== 'Tab') return;\n    \/\/ Skip tabindex=\"-1\" in the filter, not the selector: WPML turns quotes inside\n    \/\/ attribute selectors into &quot; on translated pages, which breaks querySelectorAll.\n    var focusable = Array.prototype.slice.call(dialog.querySelectorAll(\n      'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]'\n    )).filter(function(element) {\n      return element.getAttribute('tabindex') !== '-1' && !element.hidden && element.offsetParent !== null;\n    });\n    if (!focusable.length) return;\n    var first = focusable[0];\n    var last = focusable[focusable.length - 1];\n    if (event.shiftKey && document.activeElement === first) {\n      event.preventDefault();\n      last.focus();\n    } else if (!event.shiftKey && document.activeElement === last) {\n      event.preventDefault();\n      first.focus();\n    }\n  });\n\n  document.addEventListener('keydown', function(event) {\n    if (event.key === 'Escape' && !modal.hidden) closeModal();\n  });\n\n  loadRetreats().then(function() {\n    if (mode === 'result') renderResult();\n  });\n\n  render();\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-ebf20ab e-con-full vbook__booking-card e-flex e-con e-parent\" data-id=\"ebf20ab\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f127263 elementor-widget elementor-widget-html\" data-id=\"f127263\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!--\n  Elementor setup\n  1. Paste this file into an HTML widget inside the outer CTA container.\n  2. Add a separate Bookly Booking form or Elementor Shortcode widget in the\n     vrdc__booking-card inner container below this HTML widget.\n  3. Paste elementor-retreat-decision-cta.css into the OUTER container's\n     Advanced > Custom CSS field.\n\n  The HTML widget cannot run WordPress shortcodes. Use [bookly-form] only in\n  Elementor's Shortcode widget, not in this HTML widget.\n\n  Button links are language aware. Each button carries one data-vrdc-href-<lang>\n  attribute per language; the script picks the one matching <html lang>. Edit those\n  attributes rather than the href when a translated URL changes.\n-->\n\n<section class=\"vrdc\" data-vrdc=\"\" aria-labelledby=\"vrdc-title\">\n  <div class=\"vrdc__inner\">\n    <h2 class=\"vrdc__title\" id=\"vrdc-title\">Sie sind nicht sicher, welches Retreat das richtige f\u00fcr Sie ist?<\/h2>\n\n    <p class=\"vrdc__copy\">\n Entdecken Sie die einzelnen Retreat-Seiten oder buchen Sie vor Ihrer Entscheidung ein kostenloses Gespr\u00e4ch mit\n Dieter.\n    <\/p>\n\n    <div class=\"vrdc__buttons\">\n      <a href=\"https:\/\/vietnamdetox.com\/de\/kennenlerngespraech\/\" class=\"vrdc__button vrdc__button--primary\" data-vrdc-href-en=\"\/discovery-call\/\" data-vrdc-href-vi=\"\/vi\/cuoc-goi-tham-khao\/\">Kennenlerngespr\u00e4ch buchen<\/a>\n\n      <a href=\"https:\/\/vietnamdetox.com\/de\/faq\/\" class=\"vrdc__button vrdc__button--outline\" data-vrdc-href-en=\"\/faq\/\" data-vrdc-href-vi=\"\/vi\/cau-hoi-thuong-gap\/\">FAQ<\/a>\n    <\/div>\n  <\/div>\n<\/section>\n\n<script>\n(function () {\n  'use strict';\n\n  var SUPPORTED = ['en', 'vi'];\n\n  function applyLanguage(root) {\n    if (root.dataset.vrdcReady === 'true') return;\n    root.dataset.vrdcReady = 'true';\n\n    var language = String(document.documentElement.lang || 'en')\n      .toLowerCase()\n      .split(\/[-_]\/)[0];\n\n    if (language === 'vn') language = 'vi';\n    if (SUPPORTED.indexOf(language) < 0) language = 'en';\n\n    var attribute = 'data-vrdc-href-' + language;\n\n    Array.prototype.forEach.call(\n      root.querySelectorAll('.vrdc__button'),\n      function (button) {\n        var href = button.getAttribute(attribute);\n        if (href) button.setAttribute('href', href);\n      }\n    );\n  }\n\n  function init() {\n    Array.prototype.forEach.call(document.querySelectorAll('[data-vrdc]'), applyLanguage);\n  }\n\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', init);\n  } else {\n    init();\n  }\n})();\n<\/script>\n\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>Retreat-Bilder werden geladen\u2026 Box ausblenden \u00d7 Vietnam Detox \u00b7 Gef\u00fchrte Fasten-Retreats in Vietnam Retreats f\u00fcr Menschen, die zu viel tragen. Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt. Gleiche bew\u00e4hrte Methode. Unterschiedliche Wege, endlich aufzuh\u00f6ren. F\u00fcr Menschen, die Verantwortung f\u00fcr Arbeit, Team oder Familie [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-22644","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.5 (Yoast SEO v28.5) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Retreat-\u00dcberblick - Vietnamdetox<\/title>\n<meta name=\"description\" content=\"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Retreat-\u00dcberblick\" \/>\n<meta property=\"og:description\" content=\"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/\" \/>\n<meta property=\"og:site_name\" content=\"Vietnamdetox\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-18T09:42:53+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data1\" content=\"5\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/retreat-ueberblick\\\/\",\"url\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/retreat-ueberblick\\\/\",\"name\":\"Retreat-\u00dcberblick - Vietnamdetox\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#website\"},\"datePublished\":\"2026-08-12T06:04:29+00:00\",\"dateModified\":\"2026-10-18T09:42:53+00:00\",\"description\":\"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/retreat-ueberblick\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/retreat-ueberblick\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/retreat-ueberblick\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/startseite\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Retreat-\u00dcberblick\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/\",\"name\":\"Staging Vietnamdetox\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#organization\",\"name\":\"Staging Vietnamdetox\",\"url\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/vietnamdetox.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logo-icon-color_cutout.svg\",\"contentUrl\":\"https:\\\/\\\/vietnamdetox.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logo-icon-color_cutout.svg\",\"width\":314,\"height\":435,\"caption\":\"Staging Vietnamdetox\"},\"image\":{\"@id\":\"https:\\\/\\\/vietnamdetox.com\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Retreat-\u00dcberblick - Vietnamdetox","description":"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/","og_locale":"de_DE","og_type":"article","og_title":"Retreat-\u00dcberblick","og_description":"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.","og_url":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/","og_site_name":"Vietnamdetox","article_modified_time":"2026-10-18T09:42:53+00:00","twitter_card":"summary_large_image","twitter_misc":{"Gesch\u00e4tzte Lesezeit":"5\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/","url":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/","name":"Retreat-\u00dcberblick - Vietnamdetox","isPartOf":{"@id":"https:\/\/vietnamdetox.com\/de\/#website"},"datePublished":"2026-08-12T06:04:29+00:00","dateModified":"2026-10-18T09:42:53+00:00","description":"Gef\u00fchrtes Buchinger-Fasten in Vietnam \u2013 vergleichen Sie unsere aktuellen Retreat-Settings und w\u00e4hlen Sie, was zu Ihrer aktuellen Situation passt.","breadcrumb":{"@id":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/vietnamdetox.com\/de\/retreat-ueberblick\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vietnamdetox.com\/de\/startseite\/"},{"@type":"ListItem","position":2,"name":"Retreat-\u00dcberblick"}]},{"@type":"WebSite","@id":"https:\/\/vietnamdetox.com\/de\/#website","url":"https:\/\/vietnamdetox.com\/de\/","name":"Staging Vietnamdetox","description":"","publisher":{"@id":"https:\/\/vietnamdetox.com\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/vietnamdetox.com\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/vietnamdetox.com\/de\/#organization","name":"Staging Vietnamdetox","url":"https:\/\/vietnamdetox.com\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/vietnamdetox.com\/de\/#\/schema\/logo\/image\/","url":"https:\/\/vietnamdetox.com\/wp-content\/uploads\/2026\/05\/logo-icon-color_cutout.svg","contentUrl":"https:\/\/vietnamdetox.com\/wp-content\/uploads\/2026\/05\/logo-icon-color_cutout.svg","width":314,"height":435,"caption":"Staging Vietnamdetox"},"image":{"@id":"https:\/\/vietnamdetox.com\/de\/#\/schema\/logo\/image\/"}}]}},"_hostinger_reach_plugin_has_subscription_block":false,"_hostinger_reach_plugin_is_elementor":false,"_links":{"self":[{"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/pages\/22644","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/comments?post=22644"}],"version-history":[{"count":7,"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/pages\/22644\/revisions"}],"predecessor-version":[{"id":22786,"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/pages\/22644\/revisions\/22786"}],"wp:attachment":[{"href":"https:\/\/vietnamdetox.com\/de\/wp-json\/wp\/v2\/media?parent=22644"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}