(function(){
  const doc = document;
  const qs = (s,r=doc)=>r.querySelector(s);
  const qsa = (s,r=doc)=>Array.from(r.querySelectorAll(s));
  const monthNames=['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'];
  const weekdayShort=['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];

  function slugify(value){ return String(value || '').toLowerCase().replace(/[^\p{L}\p{N}]+/gu,'-').replace(/^-+|-+$/g,''); }
  function parseYmd(ymd){ const [y,m,d]=String(ymd||'').split('-').map(Number); const dt=new Date(y,(m||1)-1,d||1); dt.setHours(0,0,0,0); return dt; }
  function toYmd(date){ return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`; }
  function startOfMonth(date){ const d=new Date(date.getFullYear(), date.getMonth(), 1); d.setHours(0,0,0,0); return d; }
  function formatRu(ymd){ return ymd ? parseYmd(ymd).toLocaleDateString('ru-RU',{day:'2-digit',month:'long',year:'numeric'}) : ''; }
  function formatRuShort(ymd){ return ymd ? parseYmd(ymd).toLocaleDateString('ru-RU',{day:'2-digit',month:'long'}) : ''; }
  function nightsBetween(start,end){ if(!start || !end) return 0; return Math.max(0, Math.round((parseYmd(end)-parseYmd(start))/86400000)); }
  function compareYmd(a,b){ return parseYmd(a).getTime() - parseYmd(b).getTime(); }
  function priceToNumber(value){ const m=String(value||'').replace(/\s+/g,'').match(/(\d+)/g); return m ? Number(m.join('')) : 0; }
  function parseJsonScript(id, fallback){ const node=doc.getElementById(id); if(!node) return fallback; try{return JSON.parse(node.textContent||'')||fallback;}catch(e){console.error(e); return fallback;} }
  function isSameMonth(ymd, monthDate){ if(!ymd) return false; const dt=parseYmd(ymd); return dt.getFullYear()===monthDate.getFullYear() && dt.getMonth()===monthDate.getMonth(); }
  function isPastDate(ymd){ const today=new Date(); today.setHours(0,0,0,0); return parseYmd(ymd).getTime() < today.getTime(); }
  function daysBetween(start,end){ const list=[]; let cur=parseYmd(start); const endDate=parseYmd(end); while(cur<=endDate){ list.push(toYmd(cur)); cur.setDate(cur.getDate()+1); } return list; }


  /* feyaframe.com image protection: disable drag and image context menu */
  const imageProtectionSelector = 'img, picture, .complex-map-image, [data-open-lightbox]';
  function isImageProtectionTarget(target){
    return !!(target && target.closest && target.closest(imageProtectionSelector));
  }
  function applyImageProtection(root = doc){
    const images = [];
    if(root && root.nodeType === 1 && root.matches && root.matches('img')) images.push(root);
    const found = root && root.querySelectorAll ? root.querySelectorAll('img') : [];
    found.forEach(img => images.push(img));
    images.forEach(img => {
      img.setAttribute('draggable', 'false');
      img.setAttribute('oncontextmenu', 'return false;');
      img.setAttribute('onmousedown', "if(event.button===2)return false;");
      img.classList.add('is-copy-protected-image');
    });
  }
  function blockImageCopyAction(event){
    if(!isImageProtectionTarget(event.target)) return;
    event.preventDefault();
    event.stopPropagation();
    return false;
  }
  applyImageProtection(doc);
  doc.addEventListener('contextmenu', blockImageCopyAction, true);
  doc.addEventListener('dragstart', blockImageCopyAction, true);
  doc.addEventListener('selectstart', blockImageCopyAction, true);
  doc.addEventListener('mousedown', event => {
    if(event.button === 2) blockImageCopyAction(event);
  }, true);
  doc.addEventListener('touchstart', event => {
    if(isImageProtectionTarget(event.target)) applyImageProtection(event.target.closest(imageProtectionSelector) || doc);
  }, {capture: true, passive: true});
  if('MutationObserver' in window){
    new MutationObserver(mutations => {
      mutations.forEach(mutation => mutation.addedNodes.forEach(node => applyImageProtection(node)));
    }).observe(doc.documentElement, {childList: true, subtree: true});
  }

  function escapeHtml(value){ return String(value == null ? '' : value).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/\'/g,'&#39;'); }
  function formatRichText(value){
    const raw = String(value || '').trim();
    if(!raw) return '';
    const normalized = raw.replace(/\r\n?/g,'\n');
    const blocks = normalized.split(/\n{2,}/).map(part => part.trim()).filter(Boolean);
    return blocks.map(block => {
      const lines = block.split('\n').map(line => line.trim()).filter(Boolean);
      const isList = lines.length > 1 && lines.every(line => /^[-•*]\s+/.test(line));
      if(isList){
        return `<ul>${lines.map(line => `<li>${escapeHtml(line.replace(/^[-•*]\s+/, ''))}</li>`).join('')}</ul>`;
      }
      return `<p>${lines.map(line => escapeHtml(line)).join('<br>')}</p>`;
    }).join('');
  }
  const recurringHolidays = new Set(['01-01','01-02','01-03','01-04','01-05','01-06','01-07','01-08','02-23','03-08','05-01','05-09','06-12','11-04','12-31']);
  function isHolidayDate(ymd){ return recurringHolidays.has(String(ymd||'').slice(5)); }

  const bookingModal = qs('[data-booking-modal]');
  const detailsModal = qs('[data-details-modal]');
  const lightboxModal = qs('[data-lightbox-modal]');
  const lightboxImage = qs('[data-lightbox-image]');
  const mobileNav = qs('[data-mobile-nav]');
  const burger = qs('[data-burger]');
  const form = qs('[data-booking-form]');
  const submitButton = qs('[data-booking-submit]');
  const jsEnabledField = qs('[data-js-enabled]');
  const interactionStartedField = qs('[data-interaction-started]');
  const statusBox = qs('[data-form-status]');
  const bookingSuccessModal = qs('[data-booking-success-modal]');
  const bookingSuccessTitleNode = qs('[data-booking-success-title]');
  const bookingSuccessTextNode = qs('[data-booking-success-text]');
  const bookingSuccessSecondsNode = qs('[data-booking-success-seconds]');
  const checkinField = qs('[data-checkin]');
  const checkoutField = qs('[data-checkout]');
  const datesRangeField = qs('[data-dates-range]');
  const estimatedTotalField = qs('[data-estimated-total]');
  const houseSelect = qs('[data-house-select]');
  const houseTitleNode = qs('[data-booking-house-title]');
  const houseDescNode = qs('[data-booking-house-description]');
  const houseMetaNode = qs('[data-booking-house-meta]');
  const houseFeaturesNode = qs('[data-booking-house-features]');
  const houseGalleryNode = qs('[data-booking-house-gallery]');
  const houseTimesNode = qs('[data-booking-house-times]');
  const houseNoteNode = qs('[data-booking-house-note]');
  const summaryNights = qs('[data-summary-nights]');
  const summaryTotal = qs('[data-summary-total]');
  const calendarRoot = qs('[data-calendar-picker]');
  const detailTitle = qs('[data-details-title]');
  const detailMeta = qs('[data-details-meta]');
  const detailDescription = qs('[data-details-description]');
  const detailFeatures = qs('[data-details-features]');
  const detailPros = qs('[data-details-pros]');
  const detailInside = qs('[data-details-inside]');
  const detailTerritory = qs('[data-details-territory]');
  const detailTimes = qs('[data-details-times]');
  const detailGallery = qs('[data-details-gallery]');
  const detailBookingBtn = qs('[data-details-booking]');

  function getDetailsRefs(){
    const modal = qs('[data-details-modal]') || detailsModal;
    return {
      modal,
      title: qs('[data-details-title]') || detailTitle,
      meta: qs('[data-details-meta]') || detailMeta,
      description: qs('[data-details-description]') || detailDescription,
      features: qs('[data-details-features]') || detailFeatures,
      pros: qs('[data-details-pros]') || detailPros,
      inside: qs('[data-details-inside]') || detailInside,
      territory: qs('[data-details-territory]') || detailTerritory,
      times: qs('[data-details-times]') || detailTimes,
      gallery: qs('[data-details-gallery]') || detailGallery,
      bookingBtn: qs('[data-details-booking]') || detailBookingBtn
    };
  }

  const availability = parseJsonScript('availability-data', {global:[], houses:{}});
  const housesCollection = parseJsonScript('houses-data', {items:[]});
  const ratesCollection = parseJsonScript('rates-data', {items:[]});
  const bookingSuccessConfig = Object.assign({enabled:true,title:'Спасибо за заявку',text:'Мы скоро с вами свяжемся.',details_template:'Ваша бронь: {house}\nДаты: {checkin_ru} – {checkout_ru}\nМы свяжемся с вами.',duration_ms:10000}, parseJsonScript('booking-success-settings', {}));
  const housesMap = {};
  (housesCollection.items || []).forEach(item => {
    if (!item || !item.title) return;
    housesMap[item.title] = item;
    housesMap[slugify(item.title)] = item;
  });

  const state = {

    currentHouse: '',
    checkin: '',
    checkout: '',
    month: startOfMonth(new Date()),
    selecting: 'checkin'
  };
  window.__siteState = state;

  const lightboxState = {items: [], index: 0};
  let bookingSuccessTimer = 0;
  let bookingSubmitting = false;
  let metrikaGoalOnlySubmit = false;
  if(jsEnabledField) jsEnabledField.value = '1';

  function getHouse(house){
    const list = (housesCollection && Array.isArray(housesCollection.items)) ? housesCollection.items : [];
    const raw = String(house || '').trim();
    if(!raw) return null;
    const normalized = raw.toLowerCase();
    const slugified = normalized
      .replace(/\s+/g,'-')
      .replace(/[^\p{L}\p{N}\-]+/gu,'')
      .replace(/-+/g,'-')
      .replace(/^-|-$/g,'');
    return list.find(item=>{
      const title = String(item.title || '').trim();
      const titleNorm = title.toLowerCase();
      const itemSlug = String(item.slug || '').trim().toLowerCase();
      const titleSlug = titleNorm
        .replace(/\s+/g,'-')
        .replace(/[^\p{L}\p{N}\-]+/gu,'')
        .replace(/-+/g,'-')
        .replace(/^-|-$/g,'');
      return title === raw || titleNorm === normalized || itemSlug === normalized || titleSlug === slugified;
    }) || null;
  }

  function getHousePricing(house){
    const list = (ratesCollection && Array.isArray(ratesCollection.items)) ? ratesCollection.items : [];
    const raw = String(house || state.currentHouse || '').trim();
    const normalized = raw.toLowerCase();
    const slugified = normalized
      .replace(/\s+/g,'-')
      .replace(/[^\p{L}\p{N}\-]+/gu,'')
      .replace(/-+/g,'-')
      .replace(/^-|-$/g,'');
    const matched = list.filter(item => {
      const name = String((item && item.house) || '').trim();
      const nameNorm = name.toLowerCase();
      const nameSlug = nameNorm
        .replace(/\s+/g,'-')
        .replace(/[^\p{L}\p{N}\-]+/gu,'')
        .replace(/-+/g,'-')
        .replace(/^-|-$/g,'');
      return name === raw || nameNorm === normalized || nameSlug === slugified;
    });
    const row = matched[0] || {};
    return {
      base: priceToNumber(row.weekday_price || row.price || 0),
      weekday: priceToNumber(row.weekday_price || row.price || 0),
      weekend: priceToNumber(row.weekend_price || row.weekday_price || row.price || 0),
      holiday: priceToNumber(row.holiday_price || row.weekend_price || row.weekday_price || row.price || 0),
      minNights: Number(row.min_nights || 1) || 1,
      notes: String(row.notes || '')
    };
  }

  function getNightPrice(ymd, house){
    const pricing = getHousePricing(house);
    const dt = parseYmd(ymd);
    const day = dt.getDay();
    if (isHolidayDate(ymd)) return pricing.holiday || pricing.weekend || pricing.weekday || pricing.base || 0;
    if (day === 0 || day === 6) return pricing.weekend || pricing.weekday || pricing.base || 0;
    return pricing.weekday || pricing.base || 0;
  }
  function calculateStayPrice(checkin, checkout, house){
    if(!checkin || !checkout || compareYmd(checkout, checkin) <= 0) return 0;
    let cur = parseYmd(checkin);
    const end = parseYmd(checkout);
    let total = 0;
    while(cur < end){
      total += getNightPrice(toYmd(cur), house);
      cur.setDate(cur.getDate() + 1);
    }
    return total;
  }
  function formatMoney(value){ return value ? `${new Intl.NumberFormat('ru-RU').format(value)} ₽` : ''; }
  function getClosedDates(house){
    const slug = slugify(house || state.currentHouse);
    return new Set([...(availability.global || []), ...((availability.houses && availability.houses[slug]) || [])]);
  }
  function isClosedDate(ymd, house){ return getClosedDates(house).has(ymd); }
  function rangeHasClosed(start,end, house){
    if(!start || !end) return false;
    const closed = getClosedDates(house);
    let cur = parseYmd(start);
    const endDate = parseYmd(end);
    cur.setDate(cur.getDate()+1);
    while(cur < endDate){
      if(closed.has(toYmd(cur))) return true;
      cur.setDate(cur.getDate()+1);
    }
    return false;
  }
  function getSelectionMode(){ return state.selecting || (state.checkin && !state.checkout ? 'checkout' : 'checkin'); }

  function syncDisplayedFields(){
    if(checkinField) checkinField.value = formatRu(state.checkin);
    if(checkoutField) checkoutField.value = formatRu(state.checkout);
    if(datesRangeField) datesRangeField.value = state.checkin && state.checkout ? `${state.checkin} — ${state.checkout}` : '';
  }

  function setRange(checkin='', checkout=''){
    state.checkin = checkin || '';
    state.checkout = checkout || '';
    state.selecting = state.checkin && !state.checkout ? 'checkout' : 'checkin';
    if(state.checkin && !isSameMonth(state.checkin, state.month)) state.month = startOfMonth(parseYmd(state.checkin));
    syncDisplayedFields();
    updateSummary();
    renderCalendar();
    if(state.checkin && state.checkout) clearBookingFieldError('dates');
  }

  function updateSummary(){
    const nights = nightsBetween(state.checkin, state.checkout);
    const pricing = getHousePricing(state.currentHouse);
    const total = calculateStayPrice(state.checkin, state.checkout, state.currentHouse);
    if(summaryNights) summaryNights.textContent = nights ? `${nights}` : '—';
    if(summaryTotal){
      if(nights && total){
        summaryTotal.textContent = formatMoney(total);
      } else if (pricing.base) {
        const weekendNote = pricing.weekend && pricing.weekend !== pricing.weekday ? `, выходные/сб-вс ${formatMoney(pricing.weekend)}` : '';
        const holidayNote = pricing.holiday && pricing.holiday !== pricing.weekend ? `, праздники ${formatMoney(pricing.holiday)}` : '';
        summaryTotal.textContent = `от ${formatMoney(pricing.weekday || pricing.base)} / ночь${weekendNote}${holidayNote}`;
      } else {
        summaryTotal.textContent = 'Уточняется';
      }
    }
    if(estimatedTotalField){
      estimatedTotalField.value = nights && total ? formatMoney(total) : '';
    }
  }

  function showFormMessage(text, isError=false){
    if(!statusBox) return;
    statusBox.textContent = text || '';
    statusBox.className = `form-status${isError ? ' is-error' : (text ? ' is-success' : '')}`;
  }

  function showValidationSummary(errors){
    if(!statusBox) return;
    statusBox.innerHTML = '';
    statusBox.className = 'form-status is-error form-status--validation';
    const title = doc.createElement('strong');
    title.textContent = errors.length === 1 ? 'Проверьте отмеченное поле:' : 'Заполните отмеченные поля:';
    statusBox.appendChild(title);
    const list = doc.createElement('ul');
    errors.forEach(item=>{
      const li = doc.createElement('li');
      li.textContent = item.message;
      list.appendChild(li);
    });
    statusBox.appendChild(list);
  }

  function markBookingInteraction(){
    if(interactionStartedField && (!interactionStartedField.value || interactionStartedField.value === '0')){
      interactionStartedField.value = String(Math.floor(Date.now() / 1000));
    }
  }

  function bookingField(name){
    return form ? form.querySelector(`[name="${name}"]`) : null;
  }

  function bookingFieldWrapper(key){
    return form ? form.querySelector(`[data-booking-field="${key}"]`) : null;
  }

  function bookingFieldErrorNode(key){
    return form ? form.querySelector(`[data-booking-field-error="${key}"]`) : null;
  }

  function bookingControlForKey(key){
    if(key === 'dates') return calendarRoot || checkinField || checkoutField;
    return bookingField(key);
  }

  function clearBookingFieldError(key){
    if(!form || !key) return;
    const wrapper = bookingFieldWrapper(key);
    const errorNode = bookingFieldErrorNode(key);
    const controls = [];
    const mainControl = bookingControlForKey(key);
    if(mainControl) controls.push(mainControl);
    if(key === 'dates'){
      if(checkinField) controls.push(checkinField);
      if(checkoutField) controls.push(checkoutField);
    }
    controls.forEach(node=>{
      if(!node) return;
      node.removeAttribute('aria-invalid');
      if(typeof node.setCustomValidity === 'function') node.setCustomValidity('');
    });
    if(wrapper) wrapper.classList.remove('is-invalid');
    if(errorNode){
      errorNode.textContent = '';
      errorNode.hidden = true;
    }
  }

  function clearBookingFieldErrors(){
    if(!form) return;
    qsa('[data-booking-field]', form).forEach(wrapper=>{
      const key = wrapper.getAttribute('data-booking-field') || '';
      if(key) clearBookingFieldError(key);
    });
    showFormMessage('', false);
  }

  function invalidateBookingField(key, node, message){
    const target = node || bookingControlForKey(key);
    const wrapper = bookingFieldWrapper(key);
    const errorNode = bookingFieldErrorNode(key);
    if(target){
      target.setAttribute('aria-invalid','true');
      if(typeof target.setCustomValidity === 'function') target.setCustomValidity(message || 'Проверьте поле.');
    }
    if(key === 'dates'){
      if(checkinField) checkinField.setAttribute('aria-invalid','true');
      if(checkoutField) checkoutField.setAttribute('aria-invalid','true');
    }
    if(wrapper) wrapper.classList.add('is-invalid');
    if(errorNode){
      errorNode.textContent = message || 'Проверьте поле.';
      errorNode.hidden = false;
    }
    return {key, node:target, wrapper, message};
  }

  function bookingLetterCount(value){
    const text = String(value || '');
    try {
      const matches = text.match(new RegExp('\\p{L}', 'gu'));
      return matches ? matches.length : 0;
    } catch(err) {
      const matches = text.match(/[A-Za-zА-Яа-яЁё]/g);
      return matches ? matches.length : 0;
    }
  }

  function focusBookingError(error){
    if(!error) return;
    const scrollTarget = error.wrapper || error.node;
    if(scrollTarget && typeof scrollTarget.scrollIntoView === 'function'){
      try { scrollTarget.scrollIntoView({behavior:'smooth', block:'center'}); }
      catch(err) { scrollTarget.scrollIntoView(); }
    }
    if(error.node && typeof error.node.focus === 'function'){
      window.setTimeout(()=>{
        try { error.node.focus({preventScroll:true}); }
        catch(err) { try { error.node.focus(); } catch(ignore) {} }
      }, 250);
    }
  }

  function validateBookingForm(){
    clearBookingFieldErrors();
    const nameField = bookingField('name');
    const phoneField = bookingField('phone');
    const privacyField = bookingField('privacy');
    const adultsField = bookingField('adults');
    const name = String(nameField && nameField.value ? nameField.value : '').trim();
    const phone = String(phoneField && phoneField.value ? phoneField.value : '').trim();
    const phoneDigits = phone.replace(/\D+/g,'');
    const errors = [];
    const invalidKeys = Object.create(null);
    const addError = (key, node, message)=>{
      if(invalidKeys[key]) return;
      invalidKeys[key] = true;
      errors.push(invalidateBookingField(key, node, message));
    };

    if(!state.currentHouse || !housesMap[state.currentHouse]){
      addError('house', houseSelect, 'Выберите домик.');
    }
    if(!state.checkin || !state.checkout){
      addError('dates', calendarRoot, 'Выберите даты заезда и выезда.');
    }
    if(name.length < 2 || bookingLetterCount(name) < 2){
      addError('name', nameField, 'Укажите настоящее имя — минимум 2 буквы.');
    }
    if(phoneDigits.length < 10 || phoneDigits.length > 15 || new Set(phoneDigits.split('')).size < 2){
      addError('phone', phoneField, 'Введите корректный номер телефона.');
    }
    if(adultsField && (Number(adultsField.value) < 1 || Number(adultsField.value) > 20)){
      addError('adults', adultsField, 'Укажите количество взрослых от 1 до 20.');
    }
    if(!privacyField || !privacyField.checked){
      addError('privacy', privacyField, 'Подтвердите согласие с условиями.');
    }

    // Respect any additional fields marked as required in the admin editor.
    // Native browser bubbles are disabled so every missing field can be shown at once.
    qsa('[required]', form).forEach(control=>{
      if(!control || control.disabled || control.type === 'hidden') return;
      const fieldName = control.name || '';
      if(!fieldName || invalidKeys[fieldName] || fieldName === 'checkin' || fieldName === 'checkout') return;
      const empty = (control.type === 'checkbox' || control.type === 'radio')
        ? !control.checked
        : String(control.value || '').trim() === '';
      if(!empty) return;
      const wrapper = bookingFieldWrapper(fieldName);
      let label = '';
      if(wrapper){
        const children = wrapper.children || [];
        for(let i=0;i<children.length;i++){
          if(children[i].tagName === 'SPAN'){
            label = String(children[i].textContent || '').trim();
            break;
          }
        }
      }
      addError(fieldName, control, label ? `Заполните поле «${label}».` : 'Заполните обязательное поле.');
    });

    if(errors.length){
      showValidationSummary(errors);
      focusBookingError(errors[0]);
      return false;
    }
    return true;
  }

  function applyServerFieldErrors(fields){
    if(!fields || typeof fields !== 'object') return false;
    clearBookingFieldErrors();
    const errors = [];
    Object.keys(fields).forEach(key=>{
      const normalizedKey = (key === 'checkin' || key === 'checkout') ? 'dates' : key;
      const message = String(fields[key] || 'Проверьте поле.');
      errors.push(invalidateBookingField(normalizedKey, bookingControlForKey(normalizedKey), message));
    });
    if(!errors.length) return false;
    showValidationSummary(errors);
    focusBookingError(errors[0]);
    return true;
  }

  function setBookingSubmitting(active){
    bookingSubmitting = !!active;
    if(submitButton){
      submitButton.disabled = bookingSubmitting;
      submitButton.setAttribute('aria-busy', bookingSubmitting ? 'true' : 'false');
      if(!submitButton.dataset.defaultLabel) submitButton.dataset.defaultLabel = submitButton.textContent || 'Отправить заявку';
      submitButton.textContent = bookingSubmitting ? 'Отправляем…' : submitButton.dataset.defaultLabel;
    }
  }

  function confirmAutomaticFormGoal(){
    if(!form) return;
    metrikaGoalOnlySubmit = true;
    try {
      if(typeof form.requestSubmit === 'function') form.requestSubmit();
      else form.dispatchEvent(new Event('submit', {bubbles:true, cancelable:true}));
    } catch(err){
      console.debug('Form goal event was not dispatched', err);
    } finally {
      metrikaGoalOnlySubmit = false;
    }
  }

  function handleDateSelection(dateStr){
    if(!dateStr || isPastDate(dateStr) || isClosedDate(dateStr, state.currentHouse)) return;
    const mode = getSelectionMode();
    if(mode === 'checkin' || !state.checkin){
      state.selecting = 'checkout';
      setRange(dateStr, '');
      showFormMessage('');
      return;
    }
    if(compareYmd(dateStr, state.checkin) <= 0){
      state.selecting = 'checkout';
      setRange(dateStr, '');
      showFormMessage('Дата выезда должна быть позже даты заезда. Сначала выбрана новая дата заезда.', true);
      return;
    }
    if(rangeHasClosed(state.checkin, dateStr, state.currentHouse)){
      showFormMessage('В выбранном диапазоне есть закрытые даты. Выберите другие даты.', true);
      return;
    }
    state.selecting = 'checkin';
    setRange(state.checkin, dateStr);
    showFormMessage('');
  }

function renderCalendar(){
  if(!calendarRoot) return;

  const monthDate = startOfMonth(state.month);
  const closed = getClosedDates(state.currentHouse);
  const year = monthDate.getFullYear();
  const month = monthDate.getMonth();
  const first = new Date(year, month, 1);
  const startWeekday = (first.getDay() + 6) % 7;
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const mode = getSelectionMode();

  calendarRoot.innerHTML = '';
  calendarRoot.classList.add('calendar-picker--ready');

  const head = doc.createElement('div');
  head.className = 'calendar-head';

  const prevBtn = doc.createElement('button');
  prevBtn.type = 'button';
  prevBtn.className = 'calendar-head__nav';
  prevBtn.setAttribute('aria-label', 'Предыдущий месяц');
  prevBtn.textContent = '‹';
  prevBtn.addEventListener('click', function(ev){
    ev.preventDefault();
    ev.stopPropagation();
    state.month = new Date(state.month.getFullYear(), state.month.getMonth() - 1, 1);
    renderCalendar();
  });

  const title = doc.createElement('div');
  title.className = 'calendar-head__title';
  title.textContent = `${monthNames[month]} ${year}`;

  const nextBtn = doc.createElement('button');
  nextBtn.type = 'button';
  nextBtn.className = 'calendar-head__nav';
  nextBtn.setAttribute('aria-label', 'Следующий месяц');
  nextBtn.textContent = '›';
  nextBtn.addEventListener('click', function(ev){
    ev.preventDefault();
    ev.stopPropagation();
    state.month = new Date(state.month.getFullYear(), state.month.getMonth() + 1, 1);
    renderCalendar();
  });

  head.append(prevBtn, title, nextBtn);

  const hint = doc.createElement('div');
  hint.className = 'calendar-hint';
  hint.innerHTML = `Сначала выберите <strong>заезд</strong>, затем <strong>выезд</strong>. Сейчас: <strong>${mode === 'checkin' ? 'выбор заезда' : 'выбор выезда'}</strong>.`;

  const monthBox = doc.createElement('div');
  monthBox.className = 'calendar-month calendar-month--single';

  const weekdays = doc.createElement('div');
  weekdays.className = 'calendar-weekdays';
  weekdayShort.forEach(function(day){
    const span = doc.createElement('span');
    span.textContent = day;
    weekdays.appendChild(span);
  });

  const days = doc.createElement('div');
  days.className = 'calendar-days';

  for(let i = 0; i < startWeekday; i++){
    const empty = doc.createElement('span');
    empty.className = 'calendar-day calendar-day--empty';
    empty.setAttribute('aria-hidden', 'true');
    days.appendChild(empty);
  }

  for(let day = 1; day <= daysInMonth; day++){
    const dt = new Date(year, month, day);
    const key = toYmd(dt);
    const past = isPastDate(key);
    const closedDay = closed.has(key);
    const beforeStart = mode === 'checkout' && state.checkin && compareYmd(key, state.checkin) <= 0;
    const blockedRange = mode === 'checkout' && state.checkin && !beforeStart && rangeHasClosed(state.checkin, key, state.currentHouse);
    const disabled = past || closedDay || beforeStart || blockedRange;
    const selected = key === state.checkin || key === state.checkout;
    const start = key === state.checkin;
    const end = key === state.checkout;
    const inRange = state.checkin && state.checkout && compareYmd(key, state.checkin) > 0 && compareYmd(key, state.checkout) < 0;
    const candidate = mode === 'checkout' && state.checkin && !state.checkout && !disabled && compareYmd(key, state.checkin) > 0;

    const el = doc.createElement(disabled ? 'span' : 'button');
    if(!disabled){
      el.type = 'button';
      el.dataset.date = key;
      el.addEventListener('click', function(ev){
        ev.preventDefault();
        ev.stopPropagation();
        handleDateSelection(key);
      });
    }
    el.className = 'calendar-day';
    if(disabled) el.classList.add('is-disabled');
    if(closedDay) el.classList.add('is-closed');
    if(beforeStart) el.classList.add('is-before-start');
    if(selected) el.classList.add('is-selected');
    if(start) el.classList.add('is-start');
    if(end) el.classList.add('is-end');
    if(inRange) el.classList.add('is-range');
    if(candidate) el.classList.add('is-checkout-candidate');
    if(blockedRange) el.classList.add('is-blocked-range');
    if(disabled) el.setAttribute('aria-disabled', 'true');
    el.textContent = String(day);
    days.appendChild(el);
  }

  monthBox.append(weekdays, days);

  const legend = doc.createElement('div');
  legend.className = 'calendar-legend';
  legend.innerHTML = '<span><i class="legend-box"></i> свободно</span><span><i class="legend-box legend-box--selected"></i> выбрано</span><span><i class="legend-box legend-box--closed"></i> закрыто</span>';

  calendarRoot.append(head, hint, monthBox, legend);
}

function renderMiniGallery(node, images, title, klass='details-gallery'){
    if(!node) return;
    const gallery = (images || []).filter(Boolean);
    if(!gallery.length){ node.innerHTML=''; return; }

    const body = document.body || document.documentElement;
    const rawLimit = klass === 'booking-gallery'
      ? Number(body.dataset.bookingThumbLimit || 6)
      : Number(body.dataset.detailsThumbLimit || 8);
    const compactLimit = Math.max(2, Math.min(24, rawLimit || (klass === 'booking-gallery' ? 6 : 8)));

    const renderMain = (src) => `
      <div class="${klass}__main">
        <button type="button" class="${klass}__zoom" data-open-lightbox="${src}" data-force-open-lightbox aria-label="Увеличить фото">
          <img src="${src}" alt="${title}">
        </button>
      </div>`;

    const needsToggle = gallery.length > compactLimit;

    node.innerHTML = `
      ${renderMain(gallery[0])}
      <div class="${klass}__thumbs" role="tablist" aria-label="Фотографии ${title}">
        ${gallery.map((src,i)=>`<button type="button" data-gallery-thumb="${src}" class="${i===0?'is-active':''}" aria-label="Фото ${i+1}"><img src="${src}" alt="${title} ${i+1}" loading="lazy"></button>`).join('')}
      </div>
      ${needsToggle ? `<button type="button" class="gallery-toggle" data-gallery-toggle aria-expanded="false">Показать все фото (${gallery.length})</button>` : ''}`;

    const updateMain = (src) => {
      const wrapper = qs(`.${klass}__main`, node);
      if(wrapper){ wrapper.outerHTML = renderMain(src); }
    };

    qsa('[data-gallery-thumb]', node).forEach((btn, index)=>btn.addEventListener('click', (e)=>{
      e.preventDefault();
      e.stopPropagation();
      updateMain(btn.dataset.galleryThumb);
      qsa('[data-gallery-thumb]', node).forEach(el=>el.classList.toggle('is-active', el===btn));
      if(toggle && collapsed && index >= compactLimit){
        collapsed = false;
        applyCollapsed();
      }
    }));

    const toggle = qs('[data-gallery-toggle]', node);
    let collapsed = !!needsToggle;
    const thumbs = qsa('[data-gallery-thumb]', node);

    function applyCollapsed(){
      thumbs.forEach((thumb, index) => {
        thumb.hidden = collapsed && index >= compactLimit;
      });
      if(toggle){
        toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
        toggle.textContent = collapsed ? `Показать все фото (${gallery.length})` : 'Свернуть фото';
      }
    }

    if(toggle){
      applyCollapsed();
      toggle.addEventListener('click', ()=>{
        collapsed = !collapsed;
        applyCollapsed();
      });
    }
  }

  function renderHouseDetails(house){
    const item = getHouse(house);
    state.currentHouse = item ? item.title : (house || '');
    if(houseSelect) houseSelect.value = state.currentHouse;
    if(houseTitleNode) houseTitleNode.textContent = state.currentHouse || 'Забронировать отдых';
    if(houseDescNode) houseDescNode.textContent = (item && (item.full_description || item.description)) || 'Выберите домик, чтобы увидеть описание и фото.';
    if(houseMetaNode){
      const badges=[];
      ['meta','capacity','area','view'].forEach(key => { if(item && item[key]) badges.push(`<span>${item[key]}</span>`); });
      houseMetaNode.innerHTML = badges.join('');
    }
    if(houseFeaturesNode){
      const features = (item && item.features) || [];
      houseFeaturesNode.innerHTML = features.map(f=>`<span>${f}</span>`).join('');
    }
    if(houseTimesNode){
      const times = [];
      if(item && item.checkin_time) times.push(`<span>Заезд: ${item.checkin_time}</span>`);
      if(item && item.checkout_time) times.push(`<span>Выезд: ${item.checkout_time}</span>`);
      houseTimesNode.innerHTML = times.join('');
      houseTimesNode.hidden = times.length === 0;
    }
    if(houseNoteNode){
      const note = item && item.booking_note ? String(item.booking_note) : '';
      houseNoteNode.innerHTML = note ? formatRichText(note) : '';
      houseNoteNode.hidden = !note;
    }
    const gallery = item && item.gallery && item.gallery.length ? item.gallery : (item && item.image ? [item.image] : []);
    renderMiniGallery(houseGalleryNode, gallery, state.currentHouse || 'Домик', 'booking-gallery');

    if(state.checkin && state.checkout && rangeHasClosed(state.checkin, state.checkout, state.currentHouse)){
      setRange('', '');
    } else {
      updateSummary();
      renderCalendar();
    }
  }

  function openDetails(house){
    const item = getHouse(house);
    const refs = getDetailsRefs();
    const modal = refs.modal;
    if(!modal || !item){
      console.warn('openDetails: домик не найден', house);
      return;
    }
    const gallery = item.gallery && item.gallery.length ? item.gallery : [item.image].filter(Boolean);
    if(refs.title) refs.title.textContent = item.title || 'Домик';
    if(refs.meta){
      const badges=[]; ['meta','capacity','area','view'].forEach(key=>{ if(item[key]) badges.push(`<span>${item[key]}</span>`); });
      refs.meta.innerHTML = badges.join('');
    }
    if(refs.description) refs.description.textContent = item.full_description || item.description || '';
    if(refs.inside) refs.inside.innerHTML = formatRichText(item.inside_text || '');
    if(refs.territory) refs.territory.innerHTML = formatRichText(item.territory_text || '');
    if(refs.times){
      const times = [];
      if(item.checkin_time) times.push(`<span>Заезд: ${item.checkin_time}</span>`);
      if(item.checkout_time) times.push(`<span>Выезд: ${item.checkout_time}</span>`);
      refs.times.innerHTML = times.join('');
      refs.times.hidden = times.length === 0;
    }
    if(refs.features) refs.features.innerHTML = ((item.features||[]).map(x=>`<span>${x}</span>`)).join('');
    if(refs.pros){
      const pros = [
        item.capacity ? `Комфортное размещение: ${item.capacity}` : '',
        item.area ? `Формат: ${item.area}` : '',
        item.view ? `Подойдёт для сценария: ${item.view}` : '',
        item.price ? `Стоимость: ${item.price}` : ''
      ].filter(Boolean);
      refs.pros.innerHTML = pros.map(x=>`<span>${x}</span>`).join('');
    }
    renderMiniGallery(refs.gallery, gallery, item.title || 'Домик', 'details-gallery');
    if(refs.bookingBtn) refs.bookingBtn.dataset.house = item.title || '';
    const dialog = qs('.modal__dialog--details', modal);
    if(dialog) dialog.scrollTop = 0;
    modal.hidden = false;
    modal.setAttribute('aria-hidden','false');
    syncMobileDetailsMotion(true);
    doc.body.classList.add('is-locked');
  }

  function openBooking(house=''){
    if(!bookingModal) return;
    bookingModal.hidden = false;
    bookingModal.setAttribute('aria-hidden','false');
    doc.body.classList.add('is-locked');
    renderHouseDetails(house || state.currentHouse || ((housesCollection.items||[])[0] && (housesCollection.items||[])[0].title) || '');
  }
  window.SiteOpenHouseDetails = openDetails;
  window.SiteOpenBooking = openBooking;


  window.SiteOpenHouseDetails = function(house){
    try{
      openDetails(house || '');
    }catch(err){
      console.error('SiteOpenHouseDetails error', err);
    }
  };
  window.SiteOpenBooking = function(house){
    try{
      openBooking(house || '');
    }catch(err){
      console.error('SiteOpenBooking error', err);
    }
  };

  function closeBooking(){
    if(bookingModal){ bookingModal.hidden=true; bookingModal.setAttribute('aria-hidden','true'); }
    if((!detailsModal || detailsModal.hidden) && (!lightboxModal || lightboxModal.hidden)) doc.body.classList.remove('is-locked');
  }
  function closeDetails(){
    const modal = qs('[data-details-modal]') || detailsModal;
    if(modal){
      modal.classList.remove('is-mobile-open','is-mobile-opening');
      modal.hidden=true;
      modal.setAttribute('aria-hidden','true');
    }
    if((!bookingModal || bookingModal.hidden) && (!lightboxModal || lightboxModal.hidden)) doc.body.classList.remove('is-locked');
  }
  function collectLightboxItems(trigger){
    const scope = trigger.closest('.review-card, .gallery-carousel, .details-layout__gallery, .booking-aside, .house-slider, .gallery-track, .complex-map-card') || doc;
    let items = [];
    const direct = qsa('[data-open-lightbox]', scope).map(btn => btn.dataset.openLightbox).filter(Boolean);
    const thumbs = qsa('[data-gallery-thumb]', scope).map(btn => btn.dataset.galleryThumb).filter(Boolean);
    items = [...direct, ...thumbs].filter((src, i, arr) => arr.indexOf(src) === i);
    if(!items.length){
      const currentOnly = trigger.dataset.openLightbox || trigger.dataset.galleryThumb || '';
      items = currentOnly ? [currentOnly] : [];
    }
    const current = trigger.dataset.openLightbox || trigger.dataset.galleryThumb || '';
    let index = Math.max(0, items.indexOf(current));
    return {items, index};
  }

  function renderLightbox(){
    if(!lightboxImage || !lightboxModal) return;
    const src = lightboxState.items[lightboxState.index] || '';
    lightboxModal.removeAttribute('data-orientation');
    lightboxImage.removeAttribute('width');
    lightboxImage.removeAttribute('height');
    lightboxImage.style.width = 'auto';
    lightboxImage.style.height = 'auto';
    lightboxImage.src = src;
    const prevBtn = qs('[data-lightbox-prev]');
    const nextBtn = qs('[data-lightbox-next]');
    const counter = qs('[data-lightbox-counter]');
    const multi = lightboxState.items.length > 1;
    lightboxModal.classList.toggle('is-single', !multi);
    if(prevBtn) prevBtn.hidden = !multi;
    if(nextBtn) nextBtn.hidden = !multi;
    if(counter) counter.textContent = multi ? `${lightboxState.index + 1} / ${lightboxState.items.length}` : '';
    lightboxImage.onload = function(){
      const w = lightboxImage.naturalWidth || 0;
      const h = lightboxImage.naturalHeight || 0;
      lightboxModal.setAttribute('data-orientation', h > w ? 'portrait' : 'landscape');
      if(lightboxModal.classList.contains('is-territory-map')){
        const stage = qs('.lightbox-stage', lightboxModal);
        if(stage && window.matchMedia && window.matchMedia('(max-width: 700px)').matches){
          window.setTimeout(function(){
            stage.scrollLeft = Math.max(0, (stage.scrollWidth - stage.clientWidth) / 2);
            stage.scrollTop = 0;
          }, 40);
        }
      }
    };
  }

  function openLightboxFromTrigger(trigger){
    if(!lightboxModal || !trigger) return;
    const set = collectLightboxItems(trigger);
    const isTerritoryMap = trigger.dataset.lightboxKind === 'territory-map' || !!trigger.closest('.complex-map-card--image');
    lightboxModal.classList.toggle('is-territory-map', isTerritoryMap);
    lightboxState.items = set.items;
    lightboxState.index = set.index;
    renderLightbox();
    lightboxModal.hidden = false;
    lightboxModal.setAttribute('aria-hidden','false');
    doc.body.classList.add('is-locked');
  }
  function stepLightbox(direction){
    if(lightboxState.items.length < 2) return;
    lightboxState.index = (lightboxState.index + direction + lightboxState.items.length) % lightboxState.items.length;
    renderLightbox();
  }
  function closeLightbox(){
    if(!lightboxModal) return;
    lightboxModal.hidden = true;
    lightboxModal.setAttribute('aria-hidden','true');
    lightboxState.items = [];
    lightboxState.index = 0;
    if(lightboxImage) lightboxImage.src = '';
    if((!bookingModal || bookingModal.hidden) && (!detailsModal || detailsModal.hidden)) doc.body.classList.remove('is-locked');
  }

  function formatRuLong(dateStr){
    if(!dateStr) return '';
    const d = parseYmd(dateStr);
    return d.toLocaleDateString('ru-RU', { day:'numeric', month:'long' });
  }
  function applyTemplate(tpl, payload){
    return String(tpl || '').replace(/\{(\w+)\}/g, (_, key)=> payload[key] == null ? '' : payload[key]);
  }
  function openBookingSuccess(message, payload={}){
    if(!bookingSuccessModal || bookingSuccessConfig.enabled === false) return;
    if(bookingSuccessTitleNode) bookingSuccessTitleNode.textContent = bookingSuccessConfig.title || 'Спасибо за заявку';
    const merged = Object.assign({
      house: payload.house || state.currentHouse || '',
      checkin: payload.checkin || state.checkin || '',
      checkout: payload.checkout || state.checkout || '',
      checkin_ru: formatRuLong(payload.checkin || state.checkin || ''),
      checkout_ru: formatRuLong(payload.checkout || state.checkout || ''),
    }, payload || {});
    if(bookingSuccessTextNode) bookingSuccessTextNode.textContent = applyTemplate(bookingSuccessConfig.details_template || message || bookingSuccessConfig.text || '', merged) || message || bookingSuccessConfig.text || 'Мы скоро с вами свяжемся.';
    const seconds = Math.max(1, Math.round((Number(bookingSuccessConfig.duration_ms) || 10000) / 1000));
    if(bookingSuccessSecondsNode) bookingSuccessSecondsNode.textContent = String(seconds);
    bookingSuccessModal.hidden = false;
    bookingSuccessModal.setAttribute('aria-hidden','false');
    doc.body.classList.add('is-locked');
    window.clearTimeout(bookingSuccessTimer);
    bookingSuccessTimer = window.setTimeout(closeBookingSuccess, Math.max(1000, Number(bookingSuccessConfig.duration_ms) || 10000));
  }
  function closeBookingSuccess(){
    if(!bookingSuccessModal) return;
    bookingSuccessModal.hidden = true;
    bookingSuccessModal.setAttribute('aria-hidden','true');
    window.clearTimeout(bookingSuccessTimer);
    bookingSuccessTimer = 0;
    if((!bookingModal || bookingModal.hidden) && (!detailsModal || detailsModal.hidden) && (!lightboxModal || lightboxModal.hidden)) doc.body.classList.remove('is-locked');
  }

  if (burger && mobileNav) burger.addEventListener('click', ()=> mobileNav.classList.toggle('is-open'));

  doc.addEventListener('click', (e)=>{
    const openBtn = e.target.closest('[data-open-booking]');
    if(openBtn){ e.preventDefault(); openBooking(openBtn.dataset.house || ''); return; }
    const detailsBtn = e.target.closest('[data-open-details]');
    if(detailsBtn){ e.preventDefault(); openDetails(detailsBtn.dataset.house || ''); return; }
    if(e.target.closest('[data-close-booking]')){ e.preventDefault(); closeBooking(); return; }
    if(e.target.closest('[data-close-details]')){ e.preventDefault(); closeDetails(); return; }
    if(e.target.closest('[data-close-lightbox]')){ e.preventDefault(); closeLightbox(); return; }
    if(e.target.closest('[data-close-booking-success]')){ e.preventDefault(); closeBookingSuccess(); return; }
    const detailsBooking = e.target.closest('[data-details-booking]');
    if(detailsBooking){ e.preventDefault(); closeDetails(); openBooking(detailsBooking.dataset.house || ''); return; }
    const lightboxBtn = e.target.closest('[data-open-lightbox]');
    if(lightboxBtn){
      const houseCard = lightboxBtn.closest('[data-house-card]');
      if(houseCard){
        e.preventDefault();
        openDetails(houseCard.getAttribute('data-house-card') || '');
        return;
      }
      e.preventDefault();
      openLightboxFromTrigger(lightboxBtn);
      return;
    }
    if(e.target.closest('[data-lightbox-prev]')){ e.preventDefault(); stepLightbox(-1); return; }
    if(e.target.closest('[data-lightbox-next]')){ e.preventDefault(); stepLightbox(1); return; }
  });

  doc.addEventListener('keydown', (e)=>{ if(e.key==='Escape'){ closeBooking(); closeDetails(); closeLightbox(); closeBookingSuccess(); } if(e.key==='ArrowLeft' && lightboxModal && !lightboxModal.hidden) stepLightbox(-1); if(e.key==='ArrowRight' && lightboxModal && !lightboxModal.hidden) stepLightbox(1); });



  if(calendarRoot){
    ['click','mousedown','mouseup','touchstart','touchend','pointerdown','pointerup'].forEach(function(evt){
      calendarRoot.addEventListener(evt, function(e){
        const nav = e.target.closest('.calendar-head__nav');
        const dayBtn = e.target.closest('button.calendar-day[data-date]');
        if(nav || dayBtn) return;
        e.preventDefault();
        e.stopPropagation();
      }, true);
    });
  }

  if(houseSelect) houseSelect.addEventListener('change', ()=>{ if(houseSelect.value){ setRange('',''); renderHouseDetails(houseSelect.value); } });

  function setupSlider(slider){
    const slides=qsa('.house-slide', slider);
    const dots=qsa('[data-slide-dot]', slider);
    let index=0;
    if(!slides.length) return;
    function show(i){
      index=(i+slides.length)%slides.length;
      slides.forEach((slide,idx)=>slide.classList.toggle('is-active', idx===index));
      dots.forEach((dot,idx)=>dot.classList.toggle('is-active', idx===index));
    }
    const slidePrev = qs('[data-slide-prev]', slider);
    const slideNext = qs('[data-slide-next]', slider);
    if(slidePrev) slidePrev.addEventListener('click', ()=>show(index-1));
    if(slideNext) slideNext.addEventListener('click', ()=>show(index+1));
    dots.forEach(dot=>dot.addEventListener('click', ()=>show(Number(dot.dataset.slideDot||0))));
    let startX=0;
    slider.addEventListener('touchstart', e=>{ startX=e.touches[0].clientX; }, {passive:true});
    slider.addEventListener('touchend', e=>{
      const changedTouch = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0] : null;
      const diff=((changedTouch && changedTouch.clientX) || 0)-startX;
      if(Math.abs(diff)<30) return;
      show(index + (diff<0 ? 1 : -1));
    }, {passive:true});
  }
  qsa('[data-house-slider]').forEach(setupSlider);

  function bindCardAction(selector, handler){
    qsa(selector).forEach(node=>{
      node.addEventListener('click', (e)=>{
        e.preventDefault();
        const house = node.getAttribute('data-house') || node.dataset.house || '';
        handler(house);
      });
    });
  }


  function setupMobileHousePreviews(){
    const isMobile = window.matchMedia('(max-width: 768px)').matches;
    qsa('[data-house-card]').forEach(card => {
      const existing = qs('.house-mobile-preview', card);
      const slider = qs('[data-house-slider]', card);
      const previewImage = qs('.house-slide img', card);
      const src = card.getAttribute('data-mobile-preview-src') || (previewImage ? previewImage.getAttribute('src') : '') || '';
      if(isMobile){
        card.classList.add('has-mobile-preview');
        if(!existing && slider && src){
          const house = card.getAttribute('data-house-card') || '';
          const btn = doc.createElement('button');
          btn.type = 'button';
          btn.className = 'house-mobile-preview';
          btn.setAttribute('data-open-details','');
          btn.setAttribute('data-house', house);
          btn.setAttribute('aria-label', `Подробнее о домике ${house}`);
          btn.innerHTML = `<img src="${src}" alt="${house} — превью" loading="lazy">`;
          slider.parentNode.insertBefore(btn, slider);
        }
      } else {
        card.classList.remove('has-mobile-preview');
        if(existing) existing.remove();
      }
    });
  }

  bindCardAction('[data-open-details]', openDetails);
  bindCardAction('[data-open-booking]', openBooking);

  doc.addEventListener('click', (e)=>{
    const detailsTrigger = e.target.closest('[data-open-details], [data-force-open-details]');
    if(detailsTrigger){
      e.preventDefault();
      e.stopPropagation();
      const detailsCard = detailsTrigger.closest('[data-house-card]');
      const house = detailsTrigger.getAttribute('data-house') || detailsTrigger.dataset.house || (detailsCard ? detailsCard.getAttribute('data-house-card') : '') || '';
      openDetails(house);
      return;
    }
    const bookingTrigger = e.target.closest('[data-open-booking], [data-force-open-booking]');
    if(bookingTrigger){
      e.preventDefault();
      e.stopPropagation();
      const bookingCard = bookingTrigger.closest('[data-house-card]');
      const house = bookingTrigger.getAttribute('data-house') || bookingTrigger.dataset.house || (bookingCard ? bookingCard.getAttribute('data-house-card') : '') || '';
      openBooking(house);
      return;
    }
    const detailsBooking = e.target.closest('[data-details-booking]');
    if(detailsBooking){
      e.preventDefault();
      e.stopPropagation();
      closeDetails();
      openBooking(detailsBooking.dataset.house || state.currentHouse || '');
    }
  }, true);

  setupMobileHousePreviews();
  window.addEventListener('resize', setupMobileHousePreviews);

  function syncMobileDetailsMotion(open){
    if(!detailsModal) return;
    const isMobile = window.matchMedia('(max-width: 760px)').matches;
    detailsModal.classList.remove('is-mobile-opening','is-mobile-open');
    if(open && isMobile){
      detailsModal.classList.add('is-mobile-opening');
      requestAnimationFrame(()=>{ detailsModal.classList.add('is-mobile-open'); });
    }
  }

  qsa('[data-gallery-carousel]').forEach(carousel=>{
    const track=qs('[data-gallery-track]', carousel);
    if(!track) return;
    const step=()=> Math.min(track.clientWidth * 0.9, 420);
    const galleryPrev = qs('[data-gallery-prev]', carousel);
    const galleryNext = qs('[data-gallery-next]', carousel);
    if(galleryPrev) galleryPrev.addEventListener('click', ()=>track.scrollBy({left:-step(),behavior:'smooth'}));
    if(galleryNext) galleryNext.addEventListener('click', ()=>track.scrollBy({left:step(),behavior:'smooth'}));
  });

  function xhrHttpRequest(url, options){
    return new Promise((resolve, reject)=>{
      const xhr = new XMLHttpRequest();
      xhr.open(options.method || 'GET', url, true);
      xhr.withCredentials = true;
      xhr.timeout = options.timeout || 35000;
      const headers = options.headers || {};
      Object.keys(headers).forEach(name=>xhr.setRequestHeader(name, headers[name]));
      xhr.onload = ()=>resolve({ok:xhr.status >= 200 && xhr.status < 300, status:xhr.status, text:xhr.responseText || ''});
      xhr.onerror = ()=>reject(new Error('Не удалось соединиться с сервером. Проверьте интернет и попробуйте ещё раз.'));
      xhr.ontimeout = ()=>{
        const error = new Error('Сервер долго не отвечает. Попробуйте ещё раз.');
        error.name = 'AbortError';
        reject(error);
      };
      xhr.send(options.body || null);
    });
  }

  async function bookingHttpRequest(url, options){
    const requestOptions = options || {};
    if(typeof window.fetch === 'function'){
      const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
      const timeoutId = controller ? window.setTimeout(()=>controller.abort(), requestOptions.timeout || 35000) : 0;
      try {
        const response = await window.fetch(url, {
          method: requestOptions.method || 'GET',
          body: requestOptions.body || null,
          credentials: 'same-origin',
          cache: 'no-store',
          headers: requestOptions.headers || {},
          signal: controller ? controller.signal : undefined
        });
        const text = await response.text();
        return {ok:response.ok, status:response.status, text};
      } catch(fetchError) {
        if(fetchError && fetchError.name === 'AbortError') throw fetchError;
        console.warn('Fetch failed, retrying with XMLHttpRequest', fetchError);
      } finally {
        if(timeoutId) window.clearTimeout(timeoutId);
      }
    }
    return xhrHttpRequest(url, requestOptions);
  }

  function updateGuardField(name, value){
    const field = bookingField(name);
    if(field && typeof value === 'string') field.value = value;
  }

  async function refreshBookingGuard(){
    if(!form) return false;
    const endpoint = form.getAttribute('data-token-endpoint') || '/form-token.php';
    try {
      const response = await bookingHttpRequest(endpoint, {
        method:'GET',
        headers:{'Accept':'application/json','X-Requested-With':'XMLHttpRequest'},
        timeout:15000
      });
      const payload = response.text ? JSON.parse(response.text) : null;
      if(!response.ok || !payload || !payload.ok) return false;
      updateGuardField('csrf_token', String(payload.csrf_token || ''));
      updateGuardField('form_issued_at', String(payload.form_issued_at || ''));
      updateGuardField('form_token', String(payload.form_token || ''));
      updateGuardField('form_signature', String(payload.form_signature || ''));
      if(jsEnabledField) jsEnabledField.value = '1';
      return true;
    } catch(error) {
      console.warn('Booking form token refresh failed', error);
      return false;
    }
  }

  async function submitBooking(){
    if(!form || bookingSubmitting) return;
    markBookingInteraction();
    if(!state.currentHouse && houseSelect && houseSelect.value) state.currentHouse = houseSelect.value;
    if(!validateBookingForm()) return;

    setBookingSubmitting(true);
    showFormMessage('Отправляем заявку…', false);
    let guardRefreshed = false;

    try {
      let json = null;
      let response = null;
      while(true){
        const data = new FormData(form);
        data.set('house', state.currentHouse);
        data.set('checkin', state.checkin);
        data.set('checkout', state.checkout);
        const actionUrl = (form.getAttribute('action') || '/send.php').trim() || '/send.php';
        response = await bookingHttpRequest(actionUrl, {
          method:'POST',
          body:data,
          headers:{'X-Requested-With':'XMLHttpRequest','Accept':'application/json'},
          timeout:35000
        });
        try {
          json = response.text ? JSON.parse(response.text) : null;
        } catch(parseErr) {
          console.error('Booking response parse error', parseErr, response.text);
          throw new Error('Сервер вернул некорректный ответ. Попробуйте ещё раз или сообщите администратору.');
        }

        if(response.status === 419 && !guardRefreshed){
          guardRefreshed = await refreshBookingGuard();
          if(guardRefreshed) continue;
        }
        break;
      }

      if(!json || !response || !response.ok || !json.ok){
        const fieldErrorsApplied = json && applyServerFieldErrors(json.fields);
        const submitError = new Error((json && json.message) || 'Ошибка отправки');
        submitError.fieldErrorsApplied = !!fieldErrorsApplied;
        throw submitError;
      }

      const bookingPayload = json.booking || {house: state.currentHouse, checkin: state.checkin, checkout: state.checkout};
      const redirectUrl = String(json.redirect_url || '').trim();
      if(!json.duplicate && !json.filtered) confirmAutomaticFormGoal();
      showFormMessage(json.duplicate ? 'Заявка уже была отправлена — повторно не дублируем.' : 'Заявка успешно отправлена.');
      setBookingSubmitting(false);
      form.reset();
      if(jsEnabledField) jsEnabledField.value = '1';
      if(interactionStartedField) interactionStartedField.value = '0';
      const keepHouse = state.currentHouse;
      setRange('','');
      renderHouseDetails(keepHouse);
      closeBooking();
      if(redirectUrl){
        try {
          window.location.assign(redirectUrl);
          return;
        } catch(navErr) {
          console.error('Booking redirect error', navErr, redirectUrl);
        }
      }
      await refreshBookingGuard();
      openBookingSuccess(json.success_text || json.message || bookingSuccessConfig.text || 'Мы скоро с вами свяжемся.', bookingPayload);
    } catch(err){
      console.error(err);
      const message = err && err.name === 'AbortError' ? 'Сервер долго не отвечает. Попробуйте ещё раз.' : ((err && err.message) || 'Не удалось отправить заявку.');
      if(!err || !err.fieldErrorsApplied) showFormMessage(message, true);
      setBookingSubmitting(false);
    }
  }

  if(form){
    form.addEventListener('pointerdown', markBookingInteraction, {passive:true});
    form.addEventListener('input', (e)=>{
      markBookingInteraction();
      const node = e.target;
      const fieldName = node && node.name ? node.name : '';
      const key = (fieldName === 'checkin' || fieldName === 'checkout' || fieldName === 'dates') ? 'dates' : fieldName;
      if(key) clearBookingFieldError(key);
      showFormMessage('', false);
    });
    form.addEventListener('change', (e)=>{
      markBookingInteraction();
      const node = e.target;
      const fieldName = node && node.name ? node.name : '';
      const key = (fieldName === 'checkin' || fieldName === 'checkout' || fieldName === 'dates') ? 'dates' : fieldName;
      if(key) clearBookingFieldError(key);
      showFormMessage('', false);
    });
    form.addEventListener('keydown', (e)=>{
      if(e.key !== 'Enter' || (e.target && e.target.tagName === 'TEXTAREA')) return;
      e.preventDefault();
      submitBooking();
    }, true);
    form.addEventListener('submit', (e)=>{
      e.preventDefault();
      if(metrikaGoalOnlySubmit) return;
      submitBooking();
    });
  }
  if(submitButton) submitButton.addEventListener('click', submitBooking);

  window.addEventListener('pageshow', (event)=>{
    setBookingSubmitting(false);
    let isBackForward = !!(event && event.persisted);
    try {
      const entries = window.performance && typeof window.performance.getEntriesByType === 'function' ? window.performance.getEntriesByType('navigation') : [];
      if(entries && entries[0] && entries[0].type === 'back_forward') isBackForward = true;
    } catch(error) {}
    if(isBackForward) refreshBookingGuard();
  });



  function addDays(date, days){ const d = new Date(date); d.setDate(d.getDate()+days); return d; }
  function nextAvailableDate(fromDate, house){
    let d = new Date(fromDate);
    for(let i=0;i<366;i++){
      const key = toYmd(d);
      if(!isPastDate(key) && !isClosedDate(key, house)) return key;
      d = addDays(d,1);
    }
    return '';
  }
  function applyQuickRange(mode){
    const today = new Date(); today.setHours(0,0,0,0);
    let start = new Date(today);
    let nights = Number(mode);
    if(mode === 'weekend'){
      const day = start.getDay();
      const add = day <= 5 ? (5 - day + 7) % 7 : 6;
      start = addDays(start, add === 0 ? 7 : add);
      nights = 2;
    }
    const checkin = nextAvailableDate(start, state.currentHouse);
    if(!checkin) return;
    let checkout = nextAvailableDate(addDays(parseYmd(checkin), nights), state.currentHouse);
    if(!checkout || compareYmd(checkout, checkin) <= 0){
      checkout = nextAvailableDate(addDays(parseYmd(checkin), nights + 1), state.currentHouse);
    }
    if(checkout){
      setRange(checkin, checkout);
      showFormMessage('');
    }
  }

  function track(action, details=''){
    if(!window.fetch) return;
    const body = new URLSearchParams({action, page: window.TRACK_PAGE || '', details});
    fetch('/track.php', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body, keepalive:true}).catch(()=>{});
  }
  track('heartbeat','init');
  setInterval(()=>track('heartbeat','ping'), 20000);
  qsa('[data-open-booking]').forEach(btn=>btn.addEventListener('click', ()=>track('booking_open', btn.dataset.house || 'all')));
  qsa('a[href^="#"]').forEach(link=>link.addEventListener('click', ()=>track('anchor_click', link.getAttribute('href') || '')));

  const firstHouse = (houseSelect && houseSelect.value) || ((housesCollection.items||[])[0] && (housesCollection.items||[])[0].title) || '';
  if(firstHouse){ try { renderHouseDetails(firstHouse); } catch(initErr){ console.error('Initial renderHouseDetails failed', initErr); } }

  window.SiteOpenAnimalsInfo = function(){ const modal = qs('[data-animals-modal]'); if(modal){ modal.hidden = false; modal.setAttribute('aria-hidden','false'); document.body.classList.add('is-locked'); } };


  const getAnimalsModal = ()=> qs('[data-animals-modal]');
  const animalsModal = getAnimalsModal();
  const animalsOpeners = qsa('[data-open-animals-popup]');

  const syncBodyLock = ()=>{
    const anyOpen =
      (bookingModal && !bookingModal.hidden) ||
      (detailsModal && !detailsModal.hidden) ||
      (lightboxModal && !lightboxModal.hidden) ||
      (animalsModal && !animalsModal.hidden);
    doc.body.classList.toggle('is-locked', !!anyOpen);
  };

  const closeAnimals = ()=>{
    const modal = getAnimalsModal();
    if(!modal) return;
    modal.hidden = true;
    modal.setAttribute('aria-hidden', 'true');
    syncBodyLock();
  };

  const openAnimals = ()=>{
    const modal = getAnimalsModal();
    if(!modal) return;
    modal.hidden = false;
    modal.setAttribute('aria-hidden', 'false');
    syncBodyLock();
  };

  window.SiteOpenAnimalsInfo = openAnimals;
  window.SiteCloseAnimalsInfo = closeAnimals;

  animalsOpeners.forEach(btn=>btn.addEventListener('click', (e)=>{
    e.preventDefault();
    e.stopPropagation();
    openAnimals();
  }));

  if(animalsModal){
    qsa('[data-close-animals]', animalsModal).forEach(btn=>btn.addEventListener('click', (e)=>{
      e.preventDefault();
      e.stopPropagation();
      closeAnimals();
    }));

    animalsModal.addEventListener('click', (e)=>{
      const target = e.target;
      if(target && (target.matches('[data-close-animals]') || target === animalsModal)){
        e.preventDefault();
        e.stopPropagation();
        closeAnimals();
      }
    });
  }

  doc.addEventListener('keydown', (e)=>{
    if(e.key === 'Escape' && animalsModal && !animalsModal.hidden){
      closeAnimals();
    }
  });



})();
