PageSourceSearch

https://static.zarpo.com.br/static/js/kombi-all.666a1efb423a.js

js zarpo.com.br collected 2026-10-02 04:48:51 UTC 221,236 bytes, 5,857 lines download raw bytes

1var kombi = {
2  getOnlyDigits: function (text) {
3    return text.replace(/\D/g, "");
4  },
5
6  getCheckoutCartId: function () {
7    var byId = document.getElementById("cart_id");
8    if (byId && byId.value) {
9      return byId.value;
10    }
11    var byClass = document.querySelector("input.cart_id");
12    if (byClass && byClass.value) {
13      return byClass.value;
14    }
15    var match = window.location.pathname.match(
16      /\/(?:one-page-checkout|payment|travelers|quotation)\/([0-9a-fA-F-]{36})(?:\/|$)/
17    );
18    return match ? match[1] : "";
19  },
20
21  getBoolAttr: function (el, attr, _default) {
22    let value = el.getAttribute(attr);
23    if (value !== null) {
24      return value.toLowerCase() == "true";
25    }
26    return _default;
27  },
28
29  disableOnClick: function (el) {
30    if (el.tagName === "A") {
31      el.classList.add("disabled");
32      el.style.pointerEvents = "none";
33    } else {
34      el.disabled = true;
35    }
36  },
37
38  initTooltips: function (container) {
39    if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) return;
40    const root = container || document;
41    const trigger = "hover focus click";
42    root.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) {
43      const existing = bootstrap.Tooltip.getInstance(el);
44      if (existing) existing.dispose();
45      new bootstrap.Tooltip(el, { trigger: trigger });
46    });
47  },
48};
49
50document.addEventListener("click", function (evt) {
51  if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) return;
52  const clickedTrigger = evt.target.closest("[data-bs-toggle='tooltip']");
53  const clickedInsideTooltip = evt.target.closest(".tooltip");
54
55  if (!clickedTrigger && !clickedInsideTooltip) {
56    document.querySelectorAll("[data-bs-toggle='tooltip']").forEach(function (el) {
57      const t = bootstrap.Tooltip.getInstance(el);
58      if (t) t.hide();
59    });
60  } else if (clickedTrigger) {
61    document.querySelectorAll("[data-bs-toggle='tooltip']").forEach(function (el) {
62      if (el !== clickedTrigger) {
63        const t = bootstrap.Tooltip.getInstance(el);
64        if (t) t.hide();
65      }
66    });
67  }
68}, true);
69
70document.addEventListener("DOMContentLoaded", function () {
71  kombi.searchTabs();
72  kombi.search();
73  kombi.searchBar();
74  kombi.flatpickr();
75  if (kombi.calendarMobile) kombi.calendarMobile();
76  if (kombi.cleaveMask) kombi.cleaveMask();           // ausente no hotel-detail bundle
77  kombi.activeLinks();
78  if (kombi.paymentPagBank) kombi.paymentPagBank();   // ausente no hotel-detail bundle
79  kombi.disableCollapseFields();
80  if (kombi.travelersFormValidation) kombi.travelersFormValidation(); // ausente no hotel-detail bundle
81  if (kombi.checkoutTimer) {                           // ausente no hotel-detail bundle
82    kombi.checkoutTimer.checkAvailability();
83    kombi.checkoutTimer.fixedDiv();
84  }
85  kombi.carousel();
86  if (kombi.reservation) kombi.reservation.navLinkActiveStyle(); // ausente no hotel-detail bundle
87  if (kombi.profile) kombi.profile.formatPhoneNumber();          // ausente no hotel-detail bundle
88  kombi.sidebar.collapseToggle();
89  kombi.sidebar.menuLinkActive();
90  kombi.sidebar.mobileDropdownModal();
91  kombi.sidebar.navBarFade();
92  kombi.toggleReadMore.init();
93  kombi.readMore();
94  kombi.flight.bindDeltaPricing();
95  kombi.flight.bindPackageCheckoutVisibility();
96  kombi.flight.initPackageCheckoutVisibility();
97  kombi.flight.calculateDeltaPricing();
98  kombi.flight.changeDateMobile();
99  if (kombi.reloadInstallments) kombi.reloadInstallments(); // ausente no hotel-detail bundle
100  window.onload = kombi.flight.changeDateMobile;
101  window.onresize = kombi.flight.changeDateMobile;
102  kombi.flight.changeDateDesktop();
103  kombi.accordion();
104  kombi.hotel();
105  kombi.packageSearchPollingLoader();
106  kombi.checkSearchHash();
107  kombi.travelSuitcaseNavLinkMobile();
108  kombi.travelSuitcaseNavLinkDesktop();
109  kombi.initTooltips();
110});
111
112// HTMX event guide (this project):
113// - afterOnLoad: UI reinits on new content (amenities, rooms, prices)
114// - afterSwap: one-shot body listeners via kombi.lifecycle.bindOnce / onBody
115// - afterSettle: settled DOM — tooltips, analytics
116const HTMX_AFTER_ON_LOAD = [
117  { run: function (root) { kombi.toggleReadMore.init(root); } },
118  { run: function (root) { kombi.readMore(root); } },
119  { run: function (root) { kombi.carousel(root); } },
120  { run: function () { kombi.flight.initPackageCheckoutVisibility(); } },
121  { run: function () { kombi.flight.calculateDeltaPricing(); } },
122  { run: function (root) { kombi.hotel(root); } },
123  { run: function () { kombi.packageSearchPollingLoader(); } },
124  { run: function () { kombi.showCartToastIfFull(); } },
125];
126
127document.body.addEventListener("htmx:afterOnLoad", function (event) {
128  const root = event.target;
129  HTMX_AFTER_ON_LOAD.forEach(function (entry) {
130    entry.run(root);
131  });
132});
133
134document.body.addEventListener("htmx:afterSettle", function (event) {
135  kombi.initTooltips(event.target);
136});
137
138const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
139const popoverList = [...popoverTriggerList].map(function (popoverTriggerEl) {
140  return new bootstrap.Popover(popoverTriggerEl);
141});
142;
143/**
144 * Shared HTMX lifecycle helpers (bind-once + per-element init guards).
145 * Consumed by storefront modules after DOMContentLoaded / htmx swaps.
146 */
147kombi.lifecycle = (function () {
148  const onceKeys = Object.create(null);
149  const DATASET_PREFIX = "kombiInit";
150  const VALID_KEY = /^[a-zA-Z0-9_-]+$/;
151
152  function isNonEmptyString(value) {
153    return typeof value === "string" && value.length > 0;
154  }
155
156  function hasDataset(el) {
157    return !!(el && el.dataset);
158  }
159
160  /**
161   * "room-toggle" → "kombiInitRoomToggle" (dataset / data-kombi-init-room-toggle).
162   */
163  function toDatasetKey(key) {
164    if (!isNonEmptyString(key) || !VALID_KEY.test(key)) {
165      return null;
166    }
167
168    const parts = key.split(/[-_]/).filter(Boolean);
169    if (!parts.length) {
170      return null;
171    }
172
173    const pascal = parts
174      .map(function (part) {
175        return part.charAt(0).toUpperCase() + part.slice(1);
176      })
177      .join("");
178
179    return DATASET_PREFIX + pascal;
180  }
181
182  function bindOnce(key, fn) {
183    if (!isNonEmptyString(key) || typeof fn !== "function") {
184      return;
185    }
186    if (onceKeys[key]) {
187      return;
188    }
189    onceKeys[key] = true;
190    fn();
191  }
192
193  function isInitialized(el, key) {
194    if (!hasDataset(el)) {
195      return false;
196    }
197    const datasetKey = toDatasetKey(key);
198    if (!datasetKey) {
199      return false;
200    }
201    return !!el.dataset[datasetKey];
202  }
203
204  function markInitialized(el, key) {
205    if (!hasDataset(el)) {
206      return;
207    }
208    const datasetKey = toDatasetKey(key);
209    if (!datasetKey) {
210      return;
211    }
212    el.dataset[datasetKey] = "true";
213  }
214
215  function initScope(root, fns) {
216    const scope = root || document;
217    if (!Array.isArray(fns)) {
218      return;
219    }
220    fns.forEach(function (fn) {
221      if (typeof fn === "function") {
222        fn(scope);
223      }
224    });
225  }
226
227  /**
228   * Register a single body listener via bindOnce.
229   * options: { targetId?, onceKey? }
230   * When targetId is set, handler runs only if event.detail.target.id matches.
231   */
232  function onBody(eventName, handler, options) {
233    if (!isNonEmptyString(eventName) || typeof handler !== "function") {
234      return;
235    }
236
237    const opts = options || {};
238    const targetId = opts.targetId;
239    const onceKey =
240      opts.onceKey || eventName + ":" + (targetId || "*");
241
242    bindOnce(onceKey, function () {
243      document.body.addEventListener(eventName, function (event) {
244        if (targetId) {
245          const eventTarget = event.detail && event.detail.target;
246          if (!eventTarget || eventTarget.id !== targetId) {
247            return;
248          }
249        }
250        handler(event);
251      });
252    });
253  }
254
255  return {
256    bindOnce: bindOnce,
257    isInitialized: isInitialized,
258    markInitialized: markInitialized,
259    initScope: initScope,
260    onBody: onBody,
261  };
262})();
263;
264kombi.cleaveMask = function () {
265  document.querySelectorAll("input.cleave").forEach((item) => {
266    let opts = {
267      creditCard: this.getBoolAttr(item, "data-credit-card", false),
268      phone: this.getBoolAttr(item, "data-phone", false),
269      phoneRegionCode: item.getAttribute("data-phone-region-code") || "br",
270      date: this.getBoolAttr(item, "data-date", false),
271      datePattern: (item.getAttribute("data-date-pattern") || "d/m/Y").split(
272        "/"
273      ),
274      dateMin: item.getAttribute("data-date-min"),
275      dateMax: item.getAttribute("data-date-max"),
276      time: this.getBoolAttr(item, "data-time", false),
277      timePattern: (item.getAttribute("data-time-pattern") || "h:m").split(":"),
278      timeFormat: item.getAttribute("data-time-format") || "24",
279      blocks: item
280        .getAttribute("data-blocks")
281        ?.split("|")
282        .map((x) => parseInt(x)),
283      delimiter: item.getAttribute("data-delimiters"),
284      delimiters: item.getAttribute("data-delimiters")?.split("|"),
285      uppercase: this.getBoolAttr(item, "data-uppercase", false),
286      lowercase: this.getBoolAttr(item, "data-lowercase", false),
287      numeral: this.getBoolAttr(item, "data-numeral", false),
288      numericOnly: this.getBoolAttr(item, "data-numeric-only", false),
289      numeralThousandsGroupStyle: item.getAttribute(
290        "data-numeral-thousands-group-style"
291      ),
292      numeralDecimalMark: item.getAttribute("data-numeral-decimal-mark") || ",",
293      prefix: item.getAttribute("data-prefix"),
294      tailPrefix: this.getBoolAttr(item, "data-tail-prefix", false),
295    };
296    new Cleave(item, opts);
297  });
298};
299;
300kombi.carousel = function (root) {
301    const scope = root && root.querySelector ? root : document;
302    const carousel =
303        scope.id === "hotelCarousel"
304            ? scope
305            : scope.querySelector("#hotelCarousel");
306
307    if (!carousel) {
308        return;
309    }
310    if (kombi.lifecycle.isInitialized(carousel, "hotel-carousel")) {
311        return;
312    }
313    kombi.lifecycle.markInitialized(carousel, "hotel-carousel");
314
315    function deferHotelCarouselAutoplay(carouselEl) {
316        if (!carouselEl || !window.bootstrap || !bootstrap.Carousel) {
317            return;
318        }
319        if (carouselEl.querySelectorAll(".carousel-item").length < 2) {
320            return;
321        }
322
323        function startCycle() {
324            bootstrap.Carousel.getOrCreateInstance(carouselEl).cycle();
325        }
326
327        function onIdle(callback) {
328            if (typeof window.requestIdleCallback === "function") {
329                window.requestIdleCallback(callback, { timeout: 4000 });
330            } else {
331                window.setTimeout(callback, 1);
332            }
333        }
334
335        if (document.readyState === "complete") {
336            onIdle(startCycle);
337        } else {
338            window.addEventListener(
339                "load",
340                function () {
341                    onIdle(startCycle);
342                },
343                { once: true }
344            );
345        }
346    }
347
348    function parseSlideIndex(value, fallback) {
349        const parsed = Number(value);
350        return Number.isInteger(parsed) ? parsed : fallback;
351    }
352
353    deferHotelCarouselAutoplay(carousel);
354
355    const thumbTrack = carousel.querySelector(".carousel-indicators");
356    if (!thumbTrack) {
357        return;
358    }
359
360    const indicators = Array.from(thumbTrack.querySelectorAll("img[data-bs-slide-to]"));
361    const geometry = {
362        sliderWidth: 0,
363        maxScroll: 0,
364        thumbCenters: [],
365    };
366    const RELEASE_EVENTS = ["pointerup", "pointercancel"];
367
368    function readGeometry() {
369        geometry.sliderWidth = thumbTrack.clientWidth;
370        geometry.maxScroll = Math.max(0, thumbTrack.scrollWidth - thumbTrack.clientWidth);
371        geometry.thumbCenters = indicators.map(function (thumb) {
372            return thumb.offsetLeft + thumb.offsetWidth / 2;
373        });
374    }
375
376    function scrollThumbIntoView(index) {
377        const center = geometry.thumbCenters[index];
378        if (center == null) {
379            return;
380        }
381        thumbTrack.scrollLeft = Math.min(
382            geometry.maxScroll,
383            Math.max(0, center - geometry.sliderWidth / 2)
384        );
385    }
386
387    function setActiveThumb(fromIndex, toIndex) {
388        const prev = indicators[fromIndex];
389        const next = indicators[toIndex];
390        if (prev && prev !== next) {
391            prev.classList.remove("active");
392            prev.removeAttribute("aria-current");
393        }
394        if (next) {
395            next.classList.add("active");
396            next.setAttribute("aria-current", "true");
397        }
398        scrollThumbIntoView(toIndex);
399    }
400
401    function bindTrackDrag() {
402        let isPointerDown = false;
403        let isDragging = false;
404        let startX = 0;
405        let startScroll = 0;
406        let pendingX = 0;
407        let scrollFrameId = 0;
408        let suppressClick = false;
409        const DRAG_SPEED = 3;
410        const DRAG_THRESHOLD = 8;
411
412        function applyScroll() {
413            scrollFrameId = 0;
414            if (!isDragging) {
415                return;
416            }
417            thumbTrack.scrollLeft = startScroll - (pendingX - startX) * DRAG_SPEED;
418        }
419
420        function setDocumentRelease(enabled) {
421            const method = enabled ? "addEventListener" : "removeEventListener";
422            RELEASE_EVENTS.forEach(function (name) {
423                document[method](name, endDrag);
424            });
425        }
426
427        function onPointerDown(event) {
428            if (event.pointerType !== "mouse" || event.button !== 0) {
429                return;
430            }
431            isPointerDown = true;
432            isDragging = false;
433            suppressClick = false;
434            startX = event.clientX;
435            pendingX = startX;
436            startScroll = thumbTrack.scrollLeft;
437            setDocumentRelease(true);
438        }
439
440        function onPointerMove(event) {
441            if (!isPointerDown) {
442                return;
443            }
444            if (event.buttons === 0) {
445                endDrag(event);
446                return;
447            }
448            pendingX = event.clientX;
449            if (!isDragging) {
450                if (Math.abs(pendingX - startX) <= DRAG_THRESHOLD) {
451                    return;
452                }
453                isDragging = true;
454                if (thumbTrack.setPointerCapture) {
455                    thumbTrack.setPointerCapture(event.pointerId);
456                }
457            }
458            event.preventDefault();
459            if (!scrollFrameId) {
460                scrollFrameId = window.requestAnimationFrame(applyScroll);
461            }
462        }
463
464        function endDrag(event) {
465            if (!isPointerDown) {
466                return;
467            }
468            isPointerDown = false;
469            setDocumentRelease(false);
470            if (isDragging) {
471                suppressClick = true;
472                if (scrollFrameId) {
473                    window.cancelAnimationFrame(scrollFrameId);
474                    applyScroll();
475                }
476                if (
477                    event &&
478                    thumbTrack.releasePointerCapture &&
479                    thumbTrack.hasPointerCapture &&
480                    thumbTrack.hasPointerCapture(event.pointerId)
481                ) {
482                    thumbTrack.releasePointerCapture(event.pointerId);
483                }
484            }
485            isDragging = false;
486        }
487
488        thumbTrack.addEventListener("pointerdown", onPointerDown);
489        thumbTrack.addEventListener("pointermove", onPointerMove);
490        thumbTrack.addEventListener(
491            "click",
492            function (event) {
493                if (!suppressClick) {
494                    return;
495                }
496                suppressClick = false;
497                event.preventDefault();
498                event.stopPropagation();
499            },
500            true
501        );
502    }
503
504    if (typeof ResizeObserver === "function") {
505        new ResizeObserver(readGeometry).observe(thumbTrack);
506    }
507
508    bindTrackDrag();
509    readGeometry();
510
511    carousel.addEventListener("slid.bs.carousel", function (event) {
512        const nextIndex = parseSlideIndex(event.to, 0);
513        const prevIndex = parseSlideIndex(event.from, 0);
514        if (!geometry.thumbCenters.length) {
515            readGeometry();
516        }
517        setActiveThumb(prevIndex, nextIndex);
518    });
519};
520;
521// Shared calendar day pricing (desktop + mobile). GET calendar.json?hotel_pk=&mode=hotel|package.
522kombi.calendarDayPrice = (function () {
523    const MISSING_PRICE_LABEL = "---";
524
525    let lastHotelPk = null;
526    let lastCalendarUrl = null;
527    let lastData = null;
528    let fetchSeq = 0;
529
530    function normalizeCalendarData(data) {
531        if (!data || typeof data !== "object") {
532            return null;
533        }
534        // Feature flag off / no rates → {} ; treat as no calendar for UI.
535        if (Object.keys(data).length === 0) {
536            return null;
537        }
538        return data;
539    }
540
541    function isPackageActive() {
542        return (
543            kombi.searchTabs &&
544            typeof kombi.searchTabs.isPackageActive === "function" &&
545            kombi.searchTabs.isPackageActive()
546        );
547    }
548
549    function getMode() {
550        return isPackageActive() ? "package" : "hotel";
551    }
552
553    function toDateKey(dateObj) {
554        if (typeof kombi.fp_date_key === "function") {
555            return kombi.fp_date_key(dateObj);
556        }
557        return "";
558    }
559
560    function formatPrice(value) {
561        const amount = Number(value);
562        if (!Number.isFinite(amount) || amount <= 0) {
563            return "";
564        }
565        return new Intl.NumberFormat("pt-BR", {
566            minimumFractionDigits: 0,
567            maximumFractionDigits: 0,
568        }).format(Math.round(amount));
569    }
570
571    function buildUrl(calendarUrl, hotelPk, mode, refresh) {
572        const url = new URL(calendarUrl, window.location.origin);
573        url.searchParams.set("hotel_pk", String(hotelPk));
574        url.searchParams.set("mode", mode || getMode());
575        if (refresh) {
576            url.searchParams.set("refresh", "1");
577        }
578        return url.toString();
579    }
580
581    function ensurePriceSlot(dayElement) {
582        let priceEl = dayElement.querySelector(".calendar-day-price");
583        if (!priceEl) {
584            priceEl = document.createElement("span");
585            priceEl.className = "calendar-day-price";
586            priceEl.setAttribute("aria-hidden", "true");
587            dayElement.appendChild(priceEl);
588        }
589        return priceEl;
590    }
591
592    function removePriceSlot(dayElement) {
593        const priceEl = dayElement.querySelector(".calendar-day-price");
594        if (priceEl) {
595            priceEl.remove();
596        }
597    }
598
599    // Package: no monetary value; best-price teal on the day number only.
600    function decoratePackageDay(dayElement, calendarDay, canArrive) {
601        removePriceSlot(dayElement);
602        if (canArrive && calendarDay.is_best_price === true) {
603            dayElement.classList.add("calendar-best-price");
604        }
605    }
606
607    // Hotel: daily rate under the day number;
607 keep the slot for cell alignment.
608    function decorateHotelDay(dayElement, calendarDay, canArrive) {
609        const priceEl = ensurePriceSlot(dayElement);
610        priceEl.textContent = "";
611        priceEl.classList.remove("calendar-day-price--best");
612
613        if (!canArrive) {
614            return;
615        }
616
617        if (calendarDay.is_best_price === true) {
618            dayElement.classList.add("calendar-best-price");
619            priceEl.classList.add("calendar-day-price--best");
620        }
621
622        priceEl.textContent = formatPrice(calendarDay.daily_price) || MISSING_PRICE_LABEL;
623    }
624
625    function decorateDayElement(dayElement, calendarDay, canArrive) {
626        if (!dayElement) {
627            return;
628        }
629
630        dayElement.classList.remove("calendar-best-price");
631
632        if (isPackageActive()) {
633            decoratePackageDay(dayElement, calendarDay, canArrive);
634            return;
635        }
636
637        decorateHotelDay(dayElement, calendarDay, canArrive);
638    }
639
640    function applyToInstances(data) {
641        (kombi.flatpickrInstances || []).forEach(function (fp) {
642            fp.calendar = data;
643            fp.redraw();
644        });
645        if (typeof kombi.syncAllCalendarLegendVisibility === "function") {
646            kombi.syncAllCalendarLegendVisibility();
647        }
648        if (
649            kombi.calendarMobile &&
650            typeof kombi.calendarMobile.applyCalendarData === "function"
651        ) {
652            kombi.calendarMobile.applyCalendarData(data);
653        }
654    }
655
656    function fetchCalendar(calendarUrl, hotelPk, mode, refresh) {
657        const resolvedMode = mode || getMode();
658        if (!hotelPk || !calendarUrl) {
659            fetchSeq += 1;
660            lastHotelPk = null;
661            lastCalendarUrl = null;
662            lastData = null;
663            applyToInstances(null);
664            return Promise.resolve(null);
665        }
666
667        const requestSeq = (fetchSeq += 1);
668        const requestUrl = buildUrl(calendarUrl, hotelPk, resolvedMode, refresh);
669        return fetch(requestUrl)
670            .then(function (response) {
671                if (!response.ok) {
672                    throw new Error("Calendar request failed: " + response.status);
673                }
674                return response.json();
675            })
676            .then(function (data) {
677                if (requestSeq !== fetchSeq) {
678                    return lastData;
679                }
680                const normalized = normalizeCalendarData(data);
681                lastHotelPk = hotelPk;
682                lastCalendarUrl = calendarUrl;
683                lastData = normalized;
684                applyToInstances(normalized);
685                return normalized;
686            })
687            .catch(function () {
688                if (requestSeq !== fetchSeq) {
689                    return lastData;
690                }
691                lastData = null;
692                applyToInstances(null);
693                return null;
694            });
695    }
696
697    function refetchForCurrentMode() {
698        if (!lastHotelPk || !lastCalendarUrl) {
699            return Promise.resolve(null);
700        }
701        return fetchCalendar(lastCalendarUrl, lastHotelPk, getMode());
702    }
703
704    // Calendar open only: send refresh=1 so the backend may enqueue maybe_refresh.
705    function refreshOnCalendarOpen() {
706        if (!lastHotelPk || !lastCalendarUrl) {
707            return Promise.resolve(null);
708        }
709        return fetchCalendar(lastCalendarUrl, lastHotelPk, getMode(), true);
710    }
711
712    return {
713        toDateKey: toDateKey,
714        decorateDayElement: decorateDayElement,
715        fetchCalendar: fetchCalendar,
716        refetchForCurrentMode: refetchForCurrentMode,
717        refreshOnCalendarOpen: refreshOnCalendarOpen,
718        getCachedData: function () {
719            return lastData;
720        },
721        getLastHotelPk: function () {
722            return lastHotelPk;
723        },
724        isPackageActive: isPackageActive,
725    };
726})();
727;
728kombi.flatpickrInstances = [];
729
730/** Local YYYY-MM-DD (avoids toISOString timezone cost/skew in hot calendar paths). */
731kombi.fp_date_key = function (dateObj) {
732    if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
733        return "";
734    }
735    const year = dateObj.getFullYear();
736    const month = String(dateObj.getMonth() + 1).padStart(2, "0");
737    const day = String(dateObj.getDate()).padStart(2, "0");
738    return year + "-" + month + "-" + day;
739};
740
741kombi.fp_can_stay = function (start, end, calendar) {
742    let currentDate = new Date(start);
743    let dateStr = kombi.fp_date_key(currentDate);
744    let duration = (end - start) / 86400000;
745    if (start == end || duration < calendar[dateStr].min_stay || duration > calendar[dateStr].max_stay) {
746        return false;
747    }
748    while (currentDate < end) {
749        if (!calendar[dateStr] || calendar[dateStr].available <= 0) {
750            return false;
751        }
752        currentDate.setDate(currentDate.getDate() + 1);
753        dateStr = kombi.fp_date_key(currentDate);
754    }
755    return true;
756}
757
758kombi.fp_can_start_stay = function (start, calendar) {
759    let currentStayStart = new Date(start);
760    let currentDate = new Date();
761    currentDate.setHours(0, 0, 0, 0);
762    let dateStr = kombi.fp_date_key(currentStayStart);
763    let antecedence = calendar[dateStr].antecedence;
764    if (currentStayStart < currentDate.fp_incr(antecedence)) {
765        return false;
766    }
767    // check that all days between start and min_stay are available
768    let min_stay = calendar[dateStr].min_stay;
769    let end = currentStayStart.fp_incr(min_stay);
770    while (currentStayStart < end) {
771        if (!calendar[dateStr] || calendar[dateStr].available <= 0) {
772            return false;
773        }
774        currentStayStart.setDate(currentStayStart.getDate() + 1);
775        dateStr = kombi.fp_date_key(currentStayStart);
776    }
777    return true;
778}
779
780/** Date accepts a check-in: has inventory, arrival is open and antecedence/min-stay fit. */
781kombi.fp_can_arrive = function (dateObj, calendar) {
782    const calendarDay = calendar ? calendar[kombi.fp_date_key(dateObj)] : null;
783    if (!calendarDay) {
784        return false;
785    }
786    return (
787        calendarDay.available > 0 &&
788        !calendarDay.closed_on_arrival &&
789        kombi.fp_can_start_stay(dateObj, calendar)
790    );
791};
792
793/** Day availability + pricing shared by the desktop popup and the mobile modal (onDayCreate). */
794kombi.fp_apply_day_rules = function (fp, dayElem) {
795    if (!fp.calendar || Object.keys(fp.calendar).length === 0) {
796        return;
797    }
798
799    const calendarDay = fp.calendar[kombi.fp_date_key(dayElem.dateObj)] || null;
800    const canArrive = kombi.fp_can_arrive(dayElem.dateObj, fp.calendar);
801    let showMinStay = false;
802
803    if (kombi.calendarDayPrice) {
804        kombi.calendarDayPrice.decorateDayElement(dayElem, calendarDay, canArrive);
805    }
806
807    if (!calendarDay) {
808        if (
809            !(fp.selecting_stay && kombi.fp_can_stay(fp.selecting_stay, dayElem.dateObj, fp.calendar))
810        ) {
811            dayElem.classList.add("flatpickr-disabled");
812        }
813        return;
814    }
815
816    if (!fp.selecting_stay) {
817        if (canArrive) {
818            showMinStay = true;
819        } else {
820            dayElem.classList.add("flatpickr-disabled");
821        }
822    } else if (dayElem.dateObj.getTime() === fp.selecting_stay.getTime()) {
823        dayElem.classList.add("flatpickr-disabled", "selecting");
824        showMinStay = true;
825    } else if (
826        !(
827            dayElem.dateObj >= fp.selecting_stay &&
828            kombi.fp_can_stay(fp.selecting_stay, dayElem.dateObj, fp.calendar) &&
829            !calendarDay.closed_on_departure
830        )
831    ) {
832        dayElem.classList.add("flatpickr-disabled");
833    }
834
835    // Touch has no hover: the tip must exist in the DOM, CSS reveals it on .selecting.
836    if (showMinStay) {
837        const tooltip = document.createElement("span");
838        tooltip.classList.add("calendar-tooltip");
839        tooltip.textContent = "Mín de " + calendarDay.min_stay + " noite(s)";
840        dayElem.appendChild(tooltip);
841    }
842};
843
844/**
845 * Show Melhores preços / Entrada indisponível legends when hotel calendar API data exists
846 * (Hotel or Package with hotel). Rates note ("Tarifas estimadas...") only on Hotel tab.
847 * Empty payload ({}) / cleared hotel → hide.
848 */
849kombi.setCalendarLegendVisibility = function (fp) {
850    const container = fp.calendarContainer;
851    if (!container) return;
852    const hasCalendarData = !!(fp.calendar && Object.keys(fp.calendar).length > 0);
853    const isPackage =
854        kombi.calendarDayPrice &&
855        typeof kombi.calendarDayPrice.isPackageActive === "function" &&
856        kombi.calendarDayPrice.isPackageActive();
857    const showRatesNote = hasCalendarData && !isPackage;
858    const legendContainer = container.querySelector(".calendar-legends");
859    if (legendContainer) {
860        legendContainer.classList.toggle("d-none", !hasCalendarData);
861        legendContainer.classList.toggle("d-flex", hasCalendarData);
862    }
863    const ratesNote = container.querySelector(".calendar-rates-note");
864    if (ratesNote) {
865        ratesNote.classList.toggle("d-none", !showRatesNote);
866        ratesNote.setAttribute("aria-hidden", showRatesNote ? "false" : "true");
867    }
868};
869
870kombi.syncAllCalendarLegendVisibility = function () {
871    (kombi.flatpickrInstances || []).forEach(function (fp) {
872        kombi.setCalendarLegendVisibility(fp);
873    });
874};
875
876kombi.flatpickr = function () {
877    /**
878     * Package (search): lead time is read from <body data-package-lead-days="N"> (set by backend).
879     * First allowed check-in = today + N + 1 (exclusive: today+N is not selectable).
880     * Hotel: keeps new Date().fp_incr(1).
881     */
882    const DEFAULT_PACKAGE_LEAD_DAYS = 5;
883
884    function getPackageLeadDays() {
885        var raw = document.body && document.body.dataset
886            ? document.body.dataset.packageLeadDays
887            : null;
888        var parsed = parseInt(raw, 10);
889        return (parsed === parsed && parsed >= 0) ? parsed : DEFAULT_PACKAGE_LEAD_DAYS;
890    }
891
892    function getPackageMinDate() {
893        const d = new Date();
894        d.setHours(0, 0, 0, 0);
895        d.setDate(d.getDate() + getPackageLeadDays() + 1);
896        return d;
897    }
898
899    function isPackageActive() {
900        return !!(
901            kombi.searchTabs &&
902            typeof kombi.searchTabs.isPackageActive === "function" &&
903            kombi.searchTabs.isPackageActive()
904        );
905    }
906
907    function getMinDateForCurrentTab() {
908        if (isPackageActive()) {
909            return getPackageMinDate();
910        }
911        return new Date().fp_incr(1);
912    }
913
914    /** If the visible month is before minDate's month, jump the view forward. */
915    function jumpToPackageMinMonthIfNeeded(fp, minDate) {
916        const viewMonthStart = new Date(fp.currentYear, fp.currentMonth, 1);
917        const minMonthStart = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
918        if (viewMonthStart < minMonthStart) {
919            fp.jumpToDate(minDate);
920        }
921    }
922
923    /** Legends match product mocks for Hotel and Package: Melhores preços + Entrada indisponível. */
924    function getCalendarLegendsInnerHTML() {
925        const firstLegend =
926            '<div class="legend d-flex align-items-center mt-1 w-auto">' +
927            '<div class="legend-color best-prices rounded-circle me-3" style="width: 15px; height: 15px; flex-shrink: 0;"></div><span class="text-start lh-1 fs-0_8rem">Melhores preços</span>' +
928            "</div>";
929        const secondLegend =
930            '<div class="legend d-flex align-items-center mt-1 w-auto">' +
931            '<div class="position-relative me-3" style="width: 15px; height: 15px; flex-shrink: 0;">' +
932            '<div class="legend-color date-unavailable rounded-circle" style="width: 15px; height: 15px;"></div>' +
933            '<div class="border-top border-2 position-absolute top-50 translate-middle-y" style="left: -3.5px; right: -3px; border-color: #626262 !important;"></div>' +
934            "</div>" +
935            '<span class="text-start lh-1 fs-0_8rem">Entrada indisponível</span>' +
936            "</div>";
937        return firstLegend + secondLegend;
938    }
939
940    function updateAllCalendarLegendsForTab() {
941        const html = getCalendarLegendsInnerHTML();
942        kombi.flatpickrInstances.forEach(function (fp) {
943            const legendContainer = fp.calendarContainer && fp.calendarContainer.querySelector(".calendar-legends");
944            if (legendContainer) {
945                legendContainer.innerHTML = html;
946            }
947            kombi.setCalendarLegendVisibility(fp);
948        });
949    }
950
951    function syncClearButtonState(fp) {
952        if (!fp || !fp.calendarContainer) return;
953        const clearBtn = fp.calendarContainer.querySelector(".calendar-btn-clear");
954        if (!clearBtn) return;
955        const selected = fp.selectedDates || [];
956        clearBtn.disabled = selected.length === 0;
957    }
958
959    /** Desktop: center dual-month popup under .nav-search (measured, not CSS tokens). */
960    function centerCalendarUnderSearchBar(fp) {
961        const cal = fp && fp.calendarContainer;
962        if (
963            !cal ||
964            cal.classList.contains("calendar-mobile__fp") ||
965            !window.matchMedia("(min-width: 992px)").matches
966        ) {
967            return;
968        }
969        const bar =
970            (fp.element && fp.element.closest(".nav-search")) ||
971            document.querySelector(".d-sm-block .nav-search");
972        if (!bar || !cal.offsetWidth) return;
973
974        const barRect = bar.getBoundingClientRect();
975        cal.style.left =
976            barRect.left + (barRect.width - cal.offsetWidth) / 2 + window.pageXOffset + "px";
977        cal.style.right = "auto";
978        cal.style.transform = "none";
979        cal.classList.remove("rightMost", "centerMost");
980    }
981
982    function clearCalendarDates(fp, rootItem) {
983        if (!fp || !rootItem) return;
984        const currentMonth = fp.currentMonth;
985        const currentYear = fp.currentYear;
986        fp.selecting_stay = null;
987        fp.clear();
988        fp.jumpToDate(new Date(currentYear, currentMonth));
989        const startInput = rootItem.querySelector(".dates-0");
990        const endInput = rootItem.querySelector(".dates-1");
991        if (startInput) startInput.value = "";
992        if (endInput) endInput.value = "";
993        syncClearButtonState(fp);
994    }
995
996    /**
997     * Flatpickr weekday headers: single letter (D S T Q Q S S), matching the search calendar mock.
998     */
999    function getCalendarLocale(localeCode) {
1000        const code = localeCode || "pt";
1001        const base =
1002            typeof flatpickr !== "undefined" &&
1003            flatpickr.l10ns &&
1004            flatpickr.l10ns[code]
1005                ? flatpickr.l10ns[code]
1006                : null;
1007        if (!base) return code;
1008        return Object.assign({}, base, {
1009            weekdays: Object.assign({}, base.weekdays, {
1010                shorthand: ["D", "S", "T", "Q", "Q", "S", "S"],
1011            }),
1012        });
1013    }
1014
1015    // Search desktop (#search-calendar-desk). Mobile search uses calendarMobile (inline modal).
1016    // https://flatpickr.js.org/
1017    document.querySelectorAll(".flatpickr").forEach((item) => {
1018        let enableTime = this.getBoolAttr(item, "data-enable-time", false);
1019        let initialHotel = item.getAttribute("data-initial-hotel") || null;
1020        let opts = {
1021            mode: item.getAttribute("data-mode") || "single",
1022            enableTime: enableTime,
1023            noCalendar: this.getBoolAttr(item, "data-no-calendar", false),
1024            inline: this.getBoolAttr(item, "data-inline", false),
1025            animate: this.getBoolAttr(item, "data-animate", false),
1026            position: "auto",
1027            dateFormat:
1028                item.getAttribute("data-date-format") || (enableTime ? "H:i" : "Y-m-d"),
1029            disableMobile: this.getBoolAttr(item, "data-disable-mobile", true),
1030            defaultDate: item.getAttribute("data-default-date")?.split(",") || null,
1031            altInput: this.getBoolAttr(item, "data-alt-input", true),
1032            altFormat:
1033                item.getAttribute("data-alt-format") || (enableTime ? "H:i" : "d/m/y"),
1034            locale: getCalendarLocale(item.getAttribute("data-locale") || "pt"),
1035            minDate: item.getAttribute("data-min-date") || getMinDateForCurrentTab(),
1036            maxDate: item.getAttribute("data-max-date") || new Date().fp_incr(540),
1037            wrap: this.getBoolAttr(item, "data-wrap", false),
1038            showMonths: 2,
1039            // Keep open after range select; user confirms with footer Avançar.
1040            closeOnSelect: false,
1041            // Month label is text-only; avoid dropdown jumps. Year input is locked in onReady/CSS.
1042            monthSelectorType: "static",
1043            onOpen: function(selectedDates, dateStr, instance) {
1044                const searchControlsRoot =
1045                    item.closest(".nav-search") ||
1046                    item.closest("#SearchForm .pattern-form");
1047                if (searchControlsRoot && kombi.closeNavSearchDropdowns) {
1048                    kombi.closeNavSearchDropdowns(searchControlsRoot);
1049                }
1050                if (this.config.mode == "range") {
1051                    if (instance.selectedDates.length === 1) {
1052                        instance.selectedDates = [];
1053                    }
1054                    instance.selecting_stay = null;
1055                    instance.redraw();
1056                }
1057                syncClearButtonState(instance);
1058                if (isPackageActive() && instance.config.mode === "range") {
1059                    jumpToPackageMinMonthIfNeeded(instance, getPackageMinDate());
1060                }
1061                // Width may settle after Flatpickr's first position pass.
1062                requestAnimationFrame(function () {
1063                    centerCalendarUnderSearchBar(instance);
1064                });
1065                if (kombi.calendarDayPrice) {
1066                    kombi.calendarDayPrice.refreshOnCalendarOpen();
1067                }
1068            },
1069            onClose: function (selectedDates) {
1070                if (this.config.mode === "range") {
1071                    const startInput = item.querySelector(".dates-0");
1072                    const endInput = item.querySelector(".dates-1");
1073                    const incompleteRange =
1074                        selectedDates.length === 1 ||
1075                        (selectedDates.length === 2 &&
1076                            selectedDates[0].getTime() >= selectedDates[1].getTime());
1077
1078                    // Incomplete selection must not persist a lone check-in into hidden inputs.
1079                    if (incompleteRange) {
1080                        this.selecting_stay = null;
1081                        this.clear();
1082                        if (startInput) startInput.value = "";
1083                        if (endInput) endInput.value = "";
1084                        return;
1085                    }
1086
1087                    this.selecting_stay = null;
1088                    if (selectedDates[0] && startInput) {
1089                        startInput.value = this.formatDate(selectedDates[0], "Y-m-d");
1090                    }
1091                    if (selectedDates[1] && endInput) {
1092                        endInput.value = this.formatDate(selectedDates[1], "Y-m-d");
1093                    }
1094                }
1095            },
1096            onChange: function (selectedDates, dateStr, instance) {
1097                if (instance.config.mode == "range" && selectedDates.length === 1) {
1098                    instance.selecting_stay = selectedDates[0];
1099                    instance.redraw();
1100                } else {
1101                    instance.selecting_stay = null;
1102                }
1103                syncClearButtonState(instance);
1104            },
1105            onDayCreate: function (dObj, dStr, fp, dayElem) {
1106                kombi.fp_apply_day_rules(fp, dayElem);
1107            },
1108            onReady: function(selectedDates, dateStr, instance) {
1109                if (!instance.calendarContainer) return;
1110
1111                const legendsHtml = getCalendarLegendsInnerHTML();
1112
1113                const footerWrap = document.createElement("div");
1114                footerWrap.classList.add("calendar-footer-wrap", "mx-2", "mt-1");
1115
1116                const ratesNote = document.createElement("div");
1117                ratesNote.classList.add(
1118                    "calendar-rates-note",
1119                    "d-none",
1120                    "pb-2",
1121                    "fs-0_8rem",
1122                    "text-gray-650",
1123                    "text-end",
1124                );
1125                ratesNote.textContent = "Tarifas estimadas para 02 adultos";
1126                ratesNote.setAttribute("aria-hidden", "true");
1127
1128                const footer = document.createElement("div");
1129                footer.classList.add(
1130                    "calendar-footer",
1131                    "d-flex",
1132                    "align-items-center",
1133                    "justify-content-between",
1134                    "gap-3",
1135                    "pt-2",
1136                    "border-top",
1137                );
1138
1139                const legendContainer = document.createElement("div");
1140                legendContainer.classList.add(
1141                    "calendar-legends",
1142                    "d-flex",
1143                    "align-items-center",
1144                    "gap-4",
1145                    "flex-wrap"
1146                );
1147                legendContainer.innerHTML = legendsHtml;
1148
1149                const actions = document.createElement("div");
1150                actions.classList.add(
1151                    "calendar-actions",
1152                    "d-flex",
1153                    "align-items-center",
1154                    "gap-2",
1155                    "flex-shrink-0",
1156                    "ms-auto"
1157                );
1158                actions.innerHTML =
1159                    '<button type="button" class="btn btn-sm btn-outline-primary rounded-pill calendar-btn-clear px-3 py-2" disabled>Limpar</button>' +
1160                    '<button type="button" class="btn btn-sm btn-primary rounded-pill calendar-btn-confirm px-3 py-2">Avançar</button>';
1161
1162                footer.appendChild(legendContainer);
1163                footer.appendChild(actions);
1164                footerWrap.appendChild(ratesNote);
1165                footerWrap.appendChild(footer);
1166                instance.calendarContainer.appendChild(footerWrap);
1167
1168                actions.querySelector(".calendar-btn-clear").addEventListener("click", function (e) {
1169                    e.preventDefault();
1170                    e.stopPropagation();
1171                    if (e.currentTarget.disabled) return;
1172                    clearCalendarDates(instance, item);
1173                });
1174                actions.querySelector(".calendar-btn-confirm").addEventListener("click", function (e) {
1175                    e.preventDefault();
1176                    e.stopPropagation();
1177                    const dates = instance.selectedDates || [];
1178                    if (
1179                        dates.length === 2 &&
1180                        dates[0].getTime() < dates[1].getTime()
1181                    ) {
1182                        instance.close();
1183                    }
1184                });
1185                syncClearButtonState(instance);
1186                kombi.setCalendarLegendVisibility(instance);
1187            },
1188        };
1189
1190        let fpInstance = flatpickr(item, opts);
1191        fpInstance.calendar = null;
1192        fpInstance.selecting_stay = null;
1193        fpInstance.calendar_url = item.getAttribute("data-calendar-url") || null;
1194        fpInstance.initialHotel = initialHotel;
1195        // After every Flatpickr position (open/resize), fix horizontal align to .nav-search.
1196        const positionCalendar = fpInstance._positionCalendar.bind(fpInstance);
1197        fpInstance._positionCalendar = function () {
1198            positionCalendar();
1199            centerCalendarUnderSearchBar(fpInstance);
1200        };
1201        kombi.flatpickrInstances.push(fpInstance);
1202    });
1203
1204    if (this.flatpickrInstances.length > 0) {
1205        if (this.flatpickrInstances[0].initialHotel && this.flatpickrInstances[0].calendar_url) {
1206            this.updateCalendar(this.flatpickrInstances[0].initialHotel);
1207        }
1208    }
1209
1210    if (!kombi._searchTabChangeFlatpickrBound) {
1211        kombi._searchTabChangeFlatpickrBound = true;
1212        document.addEventListener("searchtab:change", function (e) {
1213            const isPackage = e.detail && e.detail.isPackage;
1214            const minDate = isPackage ? getPackageMinDate() : new Date().fp_incr(1);
1215            kombi.flatpickrInstances.forEach(function (fp) {
1216                fp.set("minDate", minDate);
1217                const dates = fp.selectedDates || [];
1218                const minTs = minDate.getTime();
1219                const hasInvalid = dates.some(function (d) {
1220                    return d.getTime() < minTs;
1221                });
1222                if (hasInvalid) {
1223                    fp.clear();
1224                    const el = fp.element;
1225                    const d0 = el.querySelector(".dates-0");
1226                    const d1 = el.querySelector(".dates-1");
1227                    if (d0) d0.value = "";
1228                    if (d1) d1.value = "";
1229                }
1230                if (isPackage) {
1231                    jumpToPackageMinMonthIfNeeded(fp, minDate);
1232                }
1233                fp.redraw();
1234            });
1235            updateAllCalendarLegendsForTab();
1236            if (kombi.calendarDayPrice) {
1237                kombi.calendarDayPrice.refetchForCurrentMode();
1238            }
1239            kombi.flatpickrInstances.forEach(function (fp) {
1240                if (fp.isOpen) centerCalendarUnderSearchBar(fp);
1241            });
1242        });
1243    }
1244};
1245
1246kombi.updateCalendar = function (hotel_pk) {
1247    // Keep mobile resolveHotelPk in sync with search-bar hotel selection/clear.
1248    // Otherwise detail-page data-initial-hotel (e.g. Blue Tree) sticks after a switch/clear.
1249    document
1250        .querySelectorAll("#calendar-mobile-mount, .calendar-mobile-trigger")
1251        .forEach(function (el) {
1252            if (hotel_pk) {
1253                el.setAttribute("data-initial-hotel", String(hotel_pk));
1254            } else {
1255                el.removeAttribute("data-initial-hotel");
1256            }
1257        });
1258    (this.flatpickrInstances || []).forEach(function (fp) {
1259        fp.initialHotel = hotel_pk || null;
1260    });
1261
1262    // Instance URL first; DOM fallback for mobile calendar (no .flatpickr instance).
1263    const calendar_url =
1264        (this.flatpickrInstances[0] && this.flatpickrInstances[0].calendar_url) ||
1265        (document.querySelector("[data-calendar-url]") &&
1266            document.querySelector("[data-calendar-url]").getAttribute("data-calendar-url")) ||
1267        null;
1268    if (kombi.calendarDayPrice) {
1269        return kombi.calendarDayPrice.fetchCalendar(calendar_url, hotel_pk);
1270    }
1271    if (hotel_pk && calendar_url) {
1272        fetch(`${calendar_url}?hotel_pk=${hotel_pk}`)
1273            .then((response) => {
1274                return response.json();
1275            })
1276            .then((data) => {
1277                this.flatpickrInstances.forEach((fp) => {
1278                    fp.calendar = data;
1279                    fp.redraw();
1280                });
1281                this.syncAllCalendarLegendVisibility();
1282            });
1283    } else {
1284        this.flatpickrInstances.forEach((fp) => {
1285            fp.calendar = null;
1286            fp.redraw();
1287        });
1288        this.syncAllCalendarLegendVisibility();
1289    }
1290};
1291
1292kombi.openDatePicker = function () {
1293    const flatpickrElements = document.querySelectorAll('.flatpickr');
1294    let flatpickrElement;
1295    if (window.matchMedia("(min-width: 992px)").matches) {
1296        flatpickrElement = flatpickrElements[0];
1297    } else {
1298        const modalElement = document.getElementById("SearchForm");
1299        let modalInstance = bootstrap.Modal.getInstance(modalElement);
1300        if (!modalInstance) {
1301            modalInstance = new bootstrap.Modal(modalElement);
1302        }
1303        modalInstance.show();
1304        flatpickrElement = flatpickrElements[1];
1305    }
1306    if (flatpickrElement) {
1307        const flatpickrInstance = flatpickrElement._flatpickr;
1308        if (flatpickrInstance) {
1309            flatpickrInstance.open();
1310        } else {
1311            flatpickrElement.click();
1312        }
1313    }
1314};;
1315// Mobile date picker modal (stacked months + day prices), sharing pricing via kombi.calendarDayPrice.
1316kombi.calendarMobile = function () {
1317    const DEFAULT_PACKAGE_LEAD_DAYS = 5;
1318
1319    function getPackageLeadDays() {
1320        var raw = document.body && document.body.dataset
1321            ? document.body.dataset.packageLeadDays
1322            : null;
1323        var parsed = parseInt(raw, 10);
1324        return (parsed === parsed && parsed >= 0) ? parsed : DEFAULT_PACKAGE_LEAD_DAYS;
1325    }
1326
1327    const MAX_ADVANCE_DAYS = 540;
1328    const INITIAL_MONTHS = 2;
1329    const MONTHS_BATCH = 2;
1330    const WEEKDAY_LABELS = ["D", "S", "T", "Q", "Q", "S", "S"];
1331    const MONTH_NAMES_PT = [
1332        "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho",
1333        "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro",
1334    ];
1335
1336    const modalEl = document.getElementById("CalendarModal");
1337    const mountEl = document.getElementById("calendar-mobile-mount");
1338    const triggerWrap = document.querySelector("#SearchForm .calendar-mobile-trigger");
1339    const bodyEl = modalEl && modalEl.querySelector(".calendar-mobile__body");
1340
1341    if (!modalEl || !mountEl || !triggerWrap || typeof flatpickr !== "function") {
1342        return;
1343    }
1344
1345    const displayInput = triggerWrap.querySelector(".calendar-mobile-display");
1346    const stayStartInput = triggerWrap.querySelector(".dates-0");
1347    const stayEndInput = triggerWrap.querySelector(".dates-1");
1348    const fieldClearButton = triggerWrap.querySelector(".clear-calendar");
1349    const modalClearButton = modalEl.querySelector(".calendar-mobile__clear");
1350    const advanceButton = modalEl.querySelector(".calendar-mobile__advance");
1351    const backButton = modalEl.querySelector(".calendar-mobile__back");
1352    const legendsEl = modalEl.querySelector(".calendar-mobile__legends");
1353    const ratesNoteEl = modalEl.querySelector(".calendar-mobile__rates-note");
1354
1355    let fpInstance = null;
1356    let modalInstance = null;
1357    let loadedMonths = INITIAL_MONTHS;
1358    let totalMonths = INITIAL_MONTHS;
1359    let scrollBeforeSelect = 0;
1360
1361    function isPackageActive() {
1362        return kombi.calendarDayPrice ? kombi.calendarDayPrice.isPackageActive() : false;
1363    }
1364
1365    function getPackageMinDate() {
1366        const d = new Date();
1367        d.setHours(0, 0, 0, 0);
1368        d.setDate(d.getDate() + getPackageLeadDays() + 1);
1369        return d;
1370    }
1371
1372    function getMinDate() {
1373        return isPackageActive() ? getPackageMinDate() : new Date().fp_incr(1);
1374    }
1375
1376    function getMaxDate() {
1377        return new Date().fp_incr(MAX_ADVANCE_DAYS);
1378    }
1379
1380    function monthsBetween(start, end) {
1381        return (
1382            (end.getFullYear() - start.getFullYear()) * 12 +
1383            (end.getMonth() - start.getMonth()) +
1384            1
1385        );
1386    }
1387
1388
1389    function toDateKey(dateObj) {
1390        return kombi.calendarDayPrice ? kombi.calendarDayPrice.toDateKey(dateObj) : "";
1391    }
1392
1393    // Same as desktop: pass date strings to Flatpickr (dateFormat Y-m-d parses as local).
1394    function parseDefaultDates() {
1395        const raw = (mountEl.getAttribute("data-default-date") || "").split(",");
1396        const parsed = raw
1397            .map(function (value) {
1398                return (value || "").trim();
1399            })
1400            .filter(Boolean);
1401        return parsed.length ? parsed : null;
1402    }
1403
1404    // YYYY-MM-DD → DD/MM/YY without Date (avoids UTC midnight skew).
1405    function formatDisplayFromDateKey(dateKey) {
1406        const parts = String(dateKey).split("T")[0].split("-");
1407        if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
1408            return "";
1409        }
1410        return parts[2] + "/" + parts[1] + "/" + parts[0].slice(-2);
1411    }
1412
1413    function hasSelectedDates() {
1414        if (fpInstance && fpInstance.selectedDates && fpInstance.selectedDates.length > 0) {
1415            return true;
1416        }
1417        const startVal = stayStartInput && stayStartInput.value ? stayStartInput.value.trim() : "";
1418        const endVal = stayEndInput && stayEndInput.value ? stayEndInput.value.trim() : "";
1419        return !!(startVal || endVal);
1420    }
1421
1422    function updateClearButtonState() {
1423        if (!modalClearButton) return;
1424        modalClearButton.disabled = !hasSelectedDates();
1425    }
1426
1427    function syncLegendVisibility() {
1428        const calendar = fpInstance && fpInstance.calendar;
1429        const hasCalendarData = !!(calendar && Object.keys(calendar).length > 0);
1430        const showRatesNote = hasCalendarData && !isPackageActive();
1431        if (legendsEl) {
1432            legendsEl.classList.toggle("d-none", !hasCalendarData);
1433            legendsEl.setAttribute("aria-hidden", hasCalendarData ? "false" : "true");
1434        }
1435        if (ratesNoteEl) {
1436            ratesNoteEl.classList.toggle("d-none", !showRatesNote);
1437            ratesNoteEl.setAttribute("aria-hidden", showRatesNote ? "false" : "true");
1438        }
1439    }
1440
1441    function refreshCalendarUi(instance) {
1442        if (!instance) return;
1443        instance.redraw();
1444        scheduleMonthHeaders(instance);
1445        syncLegendVisibility();
1446        updateClearButtonState();
1447    }
1448
1449    function syncDisplayFromHidden() {
1450        const startVal = stayStartInput && stayStartInput.value ? stayStartInput.value.trim() : "";
1451        const endVal = stayEndInput && stayEndInput.value ? stayEndInput.value.trim() : "";
1452        if (startVal && endVal && displayInput) {
1453            const start = formatDisplayFromDateKey(startVal);
1454            const end = formatDisplayFromDateKey(endVal);
1455            if (start && end) {
1456                displayInput.value = start + " até " + end;
1457                if (fieldClearButton) {
1458                    fieldClearButton.classList.remove("d-none");
1459                    fieldClearButton.classList.add("d-flex");
1460                }
1461                updateClearButtonState();
1462                return;
1463            }
1464        }
1465        if (displayInput) {
1466            displayInput.value = "";
1467        }
1468        if (fieldClearButton) {
1469            fieldClearButton.classList.add("d-none");
1470            fieldClearButton.classList.remove("d-flex");
1471        }
1472        updateClearButtonState();
1473    }
1474
1475    function writeHiddenDates(selectedDates) {
1476        if (stayStartInput) {
1477            stayStartInput.value = selectedDates[0] ? toDateKey(selectedDates[0]) : "";
1478        }
1479        if (stayEndInput) {
1480            stayEndInput.value = selectedDates[1] ? toDateKey(selectedDates[1]) : "";
1481        }
1482    }
1483
1484    function getDaysWrap(instance) {
1485        return instance.calendarContainer && instance.calendarContainer.querySelector(".flatpickr-days");
1486    }
1487
1488    function buildMonthHeader(monthDate) {
1489        const block = document.createElement("div");
1490        block.className = "calendar-mobile__month-block";
1491
1492        const title = document.createElement("div");
1493        title.className = "calendar-mobile__month-title mb-3";
1494        const monthSpan = document.createElement("span");
1495        monthSpan.className = "calendar-mobile__month-name";
1496        monthSpan.textContent = MONTH_NAMES_PT[monthDate.getMonth()];
1497        const yearSpan = document.createElement("span");
1498        yearSpan.className = "calendar-mobile__month-year";
1499        yearSpan.textContent = " " + monthDate.getFullYear();
1500        title.appendChild(monthSpan);
1501        title.appendChild(yearSpan);
1502
1503        const weekdays = document.createElement("div");
1504        weekdays.className = "calendar-mobile__weekdays";
1505        for (let w = 0; w < WEEKDAY_LABELS.length; w++) {
1506            const span = document.createElement("span");
1507            span.className = "calendar-mobile__weekday";
1508            span.textContent = WEEKDAY_LABELS[w];
1509            weekdays.appendChild(span);
1510        }
1511
1512        block.appendChild(title);
1513        block.appendChild(weekdays);
1514        return block;
1515    }
1516
1517    function renderMonthHeaders(instance) {
1518        const daysWrap = getDaysWrap(instance);
1519        if (!daysWrap) return;
1520
1521        const children = daysWrap.children;
1522        for (let i = children.length - 1; i >= 0; i--) {
1523            if (children[i].classList.contains("calendar-mobile__month-block")) {
1524                children[i].remove();
1525            }
1526        }
1527
1528        const containers = daysWrap.querySelectorAll(":scope > .dayContainer");
1529        const baseMonth = instance.currentMonth;
1530        const baseYear = instance.currentYear;
1531        for (let index = 0; index < containers.length; index++) {
1532            const monthDate = new Date(baseYear, baseMonth + index, 1);
1533            daysWrap.insertBefore(buildMonthHeader(monthDate), containers[index]);
1534        }
1535    }
1536
1537    // Re-inject headers once per Flatpickr build (a build wipes them); microtask runs before paint.
1538    function scheduleMonthHeaders(instance) {
1539        if (instance._headerRenderScheduled) return;
1540        instance._headerRenderScheduled = true;
1541        Promise.resolve().then(function () {
1542            instance._headerRenderScheduled = false;
1543            renderMonthHeaders(instance);
1544        });
1545    }
1546
1547    function resolveHotelPk() {
1548        // Prefer last fetch from search-bar updateCalendar over static detail-page attrs.
1549        if (kombi.calendarDayPrice) {
1550            const lastPk = kombi.calendarDayPrice.getLastHotelPk();
1551            if (lastPk) {
1552                return lastPk;
1553            }
1554        }
1555        // After clear, updateCalendar removes these attrs so we do not reuse the page hotel.
1556        return (
1557            mountEl.getAttribute("data-initial-hotel") ||
1558            triggerWrap.getAttribute("data-initial-hotel") ||
1559            null
1560        );
1561    }
1562
1563    function resolveCalendarUrl() {
1564        return (
1565            mountEl.getAttribute("data-calendar-url") ||
1566            triggerWrap.getAttribute("data-calendar-url") ||
1567            (kombi.flatpickrInstances[0] && kombi.flatpickrInstances[0].calendar_url) ||
1568            null
1569        );
1570    }
1571
1572    function setDateOptionIfChanged(instance, key, dateValue) {
1573        const current = instance.config[key];
1574        const currentTs = current ? new Date(current).getTime() : null;
1575        const nextTs = dateValue ? new Date(dateValue).getTime() : null;
1576        if (currentTs === nextTs) return;
1577        instance.set(key, dateValue);
1578    }
1579
1580    function resetScrollTop() {
1581        if (bodyEl) bodyEl.scrollTop = 0;
1582    }
1583
1584    function restoreBodyScroll(y) {
1585        if (!bodyEl || y === null || y === undefined) return;
1586        bodyEl.scrollTop = y;
1587        requestAnimationFrame(function () {
1588            bodyEl.scrollTop = y;
1589            // Month headers re-inject on a microtask after Flatpickr rebuild; pin scroll again.
1590            Promise.resolve().then(function () {
1591                bodyEl.scrollTop = y;
1592            });
1593        });
1594    }
1595
1596    function getFocusDate(instance) {
1597        const fp = instance || fpInstance;
1598        if (fp && fp.selectedDates && fp.selectedDates.length) {
1599            return fp.selectedDates[0];
1600        }
1601        const startVal = stayStartInput && stayStartInput.value ? stayStartInput.value.trim() : "";
1602        if (startVal) {
1603            const fromInput = new Date(startVal);
1604            if (!isNaN(fromInput.getTime())) return fromInput;
1605        }
1606        const defaults = parseDefaultDates();
1607        return defaults && defaults[0] ? defaults[0] : null;
1608    }
1609
1610    // Flatpickr setDate: put the range back on the instance before sizing the month window.
1611    function syncSelectionFromInputs(instance) {
1612        if (!instance || (instance.selectedDates && instance.selectedDates.length)) return;
1613        const startVal = stayStartInput && stayStartInput.value ? stayStartInput.value.trim() : "";
1614        const endVal = stayEndInput && stayEndInput.value ? stayEndInput.value.trim() : "";
1615        if (!startVal || !endVal) return;
1616        const start = new Date(startVal);
1617        const end = new Date(endVal);
1618        if (isNaN(start.getTime()) || isNaN(end.getTime()) || start.getTime() >= end.getTime()) return;
1619        instance.setDate([start, end], false);
1620    }
1621
1622    function monthsNeededThrough(dateObj) {
1623        if (!dateObj) return INITIAL_MONTHS;
1624        // +1 so Flatpickr still shows one month under the selection.
1625        return Math.max(INITIAL_MONTHS, monthsBetween(getMinDate(), dateObj) + 1);
1626    }
1627
1628    // Flatpickr's instance.days is only the first dayContainer — query the whole calendar.
1629    function findFocusDayElement(instance, focusDate) {
1630        if (!instance || !instance.calendarContainer || !focusDate) return null;
1631        const selected = instance.calendarContainer.querySelector(
1632            ".flatpickr-day.startRange, .flatpickr-day.selected"
1633        );
1634        if (selected && selected.dateObj) {
1635            return selected;
1636        }
1637        const year = focusDate.getFullYear();
1638        const month = focusDate.getMonth();
1639        const days = instance.calendarContainer.querySelectorAll(".dayContainer .flatpickr-day");
1640        for (let i = 0; i < days.length; i++) {
1641            const el = days[i];
1642            if (
1643                !el.dateObj ||
1644                el.classList.contains("prevMonthDay") ||
1645                el.classList.contains("nextMonthDay")
1646            ) {
1647                continue;
1648            }
1649            if (el.dateObj.getFullYear() === year && el.dateObj.getMonth() === month) {
1650                return el;
1651            }
1652        }
1653        return null;
1654    }
1655
1656    function scrollBodyToFocusMonth(instance, focusDate) {
1657        if (!bodyEl || !instance || !focusDate) return false;
1658        const dayElem = findFocusDayElement(instance, focusDate);
1659        if (!dayElem) return false;
1660        const dayContainer = dayElem.closest(".dayContainer");
1661        const prev = dayContainer && dayContainer.previousElementSibling;
1662        const target =
1663            prev && prev.classList.contains("calendar-mobile__month-block")
1664                ? prev
1665                : dayContainer || dayElem;
1666        const bodyRect = bodyEl.getBoundingClientRect();
1667        const targetRect = target.getBoundingClientRect();
1668        bodyEl.scrollTop = bodyEl.scrollTop + (targetRect.top - bodyRect.top);
1669        return true;
1670    }
1671
1672    // After Flatpickr rebuild + month-header inject, pin scroll to the selection month.
1673    function scheduleScrollToFocus(instance, focusDate) {
1674        if (!focusDate) {
1675            resetScrollTop();
1676            return;
1677        }
1678        const run = function () {
1679            if (!scrollBodyToFocusMonth(instance, focusDate)) {
1680                // Days may not exist yet right after set(showMonths); retry once.
1681                requestAnimationFrame(function () {
1682                    scrollBodyToFocusMonth(instance, focusDate);
1683                });
1684            }
1685        };
1686        Promise.resolve().then(function () {
1687            requestAnimationFrame(run);
1688        });
1689    }
1690
1691    // jumpToDate(minDate): Flatpickr window always starts at the first bookable month.
1692    function anchorToFirstMonth(instance) {
1693        const minDate = getMinDate();
1694        if (
1695            instance.currentYear !== minDate.getFullYear() ||
1696            instance.currentMonth !== minDate.getMonth()
1697        ) {
1698            instance.jumpToDate(minDate);
1699        }
1700    }
1701
1702    function syncMonthWindow(instance) {
1703        const minDate = getMinDate();
1704        const maxDate = getMaxDate();
1705        totalMonths = Math.max(2, monthsBetween(minDate, maxDate));
1706        syncSelectionFromInputs(instance);
1707        const focusDate = getFocusDate(instance);
1708        // Flatpickr showMonths grows from minDate through the selected month (earlier months stay).
1709        loadedMonths = Math.min(totalMonths, monthsNeededThrough(focusDate));
1710        setDateOptionIfChanged(instance, "minDate", minDate);
1711        setDateOptionIfChanged(instance, "maxDate", maxDate);
1712        if (instance.config.showMonths !== loadedMonths) {
1713            instance.set("showMonths", loadedMonths);
1714        }
1715        anchorToFirstMonth(instance);
1716        scheduleScrollToFocus(instance, focusDate);
1717        armLoadMore();
1718    }
1719
1720    function armLoadMore() {
1721        const observer = bodyEl && bodyEl._loadMoreIO;
1722        const sentinel = bodyEl && bodyEl.querySelector(".calendar-mobile__load-sentinel");
1723        if (!observer || !sentinel) return;
1724        observer.unobserve(sentinel);
1725        observer.observe(sentinel);
1726    }
1727
1728    function loadMoreMonths() {
1729        if (!fpInstance || loadedMonths >= totalMonths) return;
1730        loadedMonths = Math.min(totalMonths, loadedMonths + MONTHS_BATCH);
1731        const selectingStay = fpInstance.selecting_stay;
1732        fpInstance.set("showMonths", loadedMonths);
1733        fpInstance.selecting_stay = selectingStay;
1734        scheduleMonthHeaders(fpInstance);
1735        if (loadedMonths < totalMonths) {
1736            armLoadMore();
1737        }
1738    }
1739
1740    // Bottom sentinel drives incremental month loading (works with or without overflow/selection).
1741    function setupLoadMore() {
1742        if (!bodyEl || bodyEl._loadMoreIO) return;
1743        const sentinel = document.createElement("div");
1744        sentinel.className = "calendar-mobile__load-sentinel";
1745        bodyEl.appendChild(sentinel);
1746        bodyEl._loadMoreIO = new IntersectionObserver(
1747            function (entries) {
1748                for (let i = 0; i < entries.length; i++) {
1749                    if (entries[i].isIntersecting) {
1750                        loadMoreMonths();
1751                        break;
1752                    }
1753                }
1754            },
1755            { root: bodyEl, rootMargin: "240px" }
1756        );
1757        bodyEl._loadMoreIO.observe(sentinel);
1758    }
1759
1760    function loadCalendarData(instance, options) {
1761        const hotelPk = resolveHotelPk();
1762        const calendarUrl = resolveCalendarUrl();
1763        const scrollToFocus = !!(options && options.scrollToFocus);
1764
1765        function applyData(data) {
1766            instance.calendar = data;
1767            refreshCalendarUi(instance);
1768            if (scrollToFocus) {
1769                scheduleScrollToFocus(instance, getFocusDate(instance));
1770            }
1771        }
1772
1773        if (
1774            kombi.calendarDayPrice &&
1775            hotelPk &&
1776            kombi.calendarDayPrice.getLastHotelPk() === hotelPk &&
1777            kombi.calendarDayPrice.getCachedData()
1778        ) {
1779            applyData(kombi.calendarDayPrice.getCachedData());
1780            return;
1781        }
1782
1783        if (hotelPk && calendarUrl && kombi.calendarDayPrice) {
1784            kombi.calendarDayPrice.fetchCalendar(calendarUrl, hotelPk).then(applyData);
1785            return;
1786        }
1787
1788        applyData(null);
1789    }
1790
1791    function createFlatpickr() {
1792        const minDate = getMinDate();
1793        const maxDate = getMaxDate();
1794        totalMonths = Math.max(2, monthsBetween(minDate, maxDate));
1795        loadedMonths = Math.min(INITIAL_MONTHS, totalMonths);
1796        const defaultDates = parseDefaultDates();
1797
1798        fpInstance = flatpickr(mountEl, {
1799            mode: "range",
1800            inline: true,
1801            animate: false,
1802            disableMobile: true,
1803            locale: "pt",
1804            dateFormat: "Y-m-d",
1805            altInput: false,
1806            minDate: minDate,
1807            maxDate: maxDate,
1808            showMonths: loadedMonths,
1809            defaultDate: defaultDates,
1810            monthSelectorType: "static",
1811            onReady: function (selectedDates, dateStr, instance) {
1812                instance.calendarContainer.classList.add("calendar-mobile__fp");
1813                scheduleMonthHeaders(instance);
1814                updateClearButtonState();
1815            },
1816            onMonthChange: function (selectedDates, dateStr, instance) {
1817                scheduleMonthHeaders(instance);
1818            },
1819            onChange: function (selectedDates, dateStr, instance) {
1820                const y = scrollBeforeSelect;
1821                updateClearButtonState();
1822                // Check-in stays the anchor after the range closes, so the lazy month load
1823                // rebuilds keep blocking earlier dates and keep the min-stay tip in place.
1824                instance.selecting_stay = selectedDates.length ? selectedDates[0] : null;
1825                anchorToFirstMonth(instance);
1826                instance.redraw();
1827                scheduleMonthHeaders(instance);
1828                if (document.activeElement && typeof document.activeElement.blur === "function") {
1829                    document.activeElement.blur();
1830                }
1831                restoreBodyScroll(y);
1832            },
1833            onDayCreate: function (dObj, dStr, fp, dayElem) {
1834                // Flatpickr focuses the picked day; default focus scrolls .calendar-mobile__body.
1835                if (!dayElem._focusNoScroll) {
1836                    dayElem._focusNoScroll = true;
1837                    const nativeFocus = dayElem.focus.bind(dayElem);
1838                    dayElem.focus = function (opts) {
1839                        nativeFocus(Object.assign({ preventScroll: true }, opts || {}));
1840                    };
1841                }
1842                kombi.fp_apply_day_rules(fp, dayElem);
1843                scheduleMonthHeaders(fp);
1844            },
1845        });
1846
1847        fpInstance.calendar = null;
1848        fpInstance.selecting_stay = null;
1849        loadCalendarData(fpInstance);
1850        setupLoadMore();
1851    }
1852
1853    function ensureModal() {
1854        if (!modalInstance) {
1855            modalInstance = bootstrap.Modal.getOrCreateInstance(modalEl);
1856        }
1857        return modalInstance;
1858    }
1859
1860    function openMobileCalendar() {
1861        ensureModal().show();
1862    }
1863
1864    function clearSelection(keepOpen) {
1865        if (!fpInstance) return;
1866        fpInstance.clear();
1867        fpInstance.selecting_stay = null;
1868        loadedMonths = Math.min(INITIAL_MONTHS, totalMonths);
1869        if (fpInstance.config.showMonths !== loadedMonths) {
1870            fpInstance.set("showMonths", loadedMonths);
1871        }
1872        anchorToFirstMonth(fpInstance);
1873        resetScrollTop();
1874        writeHiddenDates([]);
1875        syncDisplayFromHidden();
1876        updateClearButtonState();
1877        if (keepOpen) {
1878            fpInstance.redraw();
1879            scheduleMonthHeaders(fpInstance);
1880        }
1881    }
1882
1883    function showSearchAfterHide() {
1884        const searchForm = document.getElementById("SearchForm");
1885        if (!searchForm) return;
1886        modalEl.addEventListener(
1887            "hidden.bs.modal",
1888            function () {
1889                bootstrap.Modal.getOrCreateInstance(searchForm).show();
1890            },
1891            { once: true }
1892        );
1893    }
1894
1895    function advanceSelection() {
1896        if (!fpInstance) return;
1897        const selected = fpInstance.selectedDates || [];
1898        if (selected.length !== 2 || selected[0].getTime() >= selected[1].getTime()) {
1899            return;
1900        }
1901        writeHiddenDates(selected);
1902        syncDisplayFromHidden();
1903        showSearchAfterHide();
1904        ensureModal().hide();
1905    }
1906
1907    function bindActions() {
1908        if (modalClearButton) {
1909            modalClearButton.addEventListener("click", function () {
1910                if (modalClearButton.disabled) return;
1911                clearSelection(true);
1912            });
1913        }
1914        if (advanceButton) {
1915            advanceButton.addEventListener("click", advanceSelection);
1916        }
1917        if (backButton) {
1918            // data-bs-dismiss hides the calendar; this reopens the search modal, like Avançar.
1919            backButton.addEventListener("click", showSearchAfterHide);
1920        }
1921        if (bodyEl) {
1922            // Capture before Flatpickr select/focus so we can restore after currentMonth jumps.
1923            bodyEl.addEventListener(
1924                "pointerdown",
1925                function () {
1926                    scrollBeforeSelect = bodyEl.scrollTop;
1927                },
1928                true
1929            );
1930        }
1931        if (fieldClearButton) {
1932            fieldClearButton.addEventListener("click", function (event) {
1933                event.preventDefault();
1934                event.stopPropagation();
1935                clearSelection(false);
1936            });
1937        }
1938        if (displayInput) {
1939            displayInput.addEventListener("keydown", function (event) {
1940                if (event.key === "Enter" || event.key === " ") {
1941                    event.preventDefault();
1942                    openMobileCalendar();
1943                }
1944            });
1945        }
1946    }
1947
1948    function bindModalLifecycle() {
1949        modalEl.addEventListener("shown.bs.modal", function () {
1950            if (!fpInstance) {
1951                createFlatpickr();
1952            }
1953            syncMonthWindow(fpInstance);
1954            loadCalendarData(fpInstance, { scrollToFocus: true });
1955            syncLegendVisibility();
1956            updateClearButtonState();
1957            if (kombi.calendarDayPrice) {
1958                kombi.calendarDayPrice.refreshOnCalendarOpen();
1959            }
1960        });
1961    }
1962
1963    function bindSearchTabChange() {
1964        document.addEventListener("searchtab:change", function (e) {
1965            const isPackage = e.detail && e.detail.isPackage;
1966            syncLegendVisibility();
1967            if (!fpInstance) return;
1968
1969            syncMonthWindow(fpInstance);
1970            const minDate = isPackage ? getPackageMinDate() : new Date().fp_incr(1);
1971            const dates = fpInstance.selectedDates || [];
1972            const minTs = minDate.getTime();
1973            const hasInvalid = dates.some(function (d) {
1974                return d.getTime() < minTs;
1975            });
1976            if (hasInvalid) {
1977                clearSelection(true);
1978            }
1979            fpInstance.calendar = kombi.calendarDayPrice
1980                ? kombi.calendarDayPrice.getCachedData()
1981                : null;
1982            refreshCalendarUi(fpInstance);
1983        });
1984    }
1985
1986    const previousOpenDatePicker = kombi.openDatePicker;
1987    kombi.openDatePicker = function () {
1988        if (window.matchMedia("(min-width: 992px)").matches) {
1989            if (typeof previousOpenDatePicker === "function") {
1990                return previousOpenDatePicker.apply(this, arguments);
1991            }
1992            return;
1993        }
1994        const searchModal = document.getElementById("SearchForm");
1995        if (searchModal) {
1996            bootstrap.Modal.getOrCreateInstance(searchModal).show();
1997        }
1998        openMobileCalendar();
1999    };
2000
2001    kombi.calendarMobile.applyCalendarData = function (data) {
2002        if (!fpInstance) return;
2003        fpInstance.calendar = data;
2004        refreshCalendarUi(fpInstance);
2005    };
2006
2007    syncDisplayFromHidden();
2008    syncLegendVisibility();
2009    updateClearButtonState();
2010    bindActions();
2011    bindModalLifecycle();
2012    bindSearchTabChange();
2013};
2014;
2015kombi.paymentPagBank = function () {
2016  let form_payment = document.querySelector("form.form-payment");
2017  let payment_buttons = document.querySelectorAll(".checkout-pagar-button");
2018  let pixDetailDiscount = document.querySelector("#pix_discount");
2019  //Credit Card Fields
2020  let pagbank_cc = document.querySelector("form.form-payment .payment-pagbank-credit-card");
2021  let creditCardDiv = document.querySelector("#credit-card");
2022  var pixSection = document.getElementById("pix");
2023  let totalWithDiscountDiv = document.getElementById("total_with_discounts");
2024  let totalWithoutDiscountsDiv = document.getElementById("total_without_discounts");
2025  let totalDiv = document.getElementById("total_div");
2026  let paymentDiscountSummary = document.querySelectorAll(".price-summary-payment-discount");
2027  let price_summary_installments = document.querySelectorAll('.price-summary-installments');
2028  let cc_installments_field = null;
2029
2030  if (pagbank_cc) {
2031      cc_installments_field = pagbank_cc.querySelector("#cc-installments");
2032  }
2033
2034  if (pixSection) {
2035      pixSection.addEventListener("shown.bs.collapse", function () {
2036        form_payment.addEventListener("submit", function (event) {
2037          const pix_cpf = document.querySelector('#pix-cpf');
2038          if (pix_cpf.value) {
2039            const validCpf = kombi.cpfValidator(pix_cpf.value);
2040            const pix_cpf_error_message = document.querySelector('#pix-cpf-error-message');
2041            if (!validCpf) {
2042              event.preventDefault();
2043              pix_cpf.classList.add('is-invalid');
2044              pix_cpf_error_message.querySelector("span").textContent = 'Por favor, informe um CPF válido';
2045              pix_cpf_error_message.style.display = 'block';
2046              payment_buttons.forEach(function (button) {
2047                kombi.resetButton(button)
2048                if (button.disabled) {
2049                  button.disabled = false;
2050                }
2051              });
2052            } else {
2053              pix_cpf_error_message.style.display = 'none';
2054            }
2055          } 
2056          const pix_full_name = document.querySelector('#pix-full_name');
2057          const pix_full_name_error_message = document.querySelector('#pix-full-name-error-message');
2058          if (pix_full_name.value) {
2059            const validFullName = kombi.fullNameValidator(pix_full_name.value);   
2060            if (!validFullName) {
2061              event.preventDefault();
2062              pix_full_name.classList.add('is-invalid');
2063              pix_full_name_error_message.querySelector("span").textContent = 'Por favor, informe seu nome completo';
2064              pix_full_name_error_message.style.display = 'block';
2065              payment_buttons.forEach(function (button) {
2066                kombi.resetButton(button)
2067                if (button.disabled) {
2068                  button.disabled = false;
2069                }
2070              });
2071            }  
2072          } else {
2073            pix_full_name_error_message.style.display = 'none';  
2074          }
2075        });
2076
2077
2078      });
2079      
2080      pixSection.addEventListener("shown.bs.collapse", function () {
2081        cc_installments_field.value = "";
2082        price_summary_installments.forEach(function (price_summary_installments) {
2083          price_summary_installments.style.display = 'none';
2084          price_summary_installments.textContent = "";
2085        })
2086        if (document.getElementById("discount_pix_value")) {
2087          totalWithDiscountDiv.classList.remove("d-none");
2088          totalWithDiscountDiv.classList.add("d-flex");
2089          paymentDiscountSummary.forEach(function (element) {
2090            element.classList.remove("d-none");
2091            element.classList.add("d-flex");
2092          });
2093          totalWithoutDiscountsDiv.classList.remove("d-flex");
2094          totalWithoutDiscountsDiv.classList.add("d-none");
2095          totalDiv.style.display = "flex";
2096          totalDiv.style.flexDirection = "column";
2097        }
2098      });
2099
2100      pixSection.addEventListener("hidden.bs.collapse", function () {
2101        price_summary_installments.forEach(function (price_summary_installments) {
2102          price_summary_installments.style.display = 'flex';
2103        })
2104        if (document.getElementById("discount_pix_value")) {
2105          totalWithDiscountDiv.classList.remove("d-flex");
2106          totalWithDiscountDiv.classList.add("d-none");
2107          paymentDiscountSummary.forEach(function (element) {
2108            element.classList.remove("d-flex");
2109            element.classList.add("d-none");
2110          });
2111          totalWithoutDiscountsDiv.classList.remove("d-none");
2112          totalWithoutDiscountsDiv.classList.add("d-flex");
2113          totalDiv.style.flexDirection = "row";
2114        }
2115
2116        let pix_full_name = document.getElementById("pix-full_name");
2117        let pix_full_name_error_message = document.getElementById("pix-full-name-error-message");
2118        if (pix_full_name) {
2119          pix_full_name.value = "";
2120          pix_full_name.classList.remove("is-invalid");
2121        }
2122        if (pix_full_name_error_message) {
2123          pix_full_name_error_message.style.display = "none";
2124        }
2125
2126        let pix_cpf = document.getElementById("pix-cpf");
2127        let pix_cpf_error_message = document.getElementById("pix-cpf-error-message");
2128        if (pix_cpf) {
2129          pix_cpf.value = "";
2130          pix_cpf.classList.remove("is-invalid");
2131        }
2132        if (pix_cpf_error_message) {
2133          pix_cpf_error_message.style.display = "none";
2134        }
2135      });
2136  }
2137  if (pagbank_cc) {
2138    let cc_number_field = pagbank_cc.querySelector("#cc-number");
2139    let cc_encrypted_field = pagbank_cc.querySelector("#id_credit_card-cc_encrypted");
2140    let cc_bin_field = pagbank_cc.querySelector("#id_credit_card-cc_bin");
2141    let cc_last_digits_field = pagbank_cc.querySelector("#id_credit_card-cc_last_digits");
2142    let cc_expiration_month_field = pagbank_cc.querySelector("#id_credit_card-cc_expiration_month");
2143    let cc_expiration_year_field = pagbank_cc.querySelector("#id_credit_card-cc_expiration_year");
2144    let cc_final_price = pagbank_cc.querySelector("#cc-final-price");
2145    let cc_cpf = pagbank_cc.querySelector("#cc-cpf")
2146    let cc_cpf_error_message = document.getElementById("cc-cpf-error-message");
2147    let pixFullName = document.getElementById("pix-full_name")
2148    let pixCpf = document.getElementById("pix-cpf")
2149    let pix_cpf_error_message = document.getElementById("pix-cpf-error-message");
2150    form_payment.addEventListener("submit", function (event) {
2151      if (form_payment.checkValidity()) {
2152        payment_buttons.forEach(function (button) {
2153          if (!button.disabled) {
2154            if (!button) {
2155              return;
2156          }
2157          button.classList.add('disabled');
2158          var buttonText = button.querySelector('.buttonText');
2159          if (buttonText) {
2160              buttonText.classList.add('d-none');
2161          }
2162          var spinner = button.querySelector('.buttonSpinner');
2163          if (spinner) {
2164              spinner.classList.remove('d-none');
2165          }
2166          }
2167        });
2168      }
2169
2170      try {
2171        if (PagSeguro && cc_number_field.disabled === false) {
2172          let exp = pagbank_cc.querySelector("#cc-exp").value.split("/");
2173
2174          let opts = {
2175            holder: pagbank_cc.querySelector("#cc-name").value,
2176            number: kombi.getOnlyDigits(cc_number_field.value),
2177            securityCode: pagbank_cc.querySelector("#cc-csc").value,
2178            expMonth: parseInt(exp[0]),
2179            expYear: 2000 + parseInt(exp[1]),
2180            publicKey: pagbank_cc.querySelector("#payment_public_key").value,
2181          };
2182          
2183          const card = PagSeguro.encryptCard(opts);
2184          const error_mappings = {
2185            "INVALID_SECURITY_CODE": {
2186              errorMessageId: "cc-csc-error-message",
2187              inputId: "cc-csc",
2188              errorMessage: "Por favor, digite um código válido"
2189            },
2190            "INVALID_EXPIRATION_YEAR": {
2191              errorMessageId: "cc-exp-error-message",
2192              inputId: "cc-exp",
2193              errorMessage: "Por favor, digite uma data válida"
2194            },
2195            "INVALID_HOLDER": {
2196              errorMessageId: "cc-name-error-message",
2197              inputId: "cc-name",
2198              errorMessage: "Por favor, digite um nome completo"
2199            }
2200          };
2201
2202          if (card.hasErrors) {
2203            event.preventDefault();
2204            payment_buttons.forEach(function (button) {
2205              if (button.disabled) {
2206                button.disabled = false;
2207              }
2208            });
2209            card.errors.forEach(error => {
2210              let error_code = error.code;
2211              if (error_mappings.hasOwnProperty(error_code)) {
2212                let mapping = error_mappings[error_code];
2213                let error_div = document.getElementById(mapping.errorMessageId);
2214                error_div.querySelector("span").textContent = mapping.errorMessage;
2215                error_div.style.display = 'block';
2216                let inputField = document.getElementById(mapping.inputId);
2217                inputField.classList.add("is-invalid");
2218              }
2219            });
2220          }
2221
2222          cc_encrypted_field.value = card.encryptedCard;
2223          cc_bin_field.value = kombi.getOnlyDigits(cc_number_field.value).slice(0, 6);
2224          cc_last_digits_field.value = kombi.getOnlyDigits(cc_number_field.value).slice(-4);
2225          cc_expiration_month_field.value = parseInt(exp[0]);
2226          cc_expiration_year_field.value = 2000 + parseInt(exp[1]);
2227
2228          if (cc_cpf.value) {
2229            const validCpf = kombi.cpfValidator(cc_cpf.value);
2230            if (!validCpf) {
2231              event.preventDefault();
2232              cc_cpf.classList.add('is-invalid');
2233              cc_cpf_error_message.querySelector("span").textContent = 'Por favor, informe um CPF válido';
2234              cc_cpf_error_message.style.display = 'block';
2235              payment_buttons.forEach(function (button) {
2236                kombi.resetButton(button)
2237                if (button.disabled) {
2238                  button.disabled = false;
2239                }
2240              });
2241            } else {
2242              cc_cpf_error_message.style.display = 'none';
2243            }
2244          }
2245
2246          
2247        }
2248      } catch (error) {
2249        event.preventDefault();
2250        console.error(error);
2251      }
2252    });
2253
2254    cc_number_field.addEventListener("input", function (event) {
2255      let cc_number_error_message = document.getElementById("cc-number-error-message");
2256      let cc_number_error_message_text = document.getElementById("cc-number-error-message-text");
2257      let cc_icon = document.getElementById("cc-icon")
2258      let cc_number = kombi.getOnlyDigits(cc_number_field.value);
2259      let cc_final_price = pagbank_cc.querySelector("#cc-final-price");
2260
2261      if (cc_number.length >= 14) {
2262        if (cc_installments_field.searching) return;
2263        cc_installments_field.searching = true;
2264        let params = new URLSearchParams({
2265          payment_method: "credit_card",
2266          final_price: parseFloat(
2267            cc_final_price.value.replace(/,/g, ".")
2268          ).toFixed(2),
2269          card_bin: cc_number.slice(0, 6),
2270        });
2271        let cartId = kombi.getCheckoutCartId();
2272        if (cartId) {
2273          params.set("cart_id", cartId);
2274        }
2275
2276        cc_installments_field.innerHTML =
2277          "<option value=\"\">Buscando as opções de parcelamento ...</option>";
2278        fetch("/payment/payment-fees?" + params.toString())
2279          .then((response) => {
2280            if (!response.ok) {
2281              throw new Error("Network response was not ok");
2282            }
2283            return (fees = response.json());
2284          })
2285          .then((fees) => {
2286            cc_installments_field.innerHTML = "<option value=\"\">Escolha o número de parcelas</option>";
2287            let installment_plans = fees["installment_plans"];
2288            installment_plans.forEach((plan) => {
2289              let text =
2290                plan.installments +
2291                "x de R$ " +
2292                kombi.formatAmount(plan.formatted_installment_value) +
2293                " | " +
2294                "Total: R$ " +
2295                kombi.formatAmount(plan.amount.formatted_value);
2296              if (plan.interest_free == true) {
2297                text += " Sem juros!";
2298              }
2299
2300              var option = document.createElement("option");
2301              option.value = JSON.stringify(plan);
2302              option.text = text;
2303              cc_installments_field.appendChild(option);
2304            });
2305          })
2306          .catch((error) => {
2307            cc_number_field.classList.add("is-invalid");
2308            cc_number_error_message_text.textContent = 'Por favor, informe um número válido';
2309            cc_icon.classList.add("d-none")
2310            cc_number_error_message.style.display = 'block';
2311            console.error("There was a problem with the fetch operation:", error);
2312          });
2313      } else {
2314        cc_installments_field.innerHTML =
2315          '<option value="">' +
2316          cc_installments_field.getAttribute("data-placeholder") +
2317          "</option>";
2318        cc_installments_field.searching = false;
2319        if (cc_number_field.classList.contains("is-invalid")) {
2320          cc_icon.classList.remove("d-none")
2321        }
2322      }
2323    });
2324    cc_installments_field.addEventListener("change", function (event) {
2325      let cc_total_value = document.querySelector('#cc_total_value');
2326      let cc_total_values = document.querySelectorAll('#cc_total_value');
2327      let price_summary_total_value_all = document.querySelectorAll('#price_summary_total_value');
2328      let price_summary_fees = document.querySelectorAll('.price-summary-fees');
2329      let price_summary_fees_value = document.querySelectorAll('.price-summary-fees-value');
2330      let price_summary_fees_label = document.querySelectorAll('.price-summary-fees-label');
2331      let price_summary_installments = document.querySelectorAll('.price-summary-installments');
2332      let cc_final_price = document.querySelector("#cc-final-price");
2333      if (cc_installments_field.value) {
2334        installments_json = JSON.parse(cc_installments_field.value);
2335        let total_amount = parseFloat(cc_final_price.value.replace(/,/g, "."));
2336        let total_amount_with_fees = installments_json.amount.value / 100;
2337        let total_fees = total_amount_with_fees - total_amount;
2338
2339        let installment_number_str = installments_json.installments.toString();
2340
2341        price_summary_installments.forEach(function (price_summary_installments) {
2342          price_summary_installments.style.display = '';
2343          price_summary_installments.textContent = kombi.template.render(
2344            installments_json.interest_free ?
2345              price_summary_installments.dataset['withoutFeesText'] :
2346              price_summary_installments.dataset['withFeesText'],
2347            {
2348              'installment_number_str': installment_number_str,
2349              'installment_value_str': installments_json.formatted_installment_value,
2350            }
2351          );
2352        });
2353
2354        if (total_fees >= 0.01) {
2355          price_summary_fees.forEach(function (element) {
2356            element.style.display = "";
2357          });
2358          price_summary_fees_value.forEach(function (element) {
2359            element.style.display = "";
2360            var currencySymbol = document.getElementById('currency-symbol').textContent;
2361            element.textContent = currencySymbol + new Intl.NumberFormat(
2362              'pt-BR',
2363              { minimumFractionDigits: 2, maximumFractionDigits: 2 }
2364            ).format(total_fees);
2365          });
2366          price_summary_fees_label.forEach(function (element) {
2367            element.style.display = "";
2368          });
2369        } else {
2370          price_summary_fees.forEach(function (element) {
2371            element.style.display = "none";
2372          });
2373          price_summary_fees_value.forEach(function (element) {
2374            element.style.display = "none";
2375          });
2376          price_summary_fees_label.forEach(function (element) {
2377            element.style.display = "none";
2378          });
2379        };
2380
2381        let formattedTotal = new Intl.NumberFormat(
2382          'pt-BR',
2383          { minimumFractionDigits: 2, maximumFractionDigits: 2 }
2384        ).format(total_amount_with_fees);
2385        cc_total_values.forEach(function (el) { el.textContent = formattedTotal; });
2386        } else {
2387        price_summary_installments.forEach(function (price_summary_installments) {
2388          price_summary_installments.textContent = '';
2389        });
2390      }
2391
2392      try {
2393        price_summary_total_value_all.forEach(element => {
2394          element.textContent = cc_total_value.textContent;
2395        });
2396      } catch (error) {
2397        console.error(error);
2398      }
2399
2400    });
2401
2402    if (creditCardDiv) {
2403      creditCardDiv.addEventListener("hidden.bs.collapse", function () {
2404        let cc_final_price = document.querySelector("#cc-final-price");
2405        if (!cc_final_price) return;
2406
2407        let original_total = parseFloat(cc_final_price.value.replace(/,/g, "."));
2408        let formattedTotal = new Intl.NumberFormat(
2409          'pt-BR',
2410          { minimumFractionDigits: 2, maximumFractionDigits: 2 }
2411        ).format(original_total);
2412
2413        document.querySelectorAll('#cc_total_value').forEach(function (el) {
2414          el.textContent = formattedTotal;
2415        });
2416
2417        document.querySelectorAll('#price_summary_total_value').forEach(function (el) {
2418          el.textContent = formattedTotal;
2419        });
2420
2421        document.querySelectorAll('.price-summary-installments').forEach(function (el) {
2422          el.textContent = '';
2423          el.style.display = 'none';
2424        });
2425
2426        document.querySelectorAll('.price-summary-fees').forEach(function (el) {
2427          el.style.display = 'none';
2428        });
2429
2430        document.querySelectorAll('.price-summary-fees-value').forEach(function (el) {
2431          el.style.display = 'none';
2432        });
2433
2434        document.querySelectorAll('.price-summary-fees-label').forEach(function (el) {
2435          el.style.display = 'none';
2436        });
2437
2438        creditCardDiv.querySelectorAll('.cc-input').forEach(function (field) {
2439          if (field.tagName === 'SELECT') {
2440            field.innerHTML =
2441              '<option value="">' +
2442              field.getAttribute('data-placeholder') +
2443              '</option>';
2444          } else {
2445            field.value = '';
2446          }
2447          field.classList.remove('is-invalid');
2448          let errorMessage = document.getElementById(field.id + '-error-message');
2449          if (errorMessage) {
2450            errorMessage.style.display = 'none';
2451          }
2452        });
2453
2454        let cc_icon = document.getElementById('cc-icon');
2455        if (cc_icon) {
2456          cc_icon.classList.remove('d-none');
2457        }
2458      });
2459    }
2460
2461    if (!pixSection) {
2462      let buttons = document.querySelectorAll("body.checkout [data-bs-target='#credit-card'], body.payment-view [data-bs-target='#credit-card']");
2463
2464      buttons.forEach(button => {
2465          button.removeAttribute("data-bs-toggle");
2466          button.removeAttribute("onclick");
2467          button.classList.remove("collapsed");
2468      });
2469
2470      let cc_collapse = new bootstrap.Collapse(creditCardDiv, { toggle: false });
2471
2472      cc_collapse.show();
2473    }
2474  }
2475  if (form_payment) {
2476    let input_fields = form_payment.querySelectorAll("input");
2477    input_fields.forEach(input_field => {
2478      input_field.addEventListener("input", function () {
2479        if (this.classList.contains("is-invalid")) {
2480          this.classList.remove("is-invalid");
2481          let errorDivId = this.id + "-error-message";
2482          let errorDiv = document.getElementById(errorDivId);
2483          if (errorDiv) {
2484            errorDiv.style.display = "none";
2485          }
2486        }
2487      });
2488    });
2489  }
2490}
2491kombi.formatAmount = function (amount) {
2492  let number = parseFloat(amount.replace(",", "."));
2493  return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number);
2494}
2495
2496kombi.isCollapseOpen = function(collapse_id) {
2497    let collapse = document.getElementById(collapse_id);
2498    return collapse.classList.contains("show") || collapse.classList.contains("collapsing");
2499};
2500kombi.activeLinks = function () {
2501  let found = false;
2502  document.querySelectorAll(".nav-link[href='" + location.pathname + "']").forEach((item) => {
2503    found = true;
2504    item.classList.add("active");
2505  });
2506
2507  if (!found) {
2508    if (location.pathname.indexOf("/hotel/") == 0){
2509      document.querySelectorAll(".nav-link[href='/hotel/']").forEach((item) => {
2510        item.classList.add("active");
2511      });
2512    }
2513  }
2514
2515
2516};
2517;
2518kombi.currentYear = function() {
2519    function getYear() {
2520        const date = new Date()
2521        const year = date.getFullYear()
2522        return year
2523    }
2524    const yearElement = document.getElementById('year')
2525    const year = getYear()
2526    if (yearElement)  {
2527        yearElement.textContent = year
2528    }
2529}
2530
2531kombi.inputNumberPlusMinusBtn = function() {
2532    var numberInput = document.getElementById('numberInput')
2533    var plusBtn = document.getElementById('plusBtn')
2534    var minusBtn = document.getElementById('minusBtn')
2535
2536    plusBtn?.addEventListener('click', function () {
2537        numberInput.value = parseInt(numberInput.value) + 1
2538    })
2539    minusBtn?.addEventListener('click', function () {
2540        if (parseInt(numberInput.value) > 0) {
2541            numberInput.value = parseInt(numberInput.value) - 1
2542        }
2543    })
2544}    
2545;
2546kombi.requests = {
2547    request: function (url, method, headers, body) {
2548        return fetch(url, {
2549            method: method.toUpperCase(),
2550            headers: headers,
2551            body: body
2552        })
2553    },
2554    postFormJson: async function (url, form, csrfmiddlewaretoken) {
2555        try {
2556            if (!form.__disabled) {
2557                form.__disabled = true
2558                const postData = new FormData(form)
2559                const csrfToken = csrfmiddlewaretoken || postData.get('csrfmiddlewaretoken')
2560                const headers = {
2561                    'Content-Type': 'application/json',
2562                    'X-CSRFToken': csrfToken
2563                }
2564                const response = await this.request(
2565                    url,
2566                    'POST',
2567                    headers,
2568                    JSON.stringify(Object.fromEntries(postData.entries()))
2569                ).catch((error) => {
2570                    console.error("There has been a problem with your fetch operation:", error);
2571                });
2572
2573                form.__disabled = false
2574                return await response.json();
2575            } else {
2576                console.info("postFormJson is running, wait", form);
2577            }
2578        } catch (error) {
2579            form.__disabled = false
2580            console.error("There has been a problem with your postFormJson", error);
2581        }
2582    },
2583    getText: async function (url) {
2584        try {
2585
2586            let response = await this.request(url, 'GET')
2587                .catch((error) => {
2588                    console.error("There has been a problem with your fetch operation:", error);
2589                });
2590
2591            return await response.text();
2592        } catch (error) {
2593            console.error("There has been a problem with your getText", error);
2594        }
2595    }
2596}
2597;
2598kombi.alert = function (parent, message, type, clear_all) {
2599    const wrapper = document.createElement('div')
2600    wrapper.innerHTML = [
2601        `<div class="alert alert-${type} fade show alert-dismissible" role="alert">`,
2602        `   <div>${message}</div>`,
2603        '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
2604        '</div>'
2605    ].join('')
2606    if (clear_all) {
2607        parent.replaceChildren(wrapper)
2608    } else {
2609        parent.append(wrapper)
2610    }
2611
2612    setTimeout(() => {
2613        wrapper.querySelector(".btn-close").click()
2614    }, 5000)
2615}
2616
2617kombi.popFloatAlert = function (message, type) {
2618    type = type === 'error' ? 'danger' : type;
2619    const floatMessageBox = document.getElementById('FloatMessageContainer');
2620    floatMessageBox.innerHTML = `
2621                            <div class="alert alert-${type} float-alert text-center" role="alert">
2622                                ${message}
2623                            </div>
2624                        `;
2625};
2626kombi.template = {
2627  render: function (text, context) {
2628    let keys = Object.keys(context);
2629    let values = Object.values(context);
2630    return new Function(...keys, `return \`${text}\`;`)(...values);
2631  },
2632  render_from_url: function (url, context){
2633    try {
2634      let self = this;
2635      return kombi.requests.getText(url).then((text) => {
2636        return self.render(text, context);
2637      })
2638    } catch (error) {
2639      console.error("There has been a problem with your render_from_url", error);
2640    }
2641  }
2642}
2643;
2644kombi.reservation = {
2645    navLinkActiveStyle: function() {
2646        let activeLink = document.querySelector('.reservation .nav-link.active');
2647        let parents = document.querySelectorAll('.reservation .nav-item');
2648    
2649        parents.forEach(function(parent) {
2650            parent.classList.remove('active-parent');
2651        });
2652        if (activeLink) {
2653            activeLink.parentElement.classList.add('active-parent');
2654        }
2655    }
2656    // modal: function() {
2657    //     const myReservations = document.querySelector('body.dashboard');
2658    //     const modal = document.querySelector('#myReservationsModal');
2659    //     if (myReservations && modal) {
2660    //         var modal_my_reservations = new bootstrap.Modal('#myReservationsModal');
2661    //         modal_my_reservations.show()
2662    //     }
2663    // }
2664}
2665
2666;
2667kombi.profile = {
2668    profileImage: function () {
2669        let inputFile = document.getElementById("id_image_upload")
2670        inputFile.addEventListener("change", function() {
2671            document.getElementById("submit_image").click()
2672        })
2673    },
2674    edit: function (customClass) {
2675        let editElement = document.querySelector('.edit' + customClass);
2676        let saveElement = document.querySelector('.save' + customClass);
2677        if (editElement) {
2678            saveElement.classList.remove('d-flex');
2679            saveElement.classList.add('d-none');
2680            editElement.classList.remove('d-none');
2681            editElement.classList.add('d-flex');
2682        }
2683    },
2684    return: function (customClass) {
2685        let editElement = document.querySelector('.edit' + customClass);
2686        let saveElement = document.querySelector('.save' + customClass);
2687        if (editElement) {
2688            saveElement.classList.remove('d-none');
2689            saveElement.classList.add('d-flex');
2690            editElement.classList.remove('d-flex');
2691            editElement.classList.add('d-none');
2692        }
2693    },
2694    formatPhoneNumber: function () {
2695        let phoneNumberElement = document.getElementById('phone_number')
2696        if (phoneNumberElement) {
2697            let phoneNumber = phoneNumberElement.textContent.trim();
2698            if (phoneNumber.startsWith('+55')) {
2699                let numberWithoutDDD = phoneNumber.slice(3).replace(/\D/g, '');
2700                let formattedNumber = numberWithoutDDD.replace(/^(\d{2})(\d{5})(\d{4})$/, '+55 ($1) $2-$3');
2701                phoneNumberElement.textContent = formattedNumber;
2702            }
2703        }
2704
2705        let phoneInput = document.getElementById('id_phone_number');
2706        let errorMessage = document.getElementById('error-message');
2707        let errorMessageText = document.getElementById('error-message-text');
2708        if (phoneInput) {
2709            let lastValue = phoneInput.value;        
2710            phoneInput.addEventListener('input', kombi.dddValidation(phoneInput, errorMessage, errorMessageText, lastValue));
2711        }
2712    },
2713};
2714kombi.disableCollapseFields = function () {
2715  let toggleFields = function (item, disable) {
2716    item.querySelectorAll("input, select, textarea, button").forEach((field) => {
2717      field.disabled = disable;
2718    });
2719  }
2720  document.querySelectorAll(".disable-collapse-fields").forEach((item) => {
2721    item.addEventListener('hidden.bs.collapse', event => {
2722      toggleFields(item, true)
2723    });
2724    item.addEventListener('show.bs.collapse', event => {
2725      toggleFields(item, false)
2726    });
2727
2728    toggleFields(item, !item.classList.contains("show"))
2729  });
2730}
2731;
2732kombi.travelersFormValidation = function () {
2733
2734    let form = document.querySelector("form.form-payment");
2735
2736    if (form !== null) {
2737        let formElements = Array.from(form.elements);
2738
2739        formElements.forEach(function (element) {
2740            if (element.tagName == 'INPUT' && element.name.startsWith('item_')) {
2741                let fieldInput = document.getElementById(element.id);
2742
2743                if (fieldInput) {
2744                    fieldInput.addEventListener('input', function () {
2745                        if (fieldInput.classList.contains('is-invalid')) {
2746                            fieldInput.classList.remove('is-invalid');
2747                        }
2748                    });
2749                    if (fieldInput.id.includes('phone')) {
2750                        const errorMessage = document.getElementById('error-message');
2751                        const errorMessageText = document.getElementById('error-message-text');
2752
2753                        let lastValue = fieldInput.value;
2754
2755                        fieldInput.addEventListener('input', kombi.dddValidation(fieldInput, errorMessage, errorMessageText, lastValue));
2756
2757                    };
2758                };
2759            };
2760            
2761        });
2762    }
2763};
2764kombi.checkoutTimer = {
2765    startTimer: function (timeStampLimit, display) {
2766        var timerInterval = setInterval(function () {
2767            var timeStampNow = parseInt(new Date().getTime() / 1000);
2768            var timer = timeStampLimit - timeStampNow;
2769            if (timer < 0) { timer = 0 };
2770            minutes = parseInt(timer / 60, 10);
2771            seconds = parseInt(timer % 60, 10);
2772            minutes = minutes < 10 ? "0" + minutes : minutes;
2773            seconds = seconds < 10 ? "0" + seconds : seconds;
2774            display.textContent = minutes + ":" + seconds;
2775            if (--timer < 0) {
2776                clearInterval(timerInterval);
2777                document.querySelectorAll(".checkout-pagar-button").forEach(function(button) {
2778                    button.setAttribute("disabled", true);
2779                });
2780                kombi.checkoutTimer.loadModal();
2781            }
2782        }, 1000);
2783    },
2784    setTimer: function () {
2785        var displays = document.querySelectorAll('.checkout_timer');
2786        if (displays.length > 0) {
2787            displays.forEach(function(display) {
2788                var timeStampLimit = parseInt(display.getAttribute('offer_timestamp_limit'));
2789                kombi.checkoutTimer.startTimer(timeStampLimit, display);
2790            });
2791        }
2792    },
2793    loadModal: function () {
2794        const expiredPixModal = document.querySelector('#expiredPixModal');
2795        if (expiredPixModal) {
2796            const modal_expired_pix = new bootstrap.Modal('#expiredPixModal');
2797            modal_expired_pix.show();
2798            kombi.checkoutTimer.expiredPix();
2799        } else {
2800            var payment = document.querySelector('#modal-unavailable');
2801            if (payment && payment.getAttribute('ispaymentunavailable') === 'True') {
2802                var modal_unavailable = new bootstrap.Modal('#modal-unavailable', {
2803                    backdrop: 'static',
2804                    keyboard: false,
2805                });
2806                modal_unavailable.show()
2807            } else {
2808                var modal_counter = new bootstrap.Modal('#modal-counter', {
2809                    backdrop: 'static',
2810                    keyboard: false,
2811                });
2812                modal_counter.show()
2813            }
2814        }
2815    },
2816    checkAvailability: function () {
2817        var available = document.querySelector('#modal-unavailable');
2818        if (available && (available.getAttribute('isunavailable') === 'True' || available.getAttribute('ispaymentunavailable') === 'True')) {
2819            var modal_unavailable = new bootstrap.Modal('#modal-unavailable', {
2820                backdrop: 'static',
2821                keyboard: false,
2822            });
2823            modal_unavailable.show()
2824        } else {
2825            kombi.checkoutTimer.setTimer();
2826        }
2827    },
2828    fixedDiv: function () {
2829        let headerHeight;
2830        let divHeader = document.querySelector('header');
2831        if (divHeader) {
2832            headerHeight = document.querySelector('header').offsetHeight;
2833        }
2834        let offerAlert = document.querySelector('.offer-alert');
2835        if (offerAlert) {
2836            let checkScroll = function () {
2837                if (window.innerWidth < 576) {
2838                    if (window.scrollY > headerHeight) {
2839                        offerAlert.classList.add('fixed-div');
2840                    } else {
2841                        offerAlert.classList.remove('fixed-div');
2842                    }
2843                }
2844            };
2845            window.addEventListener('scroll', checkScroll);
2846        }
2847    },
2848    expiredPix: function () {
2849        const pixText = document.querySelector('.pix-text');
2850        const pixImages = document.querySelectorAll('.pix-img');
2851        pixText.textContent = '';
2852        pixText.classList.add('py-3');
2853        pixImages.forEach(pixImg => {
2854            pixImg.remove();
2855        });
2856    }
2857}
2858
2859;
2860kombi.dddValidation = function (phoneInput, errorMessage, errorMessageText, lastValue) {
2861    return function() { 
2862        let currentValue = phoneInput.value;
2863        if (currentValue === lastValue && currentValue.length > 3 && currentValue.length < 8) {
2864            errorMessageText.textContent = 'Por favor, informe um DDD válido';
2865            errorMessage.style.display = 'block';
2866        } else {
2867            errorMessage.style.display = 'none';
2868        }
2869        lastValue = currentValue;
2870    }
2871};
2872kombi.cpfValidator = function (cpf) {
2873    if (cpf == null) {
2874        return false;
2875    }
2876    cpf = cpf.replace(/\D/g, '');
2877    if (cpf.length !== 11) {
2878        return false;
2879    }
2880    if (/^(\d)\1{10}$/.test(cpf)) {
2881        return false;
2882    }
2883    let sum = 0;
2884    let remainder;
2885    for (let i = 1; i <= 9; i++) {
2886        sum += parseInt(cpf.charAt(i - 1)) * (11 - i);
2887    }
2888    remainder = (sum * 10) % 11;
2889    remainder = (remainder === 10 || remainder === 11) ? 0 : remainder;
2890    if (remainder !== parseInt(cpf.charAt(9))) {
2891        return false;
2892    }
2893    sum = 0;
2894    for (let i = 1; i <= 10; i++) {
2895        sum += parseInt(cpf.charAt(i - 1)) * (12 - i);
2896    }
2897    remainder = (sum * 10) % 11;
2898    remainder = (remainder === 10 || remainder === 11) ? 0 : remainder;
2899    return remainder === parseInt(cpf.charAt(10));
2900};
2901kombi.sidebar = {
2902    collapseToggle: function() {
2903        let navItems = document.querySelectorAll('.sidebar .nav-item');
2904        navItems.forEach(item => {
2905            let isActive = item.querySelector('.nav-link').classList.contains('active');
2906            if (isActive) {
2907                let editImageBox = document.querySelector('.edit-image-box');
2908                if (item.classList.contains('profile-link')) {
2909                    editImageBox.classList.add('d-block');
2910                    editImageBox.classList.remove('d-none');
2911                }
2912            }
2913        });
2914    },
2915    menuLinkActive: function() {
2916        let menuLinks = document.querySelectorAll('.menu-links');
2917        menuLinks.forEach(link => {
2918            if(window.location.href == link.href) {
2919                link.classList.add('text-primary');
2920            }
2921        });
2922    },
2923    mobileDropdownModal: function() {
2924        let dropdownToggles = document.querySelectorAll('.nav-link.dropdown-toggle');
2925
2926        if (dropdownToggles) {
2927            dropdownToggles.forEach(function(toggle) {
2928                toggle.addEventListener('click', function(event) {
2929                    if (window.innerWidth < 576) {
2930                        let dropdownMenu = this.closest('.nav-item').querySelector('.dropdown-menu');
2931                        let menuContent = dropdownMenu.innerHTML;
2932                        let menuTitle = toggle.textContent || toggle.innerText;
2933
2934                        let modalBody = document.querySelector('#mobileDropdownModal .modal-body ul');
2935                        modalBody.innerHTML = menuContent;
2936                        let modalTitle = document.querySelector('#mobileDropdownModal .modal-title');
2937                        modalTitle.textContent = menuTitle.trim();
2938
2939                        let modal = new bootstrap.Modal(document.getElementById('mobileDropdownModal'));
2940                        modal.show();
2941                    }
2942                });
2943            });
2944        }
2945    },
2946    navBarFade: function() {
2947        document.querySelectorAll('.fade-container').forEach(function(fadeContainer) {
2948            let fadeEnd = fadeContainer.querySelector('.fade-end');
2949            let fadeElement = fadeContainer.querySelector('.fade-element');
2950            
2951            function updateFadePosition() {
2952                let maxScrollLeft = fadeEnd.scrollWidth - fadeEnd.clientWidth;
2953                let currentScroll = fadeEnd.scrollLeft;
2954                let fadeWidth = 100; 
2955                if (currentScroll >= maxScrollLeft) {
2956                    fadeElement.style.display = 'none';
2957                } else {
2958                    fadeElement.style.display = '';
2959                    fadeElement.style.width = `${Math.min(fadeWidth, maxScrollLeft - currentScroll)}px`;
2960                }
2961            }
2962    
2963            fadeEnd.addEventListener('scroll', updateFadePosition);
2964    
2965            updateFadePosition();
2966        });
2967    }
2968};
2969kombi.reloadInstallments = function () {
2970    let cc_number_field = document.getElementById("cc-number");
2971    if (!cc_number_field) {
2972        return;
2973    }
2974    let cc_number_error_message = document.getElementById("cc-number-error-message");
2975    let cc_number_error_message_text = document.getElementById("cc-number-error-message-text");
2976    let price_summary_installments = document.querySelectorAll('.price-summary-installments');
2977    let price_summary_total_value_all = document.querySelectorAll('#price_summary_total_value');
2978    let cc_total_value = document.querySelector('#cc_total_value');
2979    let cc_icon = document.getElementById("cc-icon")
2980    let cc_number = kombi.getOnlyDigits(cc_number_field.value);
2981    let cc_installments_field = document.getElementById("cc-installments");
2982    let cc_final_price = document.getElementById("cc-final-price");
2983    price_summary_total_value_all.forEach(element => {
2984        element.textContent = cc_total_value.textContent;
2985    });
2986    if (cc_number.length >= 14) {
2987        let params = new URLSearchParams({
2988            payment_method: "credit_card",
2989            final_price: parseFloat(
2990            cc_final_price.value.replace(/,/g, ".")
2991          ).toFixed(2),
2992            card_bin: cc_number.slice(0, 6),
2993        });
2994        let cartId = kombi.getCheckoutCartId();
2995        if (cartId) {
2996            params.set("cart_id", cartId);
2997        }
2998        cc_installments_field.innerHTML =
2999        "<option value=\"\">Buscando as opções de parcelamento ...</option>";
3000        fetch("/payment/payment-fees?" + params.toString())
3001            .then((response) => {
3002                if (!response.ok) {
3003                    throw new Error("Network response was not ok");
3004                }
3005                return (fees = response.json())
3006            })
3007            .then((fees) => {
3008                cc_installments_field.innerHTML = "<option value=\"\">Escolha o número de parcelas</option>";
3009                let installment_plans = fees["installment_plans"];
3010                installment_plans.forEach((plan) => {
3011                    let text =
3012                        plan.installments +
3013                        "x de R$ " +
3014                        kombi.formatAmount(plan.formatted_installment_value) +
3015                        " | " +
3016                        "Total: R$ " +
3017                        kombi.formatAmount(plan.amount.formatted_value);
3018                    if (plan.interest_free == true) {
3019                        text += " Sem juros!";
3020                    }
3021
3022                    var option = document.createElement("option");
3023                    option.value = JSON.stringify(plan);
3024                    option.text = text;
3025                    cc_installments_field.appendChild(option);
3026                });
3027            })
3028            .catch((error) => {
3029                cc_number_field.classList.add("is-invalid");
3030                cc_number_error_message_text.textContent = 'Por favor, informe um número válido';
3031                cc_icon.classList.add("d-none")
3032                cc_number_error_message.style.display = 'block';
3033                console.error("There was a problem with the fetch operation:", error);
3034            });
3035        price_summary_installments.forEach(function (price_summary_installments) {
3036            price_summary_installments.textContent = '';
3037        });
3038
3039    } else {
3040        cc_installments_field.innerHTML =
3041            '<option value="">' +
3042            cc_installments_field.getAttribute("data-placeholder") +
3043            "</option>";
3044        cc_installments_field.searching = false;
3045        if (cc_number_field.classList.contains("is-invalid")) {
3046            cc_icon.classList.remove("d-none")
3047        }
3048    }
3049};
3050kombi.syncInstallmentNoInterestTag = function (conditions) {
3051    if (!conditions) {
3052        return;
3053    }
3054
3055    const maxInstallments = Number(conditions.max_installments) || 0;
3056    if (maxInstallments > 0) {
3057        const maxLabel = document.getElementById("installment-max-label");
3058        if (maxLabel) {
3059            maxLabel.textContent = "Parcelamento em até " + maxInstallments + " vezes";
3060        }
3061        const maxLabelBottom = document.getElementById("installment-max-label-bottom");
3062        if (maxLabelBottom) {
3063            maxLabelBottom.textContent = "Até " + maxInstallments + "x no cartão";
3064        }
3065    }
3066
3067    const tag = document.getElementById("installment-no-interest-tag");
3068    const valueEl = document.getElementById("installment_no_interest_value");
3069    if (!tag || !valueEl) {
3070        return;
3071    }
3072    const noInterest = Number(conditions.no_interest_installments) || 0;
3073    if (noInterest > 1) {
3074        valueEl.textContent = noInterest + "x sem juros";
3075        tag.classList.remove("d-none");
3076    } else {
3077        valueEl.textContent = "";
3078        tag.classList.add("d-none");
3079    }
3080};
3081
3082// Template may assign conditions before this deferred bundle runs.
3083if (window.kombiInstallmentConditions) {
3084    kombi.syncInstallmentNoInterestTag(window.kombiInstallmentConditions);
3085}
3086;
3087kombi.sign = {
3088    displayErrors: function (error_list, form_id, is_raw = false, add_valid_tag = false) {
3089        /**
3090         * Display the error messages in the form
3091         * @param {object} error_list       Object with the error messages
3092         * @param {string} form_id          The form id to display the errors
3093         * @param {boolean} is_raw          If the error_list is a django form error in html format
3094         * @param {boolean} add_valid_tag   Add the is-valid class to the inputs that don't have errors
3095         * @returns {boolean}
3096         */
3097        function parseErrorString(errorString) {
3098            const regex = /<li>([^<]+)<ul class="errorlist"><li>([^<]+)/g;
3099            const matches = {};
3100            let match;
3101
3102            while ((match = regex.exec(errorString)) !== null) {
3103                const [, key, value] = match;
3104                if (!matches[key]) {
3105                    matches[key] = [];
3106                }
3107                matches[key].push(value);
3108            }
3109
3110            return matches;
3111        }
3112
3113        if (is_raw) {
3114            error_list = parseErrorString(error_list);
3115            console.log(error_list);
3116        }
3117
3118        let isValid = true;
3119        const form = document.getElementById(form_id);
3120        const all_inputs = form.querySelectorAll("input, select");
3121        Object.keys(error_list).forEach((field) => {
3122            const errorMessages = error_list[field];
3123            const formControl = form.querySelector("[name=" + field + "]");
3124            formControl.classList.add("is-invalid");
3125            formControl.classList.add("d-block");
3126            const invalidFeedback = formControl.parentElement.querySelector(".invalid-feedback");
3127            if (invalidFeedback) {
3128                invalidFeedback.innerHTML = errorMessages.join("<br>");
3129            }
3130            isValid = false;
3131        });
3132
3133
3134        if (add_valid_tag) {
3135            all_inputs.forEach((input) => {
3136                if (!error_list.hasOwnProperty(input.name)) {
3137                    const formControl = form.querySelector("[name=" + input.name + "]");
3138                    formControl.classList.add("is-valid");
3139                }
3140            });
3141        }
3142
3143        return isValid;
3144    },
3145
3146    cleanValidations: function (form_id) {
3147        /**
3148         * Clean the validation classes from the form
3149         * @param {string} form_id The form id to clean
3150         * @returns {void}
3151         *
3152         */
3153
3154        const form = document.getElementById(form_id);
3155        const all_inputs = form.querySelectorAll("input, select");
3156        all_inputs.forEach((input) => {
3157            if (input.type === "hidden") {
3158                return;
3159            }
3160            input.classList.remove("is-invalid");
3161            input.classList.remove("is-valid");
3162            const invalidFeedback = input.parentElement.querySelector(".invalid-feedback");
3163            if (invalidFeedback) {
3164                invalidFeedback.innerHTML = "";
3165            }
3166            const formControl = input.parentElement.querySelector(".form-control");
3167            if (formControl) {
3168                formControl.classList.remove("is-invalid");
3169                formControl.classList.remove("is-valid");
3170            }
3171        });
3172    },
3173
3174    clearFormFields: function (form_id) {
3175        /**
3176         * Clear the form fields
3177         * @param {string} form_id The form id to clear
3178         * @returns {void}
3179         *
3180         */
3181        const form = document.getElementById(form_id);
3182        form.classList.remove("was-validated");
3183        form.__disabled = false;
3184        const all_inputs = form.querySelectorAll("input, select");
3185        all_inputs.forEach((input) => {
3186            if (input.type === "hidden") {
3187                return;
3188            }
3189            input.value = "";
3190            input.classList.remove("is-invalid");
3191            input.classList.remove("is-valid");
3192        });
3193    },
3194
3195    emailValidation: function (email) {
3196        if (email.trim() === '') {
3197            return false;
3198        }
3199        const emailReg = /^([\w-.+]+@([\w-]+\.)+[\w-]{2,10})?$/;
3200        return emailReg.test(email);
3201    },
3202};
3203kombi.loginModalAutoOpen = {
3204  _timerId: null,
3205
3206  init: function () {
3207    const metaTag = document.querySelector('meta[name="login-modal-appear-time"]');
3208    if (!metaTag) {
3209      return;
3210    }
3211
3212    const delayMs = (parseInt(metaTag.getAttribute("content"), 10) || 0) * 1000;
3213    if (delayMs === 0) {
3214      return;
3215    }
3216
3217    const loginButton = document.querySelector("#LoginButton");
3218    if (!loginButton) {
3219      return;
3220    }
3221
3222    this.schedule(delayMs);
3223
3224    const signForm = document.querySelector("#SignForm");
3225    if (signForm) {
3226      signForm.addEventListener("show.bs.modal", this.cancel.bind(this));
3227    }
3228  },
3229
3230  schedule: function (delayMs) {
3231    this.cancel();
3232    const self = this;
3233    this._timerId = window.setTimeout(function () {
3234      self._timerId = null;
3235      const loginButton = document.querySelector("#LoginButton");
3236      if (loginButton) {
3237        loginButton.click();
3238      }
3239    }, delayMs);
3240  },
3241
3242  cancel: function () {
3243    if (this._timerId) {
3244      window.clearTimeout(this._timerId);
3245      this._timerId = null;
3246    }
3247  },
3248};
3249
3250kombi.googleOneTap = {
3251  waitForGoogle: function (callback, attempts) {
3252    let remaining = attempts || 50;
3253
3254    function tryInit() {
3255      if (
3256        window.google &&
3257        google.accounts &&
3258        google.accounts.id
3259      ) {
3260        callback();
3261        return;
3262      }
3263
3264      remaining -= 1;
3265      if (remaining <= 0) {
3266        return;
3267      }
3268
3269      window.setTimeout(tryInit, 100);
3270    }
3271
3272    tryInit();
3273  },
3274
3275  handleCredential: function (response, callbackUrl, nextUrl) {
3276    fetch(callbackUrl, {
3277      method: "POST",
3278      headers: { "Content-Type": "application/json" },
3279      credentials: "include",
3280      body: JSON.stringify({
3281        credential: response.credential,
3282        next_url: nextUrl,
3283      }),
3284    })
3285      .then(function (res) {
3286        if (!res.ok) {
3287          return null;
3288        }
3289        return res.json();
3290      })
3291      .then(function (data) {
3292        if (data && data.redirect_url) {
3293          window.location.href = data.redirect_url;
3294        }
3295      })
3296      .catch(function () {});
3297  },
3298
3299  init: function () {
3300    const configEl = document.getElementById("google-one-tap-config");
3301    if (!configEl) {
3302      return;
3303    }
3304
3305    const clientId = configEl.dataset.clientId;
3306    const callbackUrl = configEl.dataset.callbackUrl;
3307    const nextUrl = configEl.dataset.nextUrl || "/";
3308
3309    if (!clientId || !callbackUrl) {
3310      return;
3311    }
3312
3313    kombi.googleOneTap.waitForGoogle(function () {
3314      google.accounts.id.initialize({
3315        client_id: clientId,
3316        context: "signin",
3317        use_fedcm_for_prompt: true,
3318        callback: function (response) {
3319          kombi.googleOneTap.handleCredential(response, callbackUrl, nextUrl);
3320        },
3321      });
3322
3323      google.accounts.id.prompt();
3324    });
3325  },
3326};
3327
3328document.addEventListener("DOMContentLoaded", function () {
3329  if (kombi.googleOneTap) {
3330    kombi.googleOneTap.init();
3331  }
3332});
3333;
3334kombi.searchBar = () => {
3335  document.querySelectorAll(".location-search").forEach((item) => {
3336      const label = item.querySelector(".location-label");
3337      const value = item.querySelector(".location-value");
3338      const origin_term = item.querySelector(".origin-term");
3339      const input = item.querySelector(".location-input");
3340      const options = item.querySelector(".location-options");
3341      const terms_url = item.getAttribute("data-terms-url");
3342      const search_type = item.getAttribute("data-search-type") || null;
3343      const clear_location = item.querySelector(".clear-location");
3344      const toggle = item.querySelector('[data-bs-toggle="dropdown"]');
3345      const menu = item.querySelector(".dropdown-menu");
3346      const clearLabelReset =
3347        item.getAttribute("data-clear-label") ||
3348        (label && label.getAttribute("title")) ||
3349        "";
3350      const groups = {
3351        "city": "Cidades",
3352        "state": "Estados",
3353        "country": "Países",
3354        "neighborhood": "Bairros",
3355        "hotel": "Hotéis",
3356        "collection": "Coleções",
3357        "destination": "Destinos",
3358        "airport": "Aeroportos",
3359      }
3360      let waiting_set_interval = -1;
3361
3362      if (!input || !options) {
3363        return;
3364      }
3365
3366      const hintMinChars = "Digite 3 letras para começar a ver as opções";
3367
3368      function setLabelText(text) {
3369        if (label) {
3370          label.textContent = text;
3371        }
3372      }
3373
3374      function setHasResults(hasResults) {
3375        menu?.classList.toggle("has-results", hasResults);
3376      }
3377
3378      function showMinCharsHint() {
3379        item.querySelectorAll(".search-text").forEach(function (element) {
3380          element.textContent = hintMinChars;
3381        });
3382        setHasResults(false);
3383        options.innerHTML = "";
3384        if (!item.querySelector(".search-text")) {
3385          const li = document.createElement("li");
3386          li.className = "dropdown-item-text text-black px-3 py-2 small";
3387          li.textContent = hintMinChars;
3388          options.append(li);
3389        }
3390      }
3391
3392      function getOppositeSelectedTerm() {
3393        const form = input.closest("form") || document;
3394        const selectedValue = search_type === "origin"
3395          ? form.querySelector(
3396              '.location-search[data-search-type="destination"] input[name="location"]'
3397            )?.value
3398          : form.querySelector(
3399              '.location-search[data-search-type="origin"] input[name="origin_airport_term"]'
3400            )?.value;
3401        const selectedParts = (selectedValue || "").split("|");
3402
3403        if (!selectedParts[0] || !selectedParts[1]) {
3404          return "";
3405        }
3406
3407        return `${selectedParts[0]}|${selectedParts[1]}`;
3408      }
3409
3410      input.addEventListener("keypress", function(event) {
3411        if (event.key === "Enter") {
3412          event.preventDefault();
3413        }
3414      });
3415
3416      if (toggle && toggle.contains(input) && toggle !== input) {
3417        input.addEventListener("click", (e) => {
3418          e.stopPropagation();
3419          if (!menu?.classList.contains("show")) {
3420            bootstrap.Dropdown.getOrCreateInstance(toggle).show();
3421          }
3422        });
3423
3424        input.addEventListener("focus", () => {
3425          if (!menu?.classList.contains("show")) {
3426            bootstrap.Dropdown.getOrCreateInstance(toggle).show();
3427          }
3428        });
3429      }
3430
3431      item.addEventListener("shown.bs.dropdown", () => {
3432        if (typeof push_to_datalayer === "function") {
3433          const eventName =
3434            search_type === "origin"
3435              ? "search_interact_origin"
3436              : "search_interact_destination";
3437          push_to_datalayer({ event: eventName });
3438        }
3439        input.focus();
3440        input.select();
3441        if (!terms_url) {
3442          return;
3443        }
3444        if (input.value.length < 3) {
3445          showMinCharsHint();
3446        } else {
3447          input.dispatchEvent(new Event("input", { bubbles: true }));
3448        }
3449      });
3450
3451      clear_location?.addEventListener("click", (e) => {
3452        e.preventDefault();
3453        e.stopPropagation();
3454        clearTimeout(waiting_set_interval);
3455        value.value = "";
3456        const originLabelClear = item.querySelector(
3457          'input[name="origin_airport_label"]'
3458        );
3459        if (originLabelClear) {
3460          originLabelClear.value = "";
3461          origin_term.value = ""
3462        }
3463        setLabelText(clearLabelReset);
3464        input.value = "";
3465        if (terms_url) {
3466          showMinCharsHint();
3467        } else {
3468          options.innerHTML = "";
3469          setHasResults(false);
3470        }
3471        kombi.updateCalendar(null);
3472      });
3473
3474      input.addEventListener("input", () => {
3475          if (!terms_url) return;
3476          if (input.value.length >= 3){
3477            clearTimeout(waiting_set_interval);
3478            waiting_set_interval = setTimeout(() => {
3479              const fetchUrl = search_type
3480                ? `${ terms_url }?q=${encodeURIComponent(input.value)}&search_type=${search_type}&selected_search_term=${getOppositeSelectedTerm()}`
3481                : `${ terms_url }?q=${encodeURIComponent(input.value)}`;
3482              fetch(fetchUrl)
3483              .then(function(response) {
3484                  return response.json();
3485              }).then(function(data) {
3486                options.innerHTML = "";
3487
3488                if (Object.keys(data).length === 0) {
3489                  setHasResults(false);
3490                  item.querySelectorAll(".search-text").forEach(function(element) {
3491                    element.textContent = "Não encontramos um resultado para sua busca";
3492                  });
3493                  if (!item.querySelector(".search-text")) {
3494                    const li = document.createElement("li");
3495                    li.className = "dropdown-item-text text-black px-3 py-2 small";
3496                    li.textContent = "Não encontramos um resultado para sua busca";
3497                    options.append(li);
3498                  }
3499                } else {
3500                  setHasResults(true);
3501                  item.querySelectorAll(".search-text").forEach(function(element) {
3502                    element.textContent = "Resultados da busca";
3503                  });
3504
3505                  let isFirstGroup = true;
3506                  for (const [key, val] of Object.entries(data)) {
3507                    let group = document.createElement("li")
3508                    let header = document.createElement("div");
3509                    header.className = isFirstGroup
3510                      ? "dropdown-header bg-secondary-subtle text-black rounded-2"
3511                      : "dropdown-header border-top bg-secondary-subtle text-black";
3512                    header.textContent = groups[key] || key;
3513                    group.append(header);
3514                    isFirstGroup = false;
3515                    options.append(group)
3516
3517                    val.map(function(option) {
3518                      let li = document.createElement("li")
3519                      let anchor = document.createElement("a");
3520                      anchor.href = "#";
3521                      anchor.className = "dropdown-item text-wrap";
3522                      anchor.textContent = option.label;
3523                      li.append(anchor);
3524                      li.addEventListener("click", (e) => {
3525                          e.preventDefault();
3526                          kombi.updateCalendar(null)
3527                          let storedValue = `${ option.type }|${ option.pk }|${ option.label }`;
3528                          if (
3529                            search_type === "origin" &&
3530                            option.type === "airport"
3531                          ) {
3532                            const iataMatch = option.label.match(
3533                              /\(([A-Z]{3})\)\s*$/
3534                            );
3535                            if (iataMatch) {
3536                              storedValue = iataMatch[1];
3537                            }
3538                          }
3539                          value.value = storedValue;
3540
3541                          if (search_type === "origin") {
3542                             const originStoredValue = `${ option.type }|${ option.pk }|${ option.label }`;
3543                             origin_term.value = originStoredValue;
3544                          }
3545
3546                          const originLabelInput = item.querySelector(
3547                            'input[name="origin_airport_label"]'
3548                          );
3549                          if (originLabelInput) {
3550                            originLabelInput.value = option.label;
3551                          }
3552                          setLabelText(option.label);
3553                          input.value = option.label;
3554                          if (option.type === "hotel") {
3555                              kombi.updateCalendar(option.pk);
3556                          }
3557                          const dd = bootstrap.Dropdown.getInstance(toggle);
3558                          if (dd) {
3559                            dd.hide();
3560                          }
3561                      })
3562                      options.append(li)
3563                    })
3564                  }
3565                }
3566              });
3567            }, 300);
3568          } else {
3569            showMinCharsHint();
3570          }
3571      })
3572  })
3573}
3574
3575kombi.showSearch = () => {
3576  const searchBar = document.querySelector(".d-sm-block .nav-search");
3577  if (!searchBar) {
3578    window.location.href = "/#showSearch";
3579    return
3580  }
3581  if (window.matchMedia("(min-width: 992px)").matches) {
3582    kombi.highlightElement();
3583  } else {
3584    const modalElement = document.getElementById("SearchForm");
3585    let modalInstance = bootstrap.Modal.getInstance(modalElement);
3586    if (!modalInstance) {
3587        modalInstance = new bootstrap.Modal(modalElement);
3588    }
3589    modalInstance.show();
3590  }
3591};
3592
3593kombi.checkSearchHash = () => {
3594  const hash = window.location.hash;
3595
3596  if (hash === "#showSearch") {
3597    history.replaceState(null, null, ' ');
3598    kombi.showSearch();
3599  }
3600};
3601;
3602/**
3603 * searchTabs - Alternância entre abas Hotel e Pacote na busca.
3604 * Sincroniza estado entre desktop e mobile.
3605 * Expõe getActiveTab(form) e isPackageActive() para outros módulos.
3606 * Dispara CustomEvent 'searchtab:change' em troca de aba e no init (estado inicial).
3607 */
3608kombi.searchTabs = function () {
3609    const TAB = {
3610        HOTEL: 'hotel',
3611        PACKAGE: 'package'
3612    };
3613
3614    const SELECTORS = {
3615        TAB_BUTTON: '[data-search-tab]',
3616        HOTEL_FIELDS: '.search-hotel-fields',
3617        PACKAGE_FIELDS: '.search-package-fields',
3618        SEARCH_SUBMIT_BTN: '[data-search-submit-btn]'
3619    };
3620
3621    const CLASS = {
3622        HOTEL_ACTIVE: 'btn-primary',
3623        HOTEL_INACTIVE: 'btn-outline-primary bg-white border',
3624        PACKAGE_ACTIVE: 'btn-tab-package-active',
3625        PACKAGE_INACTIVE: 'btn-tab-package bg-white border text-package',
3626        HIDDEN: 'd-none'
3627    };
3628
3629    let activeTab = TAB.HOTEL;
3630
3631    function getAllTabButtons() {
3632        return document.querySelectorAll(SELECTORS.TAB_BUTTON);
3633    }
3634
3635    function getTabValue(btn) {
3636        return btn.getAttribute('data-search-tab') || '';
3637    }
3638
3639    function setActiveTab(value) {
3640        activeTab = value;
3641    }
3642
3643    function updateButtonStyles() {
3644        getAllTabButtons().forEach(function (btn) {
3645            const tabValue = getTabValue(btn);
3646            const isActive = tabValue === activeTab;
3647
3648            btn.classList.remove('btn-primary', 'btn-outline-primary', 'btn-tab-package', 'btn-tab-package-active', 'bg-white', 'border', 'text-primary-emphasis', 'text-package');
3649
3650            if (tabValue === TAB.HOTEL) {
3651                btn.classList.add(...(isActive ? [CLASS.HOTEL_ACTIVE] : CLASS.HOTEL_INACTIVE.split(' ')));
3652            } else if (tabValue === TAB.PACKAGE) {
3653                if (isActive) {
3654                    btn.classList.add(CLASS.PACKAGE_ACTIVE, 'btn-tab-package');
3655                } else {
3656                    btn.classList.add(...CLASS.PACKAGE_INACTIVE.split(' '));
3657                }
3658            }
3659        });
3660    }
3661
3662    function updateFieldsVisibility() {
3663        const hotelFields = document.querySelectorAll(SELECTORS.HOTEL_FIELDS);
3664        const packageFields = document.querySelectorAll(SELECTORS.PACKAGE_FIELDS);
3665
3666        hotelFields.forEach(function (el) {
3667            el.classList.toggle(CLASS.HIDDEN, activeTab !== TAB.HOTEL);
3668        });
3669
3670        packageFields.forEach(function (el) {
3671            el.classList.toggle(CLASS.HIDDEN, activeTab !== TAB.PACKAGE);
3672        });
3673    }
3674
3675    /** Disable inputs in the inactive tab so duplicate names (e.g. location) are not submitted. */
3676    function syncTabInputsDisabled() {
3677        const controlSelector = 'input, select, textarea';
3678        document.querySelectorAll('.SearchForm').forEach(function (form) {
3679            const hotelControls = form.querySelectorAll(
3680                `${SELECTORS.HOTEL_FIELDS} ${controlSelector}`
3681            );
3682            const packageControls = form.querySelectorAll(
3683                `${SELECTORS.PACKAGE_FIELDS} ${controlSelector}`
3684            );
3685
3686            const hotelActive = activeTab === TAB.HOTEL;
3687            hotelControls.forEach(function (el) {
3688                el.disabled = !hotelActive;
3689            });
3690            packageControls.forEach(function (el) {
3691                el.disabled = hotelActive;
3692            });
3693        });
3694    }
3695
3696    function updateSearchButtonStyles() {
3697        document.querySelectorAll(SELECTORS.SEARCH_SUBMIT_BTN).forEach(function (btn) {
3698            btn.classList.toggle('btn-search-package', activeTab === TAB.PACKAGE);
3699        });
3700    }
3701
3702    /**
3703     * Package vs hotel theme: toggles body + form classes for SCSS (hover, flatpickr, counters).
3704     * Flatpickr renders to the body, so the class must be on the document.
3705     */
3706    function updateSearchTabThemeClass() {
3707        const isPackage = activeTab === TAB.PACKAGE;
3708        document.body.classList.toggle('search-tab-package-active', isPackage);
3709        document.querySelectorAll('.SearchForm').forEach(function (form) {
3710            form.classList.toggle('search-form--package', isPackage);
3711        });
3712    }
3713
3714    function dispatchSearchTabChange() {
3715        document.dispatchEvent(
3716            new CustomEvent('searchtab:change', {
3717                detail: {
3718                    activeTab: activeTab,
3719                    isPackage: activeTab === TAB.PACKAGE
3720                }
3721            })
3722        );
3723    }
3724
3725    function switchTo(tabValue) {
3726        if (tabValue !== TAB.HOTEL && tabValue !== TAB.PACKAGE) return;
3727
3728        setActiveTab(tabValue);
3729        updateButtonStyles();
3730        updateFieldsVisibility();
3731        syncTabInputsDisabled();
3732        updateSearchButtonStyles();
3733        updateSearchTabThemeClass();
3734        dispatchSearchTabChange();
3735    }
3736
3737    function setupTabClickHandlers() {
3738        getAllTabButtons().forEach(function (btn) {
3739            btn.addEventListener('click', function () {
3740                const tabValue = getTabValue(btn);
3741                switchTo(tabValue);
3742            });
3743        });
3744    }
3745
3746    /**
3747     * Prefer the Package tab button state inside this form when tabs are rendered
3748     * together with the form (desktop). Fallback to the shared active tab for
3749     * contexts where the tab buttons live outside the form (mobile modal).
3750     */
3751    function getActiveTab(form) {
3752        const packageBtn = form.querySelector('[data-search-tab="package"]');
3753        if (packageBtn) {
3754            return packageBtn.classList.contains(CLASS.PACKAGE_ACTIVE)
3755                ? TAB.PACKAGE
3756                : TAB.HOTEL;
3757        }
3758        return activeTab;
3759    }
3760
3761    function isPackageActive() {
3762        return activeTab === TAB.PACKAGE;
3763    }
3764
3765    kombi.searchTabs.getActiveTab = getActiveTab;
3766    kombi.searchTabs.syncTabInputsDisabled = syncTabInputsDisabled;
3767    kombi.searchTabs.isPackageActive = isPackageActive;
3768
3769    function init() {
3770        activeTab = TAB.HOTEL;
3771        document.querySelectorAll('.search-tabs-container').forEach(function (container) {
3772            if (container.getAttribute('data-initial-tab') === TAB.PACKAGE) {
3773                activeTab = TAB.PACKAGE;
3774            }
3775        });
3776
3777        setupTabClickHandlers();
3778        updateButtonStyles();
3779        updateFieldsVisibility();
3780        syncTabInputsDisabled();
3781        updateSearchButtonStyles();
3782        updateSearchTabThemeClass();
3783        dispatchSearchTabChange();
3784    }
3785
3786    return init();
3787};
3788;
3789kombi.toggleReadMore = {
3790    init: function(context) {
3791        const searchContext = context || document;
3792        const mobileLimit = 70;
3793        const desktopLimit = 294;
3794        const limit = window.innerWidth < 576 ? mobileLimit : desktopLimit;
3795
3796        searchContext.querySelectorAll('.text-preview').forEach(function(textPreview) {
3797            const id = textPreview.getAttribute('data-id');
3798            const fullText = textPreview.getAttribute('data-full-text') || textPreview.textContent.trim();
3799            const toggleLink = document.querySelector('.toggle-read-more[data-id="' + id + '"]');
3800
3801            if (!toggleLink) return;
3802
3803            if (fullText.length > limit && !textPreview.getAttribute('data-full-text')) {
3804                textPreview.setAttribute('data-full-text', fullText);
3805                textPreview.textContent = fullText.substr(0, limit) + '...';
3806                toggleLink.classList.remove('d-none');
3807            } else if (fullText.length <= limit) {
3808                toggleLink.classList.add('d-none');
3809            }
3810        });
3811    },
3812
3813    click: function (toggleLink) {
3814        const mobileLimit = 70;
3815        const desktopLimit = 294;
3816        const limit = window.innerWidth < 576 ? mobileLimit : desktopLimit;
3817        
3818        const id = toggleLink.getAttribute('data-id');
3819        const wrapper = toggleLink.closest('.room-description-wrapper');
3820        if (!wrapper) {
3821            console.error("Erro: container '.room-description-wrapper' não encontrado.");
3822            return;
3823        }
3824
3825        const textPreview = wrapper.querySelector('.preview-' + id);
3826
3827        if (!textPreview) return;
3828
3829        const fullText = textPreview.getAttribute('data-full-text');
3830        const isLimited = textPreview.classList.contains('limited');
3831
3832        if (isLimited) {
3833            textPreview.textContent = fullText;
3834            textPreview.classList.remove('limited');
3835            toggleLink.textContent = 'Ver menos';
3836        } else {
3837            textPreview.textContent = fullText.substr(0, limit) + (fullText.length > limit ? '...' : '');
3838            textPreview.classList.add('limited');
3839            toggleLink.textContent = 'Ver mais';
3840        }
3841    }
3842};
3843kombi.readMore = function (root) {
3844    const scope = root && root.querySelectorAll ? root : document;
3845    const defaultMobileVisibleLimit = 5;
3846    const defaultDesktopVisibleLimit = 7;
3847
3848    function getVisibleLimit(wrapper) {
3849        const mobileLimit = parseInt(wrapper.dataset.limitMobile, 10);
3850        const desktopLimit = parseInt(wrapper.dataset.limitDesktop, 10);
3851        const mobile = Number.isFinite(mobileLimit) ? mobileLimit : defaultMobileVisibleLimit;
3852        const desktop = Number.isFinite(desktopLimit) ? desktopLimit : defaultDesktopVisibleLimit;
3853        return window.innerWidth < 576 ? mobile : desktop;
3854    }
3855
3856    scope.querySelectorAll('.amenities-wrapper').forEach(function(wrapper) {
3857        const visibleLimit = getVisibleLimit(wrapper);
3858        const items = wrapper.querySelectorAll('.amenity-item:not(.see-more-wrapper)');
3859        const btnWrappers = wrapper.querySelectorAll('.see-more-wrapper');
3860
3861        if (items.length <= visibleLimit) {
3862            btnWrappers.forEach(function (w) {
3863                w.remove();
3864            });
3865            return;
3866        }
3867
3868        const btnWrapper = btnWrappers[0];
3869        if (!btnWrapper) return;
3870
3871        const btn = btnWrapper.querySelector('.see-more-btn');
3872        if (!btn) return;
3873
3874        if (btn.getAttribute('data-bs-toggle') === 'modal') {
3875            const mobileWrapper = wrapper.querySelector(
3876                '.see-more-wrapper:not(.see-more-wrapper--desktop)'
3877            );
3878            const desktopWrapper = wrapper.querySelector('.see-more-wrapper--desktop');
3879            const activeWrapper =
3880                window.innerWidth < 576 ? mobileWrapper : desktopWrapper || mobileWrapper;
3881
3882            btnWrappers.forEach(function (w) {
3883                if (w !== activeWrapper) {
3884                    w.classList.add('d-none');
3885                    w.classList.remove('d-flex', 'd-sm-flex', 'd-sm-none');
3886                }
3887            });
3888
3889            if (!activeWrapper) return;
3890
3891            items.forEach((item, index) => {
3892                if (index >= visibleLimit) {
3893                    item.classList.add('d-none');
3894                }
3895            });
3896
3897            const itemAfterLimit = items[visibleLimit];
3898            if (itemAfterLimit) {
3899                itemAfterLimit.before(activeWrapper);
3900            }
3901            activeWrapper.classList.remove('d-none');
3902            activeWrapper.classList.add('d-flex');
3903            if (desktopWrapper && activeWrapper === desktopWrapper) {
3904                activeWrapper.classList.remove('d-sm-none');
3905            }
3906            return;
3907        }
3908
3909        const itemAfterLimit = items[visibleLimit];
3910
3911        if (itemAfterLimit) {
3912            itemAfterLimit.before(btnWrapper);
3913        }
3914        btnWrapper.classList.remove('d-none');
3915        if (btn.dataset.readMoreInitialized) return;
3916
3917        btn.textContent = 'Ver mais';
3918        btn.setAttribute('aria-expanded', 'false');
3919
3920        items.forEach((item, index) => {
3921            if (index >= visibleLimit) {
3922                item.classList.add('d-none');
3923            }
3924        });
3925
3926        btn.addEventListener('click', function(event) {
3927            event.preventDefault();
3928            const isExpanded = btn.getAttribute('aria-expanded') === 'true';
3929            const currentLimit = getVisibleLimit(wrapper);
3930
3931            if (!isExpanded) {
3932                items.forEach(item => item.classList.remove('d-none'));
3933                wrapper.append(btnWrapper);
3934                btn.textContent = 'Ver menos';
3935                btn.setAttribute('aria-expanded', 'true');
3936            } else {
3937                items.forEach((item, index) => {
3938                    if (index >= currentLimit) {
3939                        item.classList.add('d-none');
3940                    }
3941                });
3942                
3943                const itemToPlaceBefore = items[currentLimit];
3944                if (itemToPlaceBefore) {
3945                    itemToPlaceBefore.before(btnWrapper);
3946                }
3947                btn.textContent = 'Ver mais';
3948                btn.setAttribute('aria-expanded', 'false');
3949            }
3950        });
3951
3952        btn.dataset.readMoreInitialized = 'true';
3953    });
3954};
3955;
3956 kombi.handleReservationSelection = function() {
3957    document.querySelectorAll('.select-reservation').forEach(function(button) {
3958        button.addEventListener('click', function(event) {
3959            let nextUrl = button.getAttribute('href');
3960            localStorage.setItem('nextUrl', nextUrl);
3961        });
3962    });
3963};
3964kombi.deltaPricing = {
3965    calculate: function (containerSelector) {
3966        const container = document.querySelector(containerSelector);
3967
3968        if (!container) {
3969            return;
3970        }
3971
3972        const items = container.querySelectorAll("[data-delta-total]");
3973
3974        if (!items.length) {
3975            return;
3976        }
3977
3978        const highlightedItem = container.querySelector("[data-delta-highlighted]");
3979        let baseTotal = null;
3980
3981        if (highlightedItem) {
3982            baseTotal = kombi.deltaPricing.parse(
3983                highlightedItem.getAttribute("data-delta-total")
3984            );
3985        }
3986
3987        if (baseTotal === null) {
3988            const baselineAttr = container.getAttribute("data-delta-selection-baseline");
3989
3990            if (baselineAttr) {
3991                baseTotal = kombi.deltaPricing.parse(baselineAttr);
3992            }
3993        }
3994
3995        if (baseTotal === null) {
3996            baseTotal = kombi.deltaPricing.parse(
3997                items[0].getAttribute("data-delta-total")
3998            );
3999        }
4000
4001        if (baseTotal === null) {
4002            return;
4003        }
4004
4005        items.forEach(function (item) {
4006            const total = kombi.deltaPricing.parse(item.getAttribute("data-delta-total"));
4007
4008            if (total === null) {
4009                return;
4010            }
4011
4012            const delta = total - baseTotal;
4013
4014            item.querySelectorAll(".js-delta-price").forEach(function (element) {
4015                element.textContent = kombi.deltaPricing.formatDelta(delta);
4016            });
4017
4018            item.querySelectorAll(".js-delta-total").forEach(function (element) {
4019                element.textContent = "Total R$ " + kombi.deltaPricing.format(total);
4020            });
4021        });
4022    },
4023
4024    parse: function (value) {
4025        if (value === undefined || value === null) {
4026            return null;
4027        }
4028
4029        const raw = String(value).trim();
4030
4031        if (raw === "" || raw.toLowerCase() === "nan") {
4032            return null;
4033        }
4034
4035        if (/^-?\d+(\.\d+)?$/.test(raw)) {
4036            const direct = parseFloat(raw);
4037
4038            return Number.isNaN(direct) ? null : direct;
4039        }
4040
4041        if (raw.includes(",")) {
4042            const normalized = raw.replace(/\./g, "").replace(",", ".");
4043            const n = parseFloat(normalized);
4044
4045            return Number.isNaN(n) ? null : n;
4046        }
4047
4048        const fallback = parseFloat(raw.replace(/\s/g, ""));
4049
4050        return Number.isNaN(fallback) ? null : fallback;
4051    },
4052
4053    format: function (value) {
4054        return new Intl.NumberFormat("pt-BR", {
4055            minimumFractionDigits: 2,
4056            maximumFractionDigits: 2,
4057        }).format(value);
4058    },
4059
4060    formatDelta: function (value) {
4061        const prefix = value < 0 ? "- R$ " : "+ R$ ";
4062
4063        if (Math.abs(value) === 0) {
4064            return prefix + "0";
4065        }
4066
4067        return prefix + kombi.deltaPricing.format(Math.abs(value));
4068    },
4069};
4070;
4071kombi.flight = {
4072    bindDeltaPricing: function () {
4073        if (kombi.flight._deltaPricingBound) {
4074            return;
4075        }
4076
4077        const recalcFlightDeltaPricing = function (event) {
4078            const targetId = event.detail.target && event.detail.target.id;
4079
4080            if (targetId === 'flight_results_list') {
4081                kombi.flight.calculateDeltaPricing();
4082            }
4083        };
4084
4085        document.body.addEventListener('htmx:afterSwap', recalcFlightDeltaPricing);
4086        document.body.addEventListener('htmx:afterSettle', recalcFlightDeltaPricing);
4087
4088        kombi.flight._deltaPricingBound = true;
4089    },
4090    syncPackageCheckoutVisibility: function (formEl) {
4091        if (!formEl || !formEl.getAttribute || formEl.getAttribute('data-package-checkout-card') !== '1') {
4092            return;
4093        }
4094
4095        const cartDepartureFlightId = formEl.getAttribute('data-package-cart-flight-departure-id');
4096        const cartArrivalFlightId = formEl.getAttribute('data-package-cart-flight-arrival-id');
4097        const checkoutEl = formEl.querySelector('.js-package-direct-checkout');
4098        const resubmitEl = formEl.querySelector('.js-package-resubmit-flight');
4099
4100        if (!cartDepartureFlightId || !cartArrivalFlightId || !checkoutEl || !resubmitEl) {
4101            return;
4102        }
4103
4104        const departureInput = formEl.querySelector('input[name="flightCardDepartureFlight"]:checked');
4105        const arrivalInput = formEl.querySelector('input[name="flightCardArrivalFlight"]:checked');
4106
4107        if (!departureInput || !arrivalInput) {
4108            return;
4109        }
4110
4111        const matches =
4112            String(departureInput.value) === String(cartDepartureFlightId) &&
4113            String(arrivalInput.value) === String(cartArrivalFlightId);
4114
4115        if (matches) {
4116            checkoutEl.classList.remove('d-none');
4117            resubmitEl.classList.add('d-none');
4118        } else {
4119            checkoutEl.classList.add('d-none');
4120            resubmitEl.classList.remove('d-none');
4121        }
4122    },
4123    initPackageCheckoutVisibility: function () {
4124        document.querySelectorAll('form[data-package-checkout-card="1"]').forEach(function (form) {
4125            kombi.flight.syncPackageCheckoutVisibility(form);
4126        });
4127    },
4128    bindPackageCheckoutVisibility: function () {
4129        if (kombi.flight._packageCheckoutBound) {
4130            return;
4131        }
4132
4133        const resyncFlightResultsList = function (event) {
4134            const targetId = event.detail.target && event.detail.target.id;
4135
4136            if (targetId === 'flight_results_list') {
4137                kombi.flight.initPackageCheckoutVisibility();
4138            }
4139        };
4140
4141        document.body.addEventListener('change', function (event) {
4142            const el = event.target;
4143
4144            if (!el.classList || !el.classList.contains('flightCheck')) {
4145                return;
4146            }
4147
4148            const pkgForm = el.closest('form[data-package-checkout-card="1"]');
4149
4150            if (pkgForm) {
4151                kombi.flight.syncPackageCheckoutVisibility(pkgForm);
4152            }
4153        });
4154
4155        document.body.addEventListener('htmx:afterSwap', resyncFlightResultsList);
4156        document.body.addEventListener('htmx:afterSettle', resyncFlightResultsList);
4157
4158        kombi.flight._packageCheckoutBound = true;
4159    },
4160    calculateDeltaPricing: function () {
4161        if (!kombi.deltaPricing) {
4162            return;
4163        }
4164
4165        kombi.deltaPricing.calculate('#flight_results_list');
4166    },
4167    changeDateMobile: function () {
4168        let buttons = document.querySelectorAll('.changeDateButton');
4169        if (window.innerWidth < 768) {
4170            buttons.forEach(function (button) {
4171                button.setAttribute('data-bs-toggle', 'modal');
4172                button.setAttribute('data-bs-target', '#SearchForm');
4173            });
4174        } else {
4175            buttons.forEach(function (button) {
4176                button.removeAttribute('data-bs-toggle');
4177                button.removeAttribute('data-bs-target');
4178            });
4179        }
4180    },
4181    changeDateDesktop: function (room_id, element) {
4182        if (!room_id || !element) {
4183            return;
4184        }
4185        
4186        document.querySelectorAll('.modal').forEach(modal => {
4187            let modalInstance = bootstrap.Modal.getInstance(modal);
4188            if (modalInstance) {
4189                modalInstance.hide();
4190            }
4191        });
4192
4193        setTimeout(() => {
4194            window.scrollTo({ top: 0, behavior: 'smooth' });
4195        }, 200);
4196
4197        setTimeout(() => {
4198            let calendarElement = null;
4199            if (window.innerWidth < 768) {
4200                calendarElement = document.getElementById('search-calendar-mobile');
4201            } else {
4202                calendarElement = document.getElementById('search-calendar-desk');
4203            }
4204
4205            if (calendarElement) {
4206                    if (kombi.flatpickrInstances && kombi.flatpickrInstances.length > 0) {
4207                        for (let i = 0; i < kombi.flatpickrInstances.length; i++) {
4208                            const fp = kombi.flatpickrInstances[i];
4209                            if (fp.input && calendarElement.contains(fp.input)) {
4210                                fp.open();
4211                                return;
4212                            }
4213                        }
4214                    }
4215            }
4216        }, 600);
4217    },
4218    moreFlights: function (event) {
4219        let moreFlightsButton = event.target;
4220        let moreFlightsDivs = moreFlightsButton.closest('.flights-card').querySelectorAll('.more-flights-flightCard');
4221        moreFlightsDivs.forEach(function(div) {
4222            if (div.classList.contains('show')) {
4223                moreFlightsButton.innerHTML = 'Ver mais opções de horários<i class="fa-solid fa-chevron-down ms-1"></i>';
4224                div.classList.remove('show');
4225            } else {
4226                moreFlightsButton.innerHTML = 'Ver menos opções de horários<i class="fa-solid fa-chevron-up ms-1"></i>';
4227                div.classList.add('show');
4228            }
4229        });
4230    },
4231    selectionButton: function (button, multi_offer_form_id) {
4232        var allSelectionButtons = document.querySelectorAll('.spinner-disabled');
4233        allSelectionButtons.forEach(function(btn) {
4234            btn.disabled = true;
4235        });
4236    
4237        var buttonText = button.querySelector('.buttonText');
4238        var buttonSpinner = button.querySelector('.buttonSpinner');
4239        buttonText.classList.add('d-none');
4240        buttonSpinner.classList.remove('d-none');
4241    
4242        var formObj = document.getElementById(multi_offer_form_id);
4243        var formData = new FormData(formObj);
4244        var selectionString = '&selectedFlight=';
4245        selectionString += formData.get('multi_offer_id') + '.';
4246        selectionString += formData.get('flightCardDepartureFlight') + '.';
4247        selectionString += formData.get('flightCardArrivalFlight');
4248    
4249        formObj.action = formObj.action + selectionString;
4250        if (formObj.hasAttribute('hx-post')) {
4251            formObj.setAttribute('hx-post', formObj.action);
4252        }
4253        if (typeof formObj.requestSubmit === 'function' && formObj.hasAttribute('hx-post')) {
4254            formObj.requestSubmit();
4255        } else {
4256            formObj.submit();
4257        }
4258    },
4259    /**
4260     * Starts the rotating copy inside the async flight loader.
4261     * Reusable across flight results page and package hotel poller.
4262     *
4263     * Keeps a single interval; it reads the "live" DOM each tick so HTMX swaps don't break it.
4264     *
4265     * @param {ParentNode} root Element/document to search within
4266     */
4267    startLoaderTextRotation: function (root) {
4268        const isGlobalRotation = !root || root === document;
4269
4270        // If an interval exists but the "mode" changed (document vs scoped),
4271        // restart so we always query the correct live DOM.
4272        if (
4273            kombi.flight._loaderTextIntervalId != null &&
4274            kombi.flight._loaderTextIsGlobalRotation === isGlobalRotation
4275        ) {
4276            return;
4277        }
4278
4279        kombi.flight.stopLoaderTextRotation();
4280        kombi.flight._loaderTextIsGlobalRotation = isGlobalRotation;
4281
4282        let currentTextIndex = 0;
4283
4284        const getContainer = function () {
4285            // Prefer the live DOM node: HTMX can recreate the loader with the same id.
4286            const live = document.getElementById("loader-container");
4287            if (live) return live;
4288
4289            if (root && root.querySelector) {
4290                return root.querySelector("#loader-container");
4291            }
4292
4293            return null;
4294        };
4295
4296        const updateLoaderTextRotation = function () {
4297            const container = getContainer();
4298            if (!container) {
4299                kombi.flight.stopLoaderTextRotation();
4300                return;
4301            }
4302
4303            const texts = container.querySelectorAll(".loader-text");
4304            if (!texts || texts.length < 2) {
4305                // With 0/1 texts, nothing to rotate.
4306                return;
4307            }
4308
4309            const current = currentTextIndex % texts.length;
4310            texts.forEach((textEl) => textEl.classList.add("d-none"));
4311            texts[current].classList.remove("d-none");
4312            currentTextIndex = current + 1;
4313        };
4314
4315        const initialContainer = getContainer();
4316        const rotationIntervalMs =
4317            parseFloat(
4318                initialContainer?.getAttribute("data-flight-loader-text-interval") || "5",
4319                10,
4320            ) * 1000;
4321
4322        updateLoaderTextRotation();
4323        kombi.flight._loaderTextIntervalId = window.setInterval(
4324            updateLoaderTextRotation,
4325            Math.max(800, rotationIntervalMs),
4326        );
4327    },
4328    // Backwards compatible alias (used in templates / other scripts)
4329    initLoaderTextRotation: function (root) {
4330        kombi.flight.startLoaderTextRotation(root);
4331    },
4332    stopLoaderTextRotation: function () {
4333        if (kombi.flight._loaderTextIntervalId != null) {
4334            window.clearInterval(kombi.flight._loaderTextIntervalId);
4335            kombi.flight._loaderTextIntervalId = null;
4336        }
4337        kombi.flight._loaderTextIsGlobalRotation = null;
4338    },
4339};
4340function toggleAccordionButtonStyle(button, active) {
4341    if (!button) return;
4342
4343    const classes = ['border-bottom-0', 'shadow-none', 'rounded-bottom-0'];
4344    classes.forEach(cls => button.classList.toggle(cls, active));
4345}
4346
4347kombi.accordion = function() {
4348    const accordionsCollapse = document.querySelectorAll('.accordion-item.custom-plain .accordion-collapse');
4349
4350    accordionsCollapse.forEach(accordion => {
4351        accordion.addEventListener('show.bs.collapse', () => {
4352            const button = accordion.closest('.accordion-item')?.querySelector('.accordion-button');
4353            toggleAccordionButtonStyle(button, true);
4354
4355            /*TODO: ajustar quando tiver o aereo.
4356            accordionsCollapse.forEach(otherAccordion => {
4357                if (otherAccordion !== accordion) {
4358                    const otherInstance = bootstrap.Collapse.getInstance(otherAccordion);
4359                    if (otherInstance) {
4360                        otherInstance.hide();
4361                        const otherButton = otherAccordion.closest('.accordion-item')?.querySelector('.accordion-button');
4362                        toggleAccordionButtonStyle(otherButton, false);
4363                    }
4364                }
4365            });*/
4366        });
4367
4368        accordion.addEventListener('hide.bs.collapse', () => {
4369            const button = accordion.closest('.accordion-item')?.querySelector('.accordion-button');
4370            toggleAccordionButtonStyle(button, false);
4371        });
4372    });
4373};
4374
4375kombi.accordionScroll = function(type, roomLoopCounter) {
4376    const accordion = document.querySelector("#accordionResulOccupation")
4377    const accordionItem = accordion.querySelector("#accordionItem" + roomLoopCounter);
4378    if (type == "accordionButton") {
4379        accordion.scrollIntoView({
4380            behavior: 'smooth',
4381            block: 'start'
4382        });
4383    } else {
4384        accordionItem.scrollIntoView({
4385            behavior: 'smooth',
4386            block: 'start'
4387        });
4388    }
4389};
4390kombi.spinner = function(button) {
4391    if (!button) {
4392        return; 
4393    }
4394
4395    const currentHeight = button.offsetHeight;
4396    button.style.minHeight = currentHeight + 'px'; 
4397
4398    button.classList.add('disabled');
4399    var buttonText = button.querySelector('.buttonText');
4400    if (buttonText) {
4401        buttonText.classList.add('d-none');
4402    }
4403    var spinner = button.querySelector('.buttonSpinner');
4404    if (spinner) {
4405        spinner.classList.remove('d-none');
4406    }
4407}
4408
4409kombi.resetButton = function(button) {
4410    if (!button) return;
4411
4412    button.classList.remove('disabled');
4413
4414    const buttonText = button.querySelector('.buttonText');
4415    if (buttonText) {
4416        buttonText.classList.remove('d-none');
4417    }
4418
4419    const spinner = button.querySelector('.buttonSpinner');
4420    if (spinner) {
4421        spinner.classList.add('d-none');
4422    }
4423
4424    button.style.minHeight = ''; 
4425};
4426kombi.search = function () {
4427    const MAX = {
4428        ROOMS: 5,
4429        ADULTS: 10,
4430        CHILDREN: 5,
4431        PACKAGE_GUESTS: 9
4432    };
4433    const MIN = {
4434        ROOMS: 1,
4435        ADULTS: 1,
4436        CHILDREN: 0
4437    };
4438
4439    const SELECTORS = {
4440        GUEST_FORM_CONTAINER: '.room-container',
4441        ROOM_SELECTION_CARD: '.room',
4442        SEARCH_WIDGET_WRAPPER: '.guest-selector-wrapper, .guest-selector-dropdown, .guest-selector-dropdown-desktop, .guests',
4443        TRAVELER_SUMMARY: '#search-travelers-desk, .block-guests.guests',
4444        ADULT_INPUT: 'input[name="adults"]',
4445        CHILD_INPUT: 'input[name="children"]',
4446        AGE_OF_CHILDREN: 'input[name="age_of_children"]',
4447        CHILD_TOOLTIP: '.children-tooltip[data-bs-toggle="tooltip"]',
4448        ROOM_COUNT_INPUT: 'input.number_of_rooms',
4449        GUESTS_COUNT_INPUT: 'input.number_of_guests',
4450        AGE_SELECT_CONTAINER: '.age-control',
4451        AGE_TEMPLATE_SELECTOR: '.html-age-control',
4452        AGE_SELECT: '.age_text',
4453        TOAST_ROOM_LIMIT: '#room-limit-toast',
4454        BTN_ADD_ROOM: '.btn-add-room',
4455        BTN_REMOVE_LAST_ROOM: '.btn-minus-room',
4456        BTN_REMOVE_ROOM: '.btn-remove-room',
4457        BTN_INCREMENT: '.btn-plus',
4458        BTN_DECREMENT: '.btn-minus',
4459        QUANTITY_SELECTOR_GROUP: '.counter-group',
4460        ROOM_LABEL: '.label-title',
4461        TRAVELER_TOTAL_INPUT: 'input[name="number_of_guests"]',
4462        ROOM_COUNTER_ELEMENTS: '.room-display-count, input.number_of_room',
4463        SEARCH_SUMMARY_INPUT: 'input.resume',
4464        SEARCH_RESULTS_LABEL: '#search-travelers-desk .resume:not(input)',
4465        COLLAPSE_GUEST_SELECTOR: '#collapseGuests',
4466        DIVIDER: 'hr',
4467        PACKAGE_GUEST_LIMIT_MSG: '#package-guest-limit-message'
4468    };
4469
4470    const CLASS = {
4471        HIDDEN: 'd-none'
4472    };
4473
4474    const LIMITS = {
4475        adults: {
4476            min: MIN.ADULTS,
4477            max: MAX.ADULTS
4478        },
4479        children: {
4480            min: MIN.CHILDREN,
4481            max: MAX.CHILDREN
4482        },
4483        number_of_rooms: {
4484            min: MIN.ROOMS,
4485            max: MAX.ROOMS
4486        }
4487    };
4488
4489    let cachedChildAgeSelectHTML = '';
4490
4491    function getChildAgeSelectTemplate() {
4492        if (cachedChildAgeSelectHTML) return cachedChildAgeSelectHTML;
4493
4494        const template = document.querySelector(SELECTORS.AGE_TEMPLATE_SELECTOR);
4495        if (template?.innerHTML.trim()) {
4496            return (cachedChildAgeSelectHTML = template.innerHTML.trim());
4497        }
4498
4499        const select = document.querySelector(`${SELECTORS.ROOM_SELECTION_CARD} ${SELECTORS.AGE_SELECT_CONTAINER} select`);
4500        return (cachedChildAgeSelectHTML = select?.outerHTML || '');
4501    }
4502
4503    function getChildCount(roomCard) {
4504        const childInput = roomCard.querySelector(SELECTORS.CHILD_INPUT);
4505        return childInput ? parseInt(childInput.value || 0) : 0;
4506    }
4507    
4508    function getAgeContainer(roomCard) {
4509        return roomCard.querySelector(SELECTORS.AGE_SELECT_CONTAINER);
4510    }
4511    
4512    function getAges(roomCard) {
4513        const agesString = roomCard.getAttribute('data-children-ages');
4514        if (!agesString) return [];
4515        try {
4516            return JSON.parse(agesString);
4517        } catch {
4518            return [];
4519        }
4520    }
4521    
4522    function createAgeSelect(age) {
4523        const wrapper = document.createElement('div');
4524        wrapper.innerHTML = getChildAgeSelectTemplate();
4525    
4526        const select = wrapper.querySelector('select');
4527        
4528        if (!select) return null;
4529    
4530        Array.from(select.options).forEach(opt => {
4531            opt.defaultSelected = (parseInt(opt.value) === (age !== undefined ? parseInt(age) : 6));
4532        });
4533    
4534        return select;
4535    }
4536    
4537    function removeExtraAgeSelectors(ageContainer, countToKeep) {
4538        Array.from(ageContainer.children).slice(countToKeep).forEach(el => el.remove());
4539        if (countToKeep < 1) {
4540            ageContainer.closest(SELECTORS.AGE_SELECT).classList.add('d-none')
4541            ageContainer.closest(SELECTORS.ROOM_SELECTION_CARD).setAttribute('data-children-ages', '[]')
4542        } 
4543    }
4544    
4545    function syncChildAgeSelectors(roomCard) {
4546        const childCount = getChildCount(roomCard);
4547        const ageContainer = getAgeContainer(roomCard);
4548        if (!ageContainer) return;
4549        if (childCount > 0) {
4550            ageContainer.closest(SELECTORS.AGE_SELECT).classList.remove('d-none')
4551        }
4552        const current = ageContainer.children.length;
4553        const ages = getAges(roomCard);
4554    
4555        if (childCount < current) {
4556            removeExtraAgeSelectors(ageContainer, childCount);
4557        } else {
4558            const fragment = document.createDocumentFragment();
4559            for (let i = current; i < childCount; i++) {
4560                const select = createAgeSelect(ages[i]);
4561                if (select) fragment.appendChild(select);
4562            }
4563            ageContainer.appendChild(fragment);
4564        }
4565    } 
4566
4567    function formatTravelerSummary(roomCount, guestCount) {
4568        const roomText = Number(roomCount) === 1 ? 'quarto' : 'quartos';
4569        const guestText = Number(guestCount) === 1 ? 'hóspede' : 'hóspedes';
4570        return `${String(roomCount).padStart(2, '0')} ${roomText} para ${String(guestCount).padStart(2, '0')} ${guestText}`;
4571    }
4572
4573    function applyTravelerSummary(roomCount, guestCount, searchWidgetWrapper) {
4574        const summaryText = formatTravelerSummary(roomCount, guestCount);
4575    
4576        searchWidgetWrapper.querySelectorAll(SELECTORS.TRAVELER_TOTAL_INPUT).forEach((totalGuestsInput) 
4576=> {
4577            totalGuestsInput.value = guestCount;
4578        });
4579    
4580        searchWidgetWrapper.querySelectorAll(SELECTORS.ROOM_COUNTER_ELEMENTS).forEach((roomCountElement) => {
4581            const val = String(roomCount).padStart(2, '0');
4582            roomCountElement.tagName === 'INPUT' ? (roomCountElement.value = val) : (roomCountElement.textContent = val);
4583        });
4584    
4585        const hiddenRoomInput = searchWidgetWrapper.querySelector(SELECTORS.ROOM_COUNT_INPUT);
4586        if (hiddenRoomInput) hiddenRoomInput.value = String(roomCount).padStart(2, '0');
4587    
4588        const guestsBlock = searchWidgetWrapper.closest('.guests') || searchWidgetWrapper;
4589        guestsBlock.querySelectorAll(SELECTORS.SEARCH_SUMMARY_INPUT).forEach((summaryInput) => {
4590            summaryInput.value = summaryText;
4591        });
4592    
4593        guestsBlock.querySelectorAll('.resume:not(input)').forEach((resumeLabelElement) => {
4594            resumeLabelElement.textContent = summaryText;
4595        });
4596    }
4597
4598    function updateTravelerSummary(guestFormContainer, searchWidgetWrapper) {
4599        const rooms = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD);
4600        let totalAdults = 0,
4601            totalChildren = 0;
4602    
4603        rooms.forEach(room => {
4604            const adults = parseInt(room.querySelector(SELECTORS.ADULT_INPUT)?.value || 0, 10);
4605            const children = parseInt(room.querySelector(SELECTORS.CHILD_INPUT)?.value || 0, 10);
4606    
4607            totalAdults += adults;
4608            totalChildren += children;
4609        });
4610    
4611        const totalGuests = totalAdults + totalChildren;
4612        const totalRooms = rooms.length;
4613    
4614        applyTravelerSummary(totalRooms, totalGuests, searchWidgetWrapper);
4615    }
4616
4617    function getTotalGuestsInForm(guestFormContainer) {
4618        const rooms = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD);
4619        let total = 0;
4620        rooms.forEach((room) => {
4621            total += parseInt(room.querySelector(SELECTORS.ADULT_INPUT)?.value || 0, 10);
4622            total += parseInt(room.querySelector(SELECTORS.CHILD_INPUT)?.value || 0, 10);
4623        });
4624        return total;
4625    }
4626
4627    function serializeGuestOccupation(guestFormContainer) {
4628        const serializedGuestRooms = [];
4629        guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach((room) => {
4630            serializedGuestRooms.push({
4631                adults: parseInt(room.querySelector(SELECTORS.ADULT_INPUT)?.value || 0, 10),
4632                children: parseInt(room.querySelector(SELECTORS.CHILD_INPUT)?.value || 0, 10),
4633                ages: getAges(room) || []
4634            });
4635        });
4636        return serializedGuestRooms;
4637    }
4638
4639    function rebindGuestChildTooltips(guestFormContainer) {
4640        guestFormContainer.querySelectorAll(SELECTORS.CHILD_TOOLTIP).forEach((tooltipElement) => {
4641            const existingTooltip = bootstrap.Tooltip.getInstance(tooltipElement);
4642            if (existingTooltip) existingTooltip.dispose();
4643            if (typeof bootstrap !== "undefined" && bootstrap.Tooltip) {
4644                new bootstrap.Tooltip(tooltipElement);
4645            }
4646        });
4647    }
4648
4649    function syncGuestFormInnerHTMLToPeer(sourceGuestForm, targetGuestForm, targetSearchWidgetWrapper) {
4650        if (sourceGuestForm === targetGuestForm) return;
4651        targetGuestForm.innerHTML = sourceGuestForm.innerHTML;
4652        rebindGuestChildTooltips(targetGuestForm);
4653        updateRoomSearchState(targetGuestForm, targetSearchWidgetWrapper);
4654        const sourceSearchWidgetWrapper = sourceGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
4655        if (sourceSearchWidgetWrapper) {
4656            applyPackageGuestCap(sourceGuestForm, sourceSearchWidgetWrapper);
4657        }
4658    }
4659
4660    function syncPeerGuestFormFrom(sourceGuestForm) {
4661        const allGuestFormContainers = document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER);
4662        if (allGuestFormContainers.length < 2) return;
4663        const peerGuestForm = sourceGuestForm === allGuestFormContainers[0]
4664            ? allGuestFormContainers[1]
4665            : allGuestFormContainers[0];
4666        const peerSearchWidgetWrapper = peerGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
4667        if (peerSearchWidgetWrapper) {
4668            syncGuestFormInnerHTMLToPeer(sourceGuestForm, peerGuestForm, peerSearchWidgetWrapper);
4669        }
4670    }
4671
4672    function markPackageGuestsDirtyIfNeeded() {
4673        if (
4674            kombi.searchTabs &&
4675            typeof kombi.searchTabs.isPackageActive === "function" &&
4676            kombi.searchTabs.isPackageActive()
4677        ) {
4678            kombi._packageGuestsDirty = true;
4679        }
4680    }
4681
4682    function resetGuestFormToPackageDefault(guestFormContainer, searchWidgetWrapper) {
4683        const roomCardElements = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD);
4684        for (let i = roomCardElements.length - 1; i > 0; i--) {
4685            roomCardElements[i].remove();
4686        }
4687        const firstRoomCard = guestFormContainer.querySelector(SELECTORS.ROOM_SELECTION_CARD);
4688        if (firstRoomCard) {
4689            firstRoomCard.setAttribute("data-children-ages", "[]");
4690            firstRoomCard.querySelector(SELECTORS.ADULT_INPUT).value = "02";
4691            firstRoomCard.querySelector(SELECTORS.CHILD_INPUT).value = "00";
4692            const ageSelectContainer = firstRoomCard.querySelector(SELECTORS.AGE_SELECT_CONTAINER);
4693            if (ageSelectContainer) ageSelectContainer.innerHTML = "";
4694            firstRoomCard.querySelector(SELECTORS.AGE_SELECT).classList.add(CLASS.HIDDEN);
4695            syncChildAgeSelectors(firstRoomCard);
4696        }
4697        rebindGuestChildTooltips(guestFormContainer);
4698        updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4699    }
4700
4701    function applySerializedGuestOccupation(guestFormContainer, searchWidgetWrapper, occupation) {
4702        if (!Array.isArray(occupation) || !occupation.length) return;
4703        const existingRoomCards = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD);
4704        for (let i = existingRoomCards.length - 1; i >= 0; i--) {
4705            if (i >= occupation.length) {
4706                existingRoomCards[i].remove();
4707            }
4708        }
4709        for (let i = 0; i < occupation.length; i++) {
4710            let roomCard = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD)[i];
4711            if (!roomCard) {
4712                const lastRoomCard = guestFormContainer.querySelector(
4713                    `${SELECTORS.ROOM_SELECTION_CARD}:last-child`
4714                );
4715                if (!lastRoomCard) return;
4716                const clonedRoomCard = lastRoomCard.cloneNode(true);
4717                const tooltipInClone = clonedRoomCard.querySelector(SELECTORS.CHILD_TOOLTIP);
4718                if (tooltipInClone) {
4719                    const oldTooltip = bootstrap.Tooltip.getInstance(tooltipInClone);
4720                    if (oldTooltip) oldTooltip.dispose();
4721                    new bootstrap.Tooltip(tooltipInClone);
4722                }
4723                guestFormContainer.appendChild(clonedRoomCard);
4724                initializeRoomCardFields(clonedRoomCard, false);
4725                roomCard = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD)[i];
4726            }
4727            const roomOccupation = occupation[i];
4728            const childAges = roomOccupation.ages || [];
4729            roomCard.setAttribute("data-children-ages", JSON.stringify(childAges));
4730            roomCard.querySelector(SELECTORS.ADULT_INPUT).value = 
4730String(roomOccupation.adults).padStart(2, "0");
4731            roomCard.querySelector(SELECTORS.CHILD_INPUT).value = String(roomOccupation.children).padStart(2, "0");
4732            syncChildAgeSelectors(roomCard);
4733        }
4734        rebindGuestChildTooltips(guestFormContainer);
4735        updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4736    }
4737
4738    /**
4739     * Package tab: cap total guests at MAX.PACKAGE_GUESTS; toggle message and disable + / add-room when capped.
4740     */
4741    function applyPackageGuestCap(guestFormContainer, searchWidgetWrapper) {
4742        const msg = searchWidgetWrapper.querySelector(SELECTORS.PACKAGE_GUEST_LIMIT_MSG);
4743        const isPackage =
4744            kombi.searchTabs &&
4745            typeof kombi.searchTabs.isPackageActive === "function" &&
4746            kombi.searchTabs.isPackageActive();
4747
4748        if (!isPackage) {
4749            if (msg) msg.classList.add(CLASS.HIDDEN);
4750            guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach((room) => {
4751                room.querySelectorAll(SELECTORS.QUANTITY_SELECTOR_GROUP).forEach((group) => {
4752                    refreshQuantitySelectorGroup(group, searchWidgetWrapper);
4753                });
4754            });
4755            const btnPlusRoom = searchWidgetWrapper.querySelector(SELECTORS.BTN_ADD_ROOM);
4756            const roomCount = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4757            if (btnPlusRoom) btnPlusRoom.disabled = roomCount >= MAX.ROOMS;
4758            return;
4759        }
4760
4761        const totalGuests = getTotalGuestsInForm(guestFormContainer);
4762        const capped = totalGuests >= MAX.PACKAGE_GUESTS;
4763
4764        if (msg) msg.classList.toggle(CLASS.HIDDEN, !capped);
4765
4766        const btnPlusRoom = searchWidgetWrapper.querySelector(SELECTORS.BTN_ADD_ROOM);
4767        const roomCount = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4768        const blockAddRoom =
4769            totalGuests + MIN.ADULTS > MAX.PACKAGE_GUESTS;
4770        if (btnPlusRoom) {
4771            btnPlusRoom.disabled = capped || roomCount >= MAX.ROOMS || blockAddRoom;
4772        }
4773
4774        guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach((room) => {
4775            room.querySelectorAll(SELECTORS.QUANTITY_SELECTOR_GROUP).forEach((group) => {
4776                const input = group.querySelector("input");
4777                if (!input || (input.name !== "adults" && input.name !== "children")) {
4778                    return;
4779                }
4780                refreshQuantitySelectorGroup(group, searchWidgetWrapper);
4781                if (capped) {
4782                    const inc = group.querySelector(SELECTORS.BTN_INCREMENT);
4783                    if (inc) inc.disabled = true;
4784                }
4785            });
4786        });
4787    }
4788
4789    function initializeRoomCardFields(roomCard, clearFields=false) {
4790        const adults = clearFields ? 2 : MIN.ADULTS;
4791        roomCard.querySelector(SELECTORS.ADULT_INPUT).value = String(adults).padStart(2, '0');
4792        roomCard.querySelector(SELECTORS.CHILD_INPUT).value = String(MIN.CHILDREN).padStart(2, '0');
4793        roomCard.querySelector(SELECTORS.AGE_SELECT_CONTAINER).innerHTML = '';
4794        if (MIN.CHILDREN < 1) {
4795            roomCard.querySelector(SELECTORS.AGE_SELECT).classList.add('d-none')
4796        }
4797    }
4798
4799    function updateQuantityButtonState(group, fieldName, value) {
4800        const limits = LIMITS[fieldName];
4801        if (!limits) return;
4802
4803        group.querySelector(SELECTORS.BTN_DECREMENT).disabled = value <= limits.min;
4804        group.querySelector(SELECTORS.BTN_INCREMENT).disabled = value >= limits.max;
4805    }
4806
4807    function refreshQuantitySelectorGroup(group, searchWidgetWrapper) {
4808        const input = group.querySelector('input');
4809        if (!input) return;
4810
4811        let value = parseInt(input.value || 0);
4812        const fieldName = input.name;
4813
4814        if (fieldName === 'number_of_rooms' && searchWidgetWrapper) {
4815            value = parseInt(searchWidgetWrapper.querySelector(SELECTORS.ROOM_COUNT_INPUT)?.value || 0);
4816        }
4817
4818        updateQuantityButtonState(group, fieldName, value);
4819    }
4820
4821    function updateRoomSearchState(guestFormContainer, searchWidgetWrapper) {
4822        guestFormContainer.querySelectorAll(SELECTORS.DIVIDER).forEac
4822h(hr => hr.remove());
4823
4824        const roomCards = Array.from(guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD));
4825        roomCards.forEach((roomCard, index) => {
4826            const roomNumber = index + 1;
4827            roomCard.dataset.room = roomNumber;
4828            roomCard.querySelector(SELECTORS.ROOM_LABEL).textContent = `Quarto ${String(roomNumber).padStart(2, '0')}`;
4829            roomCard.querySelector(SELECTORS.BTN_REMOVE_ROOM).classList.toggle(CLASS.HIDDEN, index === 0);
4830
4831            if (index > 0) {
4832                const divider = document.createElement('hr');
4833                divider.className = 'my-3';
4834                guestFormContainer.insertBefore(divider, roomCard);
4835            }
4836
4837            syncChildAgeSelectors(roomCard);
4838        });
4839
4840        const btnMinusRoom = searchWidgetWrapper.querySelector(SELECTORS.BTN_REMOVE_LAST_ROOM);
4841        if (btnMinusRoom) {
4842            const roomCount = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4843            btnMinusRoom.disabled = roomCount <= MIN.ROOMS;
4844        }
4845
4846        const btnPlusRoom = searchWidgetWrapper.querySelector(SELECTORS.BTN_ADD_ROOM);
4847        if (btnPlusRoom) {
4848            const roomCount = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4849            btnPlusRoom.disabled = roomCount >= MAX.ROOMS;
4850        }
4851
4852        const roomLimitMsg = searchWidgetWrapper.querySelector('#room-limit-message');
4853        if (roomLimitMsg) {
4854            const roomCount = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4855            roomLimitMsg.classList.toggle('d-none', roomCount < MAX.ROOMS);
4856        }
4857
4858        updateTravelerSummary(guestFormContainer, searchWidgetWrapper);
4859        refreshQuantitySelectorGroup(searchWidgetWrapper.querySelector(SELECTORS.QUANTITY_SELECTOR_GROUP), searchWidgetWrapper);
4860        roomCards.forEach(room => room.querySelectorAll(SELECTORS.QUANTITY_SELECTOR_GROUP).forEach(group => refreshQuantitySelectorGroup(group)));
4861        applyPackageGuestCap(guestFormContainer, searchWidgetWrapper);
4862    }
4863
4864    function setupRoomSelectionForm() {
4865        document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER).forEach(guestFormContainer => {
4866          const searchWidgetWrapper = guestFormContainer.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
4867          if (!searchWidgetWrapper) return;
4868      
4869          setupAddRoomButton(guestFormContainer, searchWidgetWrapper);
4870          setupRemoveRoomButton(guestFormContainer, searchWidgetWrapper);
4871          setupRoomCardInteractions(guestFormContainer, searchWidgetWrapper);
4872      
4873          updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4874        });
4875    }
4876      
4877    function setupAddRoomButton(guestFormContainer, searchWidgetWrapper) {
4878        const btn = searchWidgetWrapper.querySelector(SELECTORS.BTN_ADD_ROOM);
4879        if (!btn) return;
4880      
4881        btn.addEventListener('click', () => {
4882          const currentRooms = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).length;
4883          if (currentRooms >= MAX.ROOMS) return showRoomLimitWarningToast(searchWidgetWrapper);
4884          if (
4885              kombi.searchTabs &&
4886              typeof kombi.searchTabs.isPackageActive === "function" &&
4887              kombi.searchTabs.isPackageActive() &&
4888              getTotalGuestsInForm(guestFormContainer) + MIN.ADULTS > MAX.PACKAGE_GUESTS
4889          ) {
4890              return;
4891          }
4892
4893          const lastRoomCard = guestFormContainer.querySelector(`${SELECTORS.ROOM_SELECTION_CARD}:last-child`);
4894          if (!lastRoomCard) return;
4895      
4896          const clonedRoomCard = lastRoomCard.cloneNode(true);
4897          
4898          if (clonedRoomCard) {
4899            clonedRoomCard.setAttribute('data-children-ages', '[]')
4900            const tooltipEl = clonedRoomCard.querySelector(SELECTORS.CHILD_TOOLTIP);
4901            if (tooltipEl) {
4902                const existingTooltip = bootstrap.Tooltip.getInstance(tooltipEl);
4903                if (existingTooltip) existingTooltip.dispose();
4904                new bootstrap.Tooltip(tooltipEl);
4905            }
4906
4907          }
4908          
4909          initializeRoomCardFields(clonedRoomCard);
4910          guestFormContainer.appendChild(clonedRoomCard);
4911          updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4912          kombi._lastGuestFormSource = guestFormContainer;
4913          markPackageGuestsDirtyIfNeeded();
4914          syncPeerGuestFormFrom(guestFormContainer);
4915        });
4916    }
4917      
4918    function setupRemoveRoomButton(guestFormContainer, searchWidgetWrapper) {
4919        const btn = searchWidgetWrapper.querySelector(SELECTORS.BTN_REMOVE_LAST_ROOM);
4920        if (!btn) return;
4921      
4922        btn.addEventListener('click', () => {
4923          const rooms = guestFormContainer.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD);
4924          if (rooms.length > MIN.ROOMS) {
4925            rooms[rooms.length - 1].remove();
4926            updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4927            kombi._lastGuestFormSource = guestFormContainer;
4928            markPackageGuestsDirtyIfNeeded();
4929            syncPeerGuestFormFrom(guestFormContainer);
4930          }
4931        });
4932    }
4933      
4934    function setupRoomCardInteractions(guestFormContainer, searchWidgetWrapper) {
4935        guestFormContainer.addEventListener('click', e => {
4936          const target = e.target;
4937          const roomCard = target.closest(SELECTORS.ROOM_SELECTION_CARD);
4938          const group = target.closest(SELECTORS.QUANTITY_SELECTOR_GROUP);
4939      
4940          if (!roomCard) return;
4941    
4942          if (target.closest(SELECTORS.BTN_REMOVE_ROOM)) {
4943            roomCard.remove();
4944            updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
4945            kombi._lastGuestFormSource = guestFormContainer;
4946            markPackageGuestsDirtyIfNeeded();
4947            syncPeerGuestFormFrom(guestFormContainer);
4948            return;
4949          }
4950      
4951          if (!group) return;
4952          const input = group.querySelector('input');
4953          if (!input) return;
4954      
4955          let val = parseInt(input.value || 0);
4956          const limits = LIMITS[input.name];
4957          if (!limits) return;
4958      
4959          if (target.closest(SELECTORS.BTN_INCREMENT) && val < limits.max) {
4960              if (
4961                  kombi.searchTabs &&
4962                  typeof kombi.searchTabs.isPackageActive === "function" &&
4963                  kombi.searchTabs.isPackageActive() &&
4964                  getTotalGuestsInForm(guestFormContainer) >= MAX.PACKAGE_GUESTS
4965              ) {
4966                  return;
4967              }
4968              val++;
4969          }
4970          if (target.closest(SELECTORS.BTN_DECREMENT) && val > limits.min) val--;
4971      
4972          input.value = String(val).padStart(2, '0');
4973      
4974          if (input.name === 'children') {
4975            syncChildAgeSelectors(roomCard);
4976          }
4977      
4978          updateTravelerSummary(guestFormContainer, searchWidgetWrapper);
4979          refreshQuantitySelectorGroup(group);
4980          applyPackageGuestCap(guestFormContainer, searchWidgetWrapper);
4981          kombi._lastGuestFormSource = guestFormContainer;
4982          markPackageGuestsDirtyIfNeeded();
4983          syncPeerGuestFormFrom(guestFormContainer);
4984        });
4985    }
4986
4987    function setupGuestSelectorCollapseListener() {
4988        const collapseElement = document.querySelector(SELECTORS.COLLAPSE_GUEST_SELECTOR);
4989        if (collapseElement) {
4990            collapseElement.addEventListener('shown.bs.collapse', () => {
4991                document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER).forEach(container => {
4992                    const wrapper = container.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
4993                    if (wrapper) updateRoomSearchState(container, wrapper);
4994                });
4995            });
4996        }
4997    }
4998
4999    function initSearchWidget() {
5000        const roomInput = document.querySelector(SELECTORS.ROOM_COUNT_INPUT)
5001        const guestInput = document.querySelector(SELECTORS.GUESTS_COUNT_INPUT)
5002        const roomCount = roomInput ? roomInput.value : 0
5003        const guestCount = guestInput ? guestInput.value : 0
5004        const travelersSummary = document.querySelectorAll(SELECTORS.TRAVELER_SUMMARY)
5005        getChildAgeSelectTemplate();
5006        setupRoomSelectionForm();
5007        setupGuestSelectorCollapseListener();
5008        const firstGuestFormContainer = document.querySelector(SELECTORS.GUEST_FORM_CONTAINER);
5009        if (firstGuestFormContainer) {
5010            kombi._lastGuestFormSource = firstGuestFormContainer;
5011        }
5012        if (kombi._packageGuestsDirty == null) {
5013            kombi._packageGuestsDirty = false;
5014        }
5015        if (kombi._hotelGuestSnapshot == null) {
5016            kombi._hotelGuestSnapshot = null;
5017        }
5018        if (kombi.searchTabs && typeof kombi.searchTabs.isPackageActive === "function") {
5019            kombi._lastTabWasPackage = kombi.searchTabs.isPackageActive();
5020        } else {
5021            kombi._lastTabWasPackage = false;
5022        }
5023        const guestFormContainersOnInit = document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER);
5024        if (guestFormContainersOnInit.length >= 2) {
5025            const desktopGuestForm = guestFormContainersOnInit[0];
5026            const mobileGuestForm = guestFormContainersOnInit[1];
5027            const desktopSearchWidgetWrapper = desktopGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5028            const mobileSearchWidgetWrapper = mobileGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5029            if (desktopSearchWidgetWrapper && mobileSearchWidgetWrapper) {
5030                syncGuestFormInnerHTMLToPeer(
5031                    desktopGuestForm,
5032                    mobileGuestForm,
5033                    mobileSearchWidgetWrapper
5034                );
5035            }
5036        }
5037        if (!kombi._searchTabChangeGuestCapBound) {
5038            kombi._searchTabChangeGuestCapBound = true;
5039            document.addEventListener("searchtab:change", function (e) {
5040                const isPackageTabActive = Boolean(e.detail && e.detail.isPackage);
5041                const previousTabWasPackage = kombi._lastTabWasPackage;
5042                const guestFormContainers = document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER);
5043                if (guestFormContainers.length < 2) {
5044                    guestFormContainers.forEach(function (guestFormContainer) {
5045                        const searchWidgetWrapper = guestFormContainer.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5046                        if (searchWidgetWrapper) {
5047                            updateRoomSearchState(guestFormContainer, searchWidgetWrapper);
5048                        }
5049                    });
5050                } else {
5051                    const desktopGuestForm = guestFormContainers[0];
5052                    const mobileGuestForm = guestFormContainers[1];
5053                    const desktopSearchWidgetWrapper = desktopGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5054                    const mobileSearchWidgetWrapper = mobileGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5055                    const lastEditedGuestForm = kombi._lastGuestFormSource;
5056                    if (
5057                        lastEditedGuestForm &&
5058                        (lastEditedGuestForm === desktopGuestForm || lastEditedGuestForm === mobileGuestForm) &&
5059                        desktopSearchWidgetWrapper &&
5060                        mobileSearchWidgetWrapper
5061                    ) {
5062                        if (lastEditedGuestForm === desktopGuestForm) {
5063                            syncGuestFormInnerHTMLToPeer(
5064                                desktopGuestForm,
5065                                mobileGuestForm,
5066                                mobileSearchWidgetWrapper
5067                            );
5068                        } else {
5069                            syncGuestFormInnerHTMLToPeer(
5070                                mobileGuestForm,
5071                                desktopGuestForm,
5072                                desktopSearchWidgetWrapper
5073                            );
5074                        }
5075                    } else if (desktopSearchWidgetWrapper && mobileSearchWidgetWrapper) {
5076                        syncGuestFormInnerHTMLToPeer(
5077                            desktopGuestForm,
5078                            mobileGuestForm,
5079                            mobileSearchWidgetWrapper
5080                        );
5081                    }
5082                    if (previousTabWasPackage === false && isPackageTabActive) {
5083                        if (getTotalGuestsInForm(desktopGuestForm) > MAX.PACKAGE_GUESTS) {
5084                            kombi._hotelGuestSnapshot = serializeGuestOccupation(desktopGuestForm);
5085                            kombi._packageGuestsDirty = false;
5086                            resetGuestFormToPackageDefault(desktopGuestForm, desktopSearchWidgetWrapper);
5087                            resetGuestFormToPackageDefault(mobileGuestForm, mobileSearchWidgetWrapper);
5088                        }
5089                    } else if (previousTabWasPackage === true && !isPackageTabActive) {
5090                        if (
5091                            !kombi._packageGuestsDirty &&
5092                            kombi._hotelGuestSnapshot &&
5093                            desktopSearchWidgetWrapper &&
5094                            mobileSearchWidgetWrapper
5095                        ) {
5096                            applySerializedGuestOccupation(
5097                                desktopGuestForm,
5098                                desktopSearchWidgetWrapper,
5099                                kombi._hotelGuestSnapshot
5100                            );
5101                            applySerializedGuestOccupation(
5102                                mobileGuestForm,
5103                                mobileSearchWidgetWrapper,
5104                                kombi._hotelGuestSnapshot
5105                            );
5106                            kombi._hotelGuestSnapshot = null;
5107                        }
5108                    }
5109                }
5110                kombi._lastTabWasPackage = isPackageTabActive;
5111            });
5112        }
5113        travelersSummary.forEach(function(travelerSummary) {
5114            applyTravelerSummary(roomCount, guestCount, travelerSummary);
5115        })
5116    }
5117
5118    function updateAgeInputForRoom(room) {
5119      const selects = room.querySelectorAll('select.age_of_children_select');
5120      const ageInput = room.querySelector(SELECTORS.AGE_OF_CHILDREN);
5121  
5122      if (!ageInput) return;
5123  
5124      ageInput.value = Array.from(selects)
5125                            .map(sel => sel.value)
5126                            .filter(Boolean)
5127                            .join(',');
5128    }
5129  
5130    let dismissOpenValidationTooltip = null;
5131
5132    function showTooltip(element, title) {
5133        if (!element) return;
5134
5135        dismissOpenValidationTooltip?.();
5136
5137        const existingTooltip = bootstrap.Tooltip.getInstance(element);
5138        if (existingTooltip) existingTooltip.dispose();
5139
5140        const tooltip = new bootstrap.Tooltip(element, {
5141            title,
5142            html: true,
5143            trigger: "manual",
5144            placement: "top",
5145        });
5146        tooltip.show();
5147
5148        const hideAfterClick = () => {
5149            dismissOpenValidationTooltip = null;
5150            const instance = bootstrap.Tooltip.getInstance(element);
5151            if (instance) instance.dispose();
5152        };
5153
5154        dismissOpenValidationTooltip = () => {
5155            document.removeEventListener("click", hideAfterClick);
5156            dismissOpenValidationTooltip = null;
5157            const instance = bootstrap.Tooltip.getInstance(element);
5158            if (instance) instance.dispose();
5159        };
5160
5161        document.addEventListener("click", hideAfterClick, { once: true });
5162    }
5163
5164    function showDateRequiredTooltip(form) {
5165        const inputEl = form.querySelector(".calendar");
5166        showTooltip(inputEl, `
5167            <div class="fs-6">
5168                <strong>Preencha as datas</strong><br>
5169                Para uma melhor experiência, preencha as datas e realize a busca.
5170            </div>
5171        `);
5172    }
5173
5174    function showDestinationRequiredTooltip(form) {
5175        const destEl =
5176            form.querySelector("#search-destination-package-desk") ||
5177            form.querySelectorAll(".search-package-fields .location-search")[1];
5178        showTooltip(destEl, `
5179            <div class="fs-6">
5180                <strong>Preencha o destino</strong><br>
5181                Para uma melhor experiência, preencha o destino e realize a busca.
5182            </div>
5183        `);
5184    }
5185
5186    function showOriginRequiredTooltip(form) {
5187        const originEl =
5188            form.querySelector("#search-origin-package-desk") ||
5189            form.querySelectorAll(".search-package-fields .location-search")[0];
5190        showTooltip(originEl, `
5191            <div class="fs-6">
5192                <strong>Preencha a origem</strong><br>
5193                Para uma melhor experiência, preencha a origem e realize a busca.
5194            </div>
5195        `);
5196    }
5197
5198    function isDateFilled(form) {
5199        const stayStart = form.querySelector('input[name="stay_start"]');
5200        return stayStart && stayStart.value && stayStart.value.trim() !== "";
5201    }
5202
5203    function isPackageOriginFilled(form) {
5204        const originInput = form.querySelector('.search-package-fields input[name="origin_airport"]');
5205        return Boolean(originInput && originInput.value && originInput.value.trim() !== "");
5206    }
5207
5208    function isPackageDestinationFilled(form) {
5209        const destInput = form.querySelector('.search-package-fields input[name="location"]');
5210        return Boolean(destInput && destInput.value && destInput.value.trim() !== "");
5211    }
5212
5213    document.querySelectorAll(".SearchForm").forEach(form => {
5214      form.addEventListener("submit", (e) => {
5215        if (typeof kombi.searchTabs.syncTabInputsDisabled === "function") {
5216          kombi.searchTabs.syncTabInputsDisabled();
5217        }
5218        const activeTab = kombi.searchTabs.getActiveTab(form);
5219        const roomsInput = form.querySelector("input.room-display-count");
5220        const rooms = roomsInput ? parseInt(roomsInput.value, 10) : 1;
5221
5222        const adultsInput = form.querySelectorAll("input[name='adults']");
5223        const childrenInput = form.querySelectorAll("input[name='children']");
5224        const adults = Array.from(adultsInput).reduce((sum, input) => {
5225            return sum + (parseInt(input.value) || 0);
5226        }, 0);
5227        const children = Array.from(childrenInput).reduce((sum, input) => {
5228            return sum + (parseInt(input.value) || 0);
5229        }, 0);
5230
5231        const dateFilled = isDateFilled(form);
5232        const isHotelDefaultConfig = rooms === 1 && adults === 2 && children === 0;
5233
5234        if (activeTab === "package") {
5235            const originFilled = isPackageOriginFilled(form);
5236            const destinationFilled = isPackageDestinationFilled(form);
5237            
5238            if (!originFilled) {
5239                showOriginRequiredTooltip(form);
5240                e.preventDefault();
5241                document.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEac
5241h(updateAgeInputForRoom);
5242                return;
5243            }
5244
5245            if (!destinationFilled) {
5246                showDestinationRequiredTooltip(form);
5247                e.preventDefault();
5248                document.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach(updateAgeInputForRoom);
5249                return;
5250            }
5251
5252            if (!dateFilled) {
5253                showDateRequiredTooltip(form);
5254                e.preventDefault();
5255                document.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach(updateAgeInputForRoom);
5256                return;
5257            }
5258        } else if (!isHotelDefaultConfig && !dateFilled) {
5259            showDateRequiredTooltip(form);
5260            e.preventDefault();
5261            document.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach(updateAgeInputForRoom);
5262            return;
5263        }
5264
5265        const submitBtn = e.submitter;
5266        if (submitBtn) kombi.spinner(submitBtn);
5267        document.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD).forEach(updateAgeInputForRoom);
5268      });
5269    });  
5270
5271    function setupDropdownCloseIcon({ parentId, toggleSelector, closeIconSelector }) {
5272        const parent = document.getElementById(parentId);
5273        if (!parent) return;
5274
5275        const dropdownToggle = parent.querySelector(toggleSelector);
5276        const closeIcon = parent.querySelector(closeIconSelector);
5277        if (!dropdownToggle || !closeIcon) return;
5278
5279        parent.addEventListener("shown.bs.dropdown", () => {
5280            closeIcon.classList.remove("d-none");
5281        });
5282
5283        parent.addEventListener("hidden.bs.dropdown", () => {
5284            closeIcon.classList.add("d-none");
5285        });
5286    }
5287
5288    setupDropdownCloseIcon({
5289        parentId: "search-travelers-desk",
5290        toggleSelector: '[data-bs-toggle="dropdown"]',
5291        closeIconSelector: ".closeIcon",
5292    });
5293
5294    setupDropdownCloseIcon({
5295        parentId: "search-location-desk",
5296        toggleSelector: '[data-bs-toggle="dropdown"]',
5297        closeIconSelector: ".clear-location",
5298    });
5299
5300    setupDropdownCloseIcon({
5301        parentId: "search-origin-package-desk",
5302        toggleSelector: '[data-bs-toggle="dropdown"]',
5303        closeIconSelector: ".clear-location",
5304    });
5305
5306    setupDropdownCloseIcon({
5307        parentId: "search-destination-package-desk",
5308        toggleSelector: '[data-bs-toggle="dropdown"]',
5309        closeIconSelector: ".clear-location",
5310    });
5311
5312    kombi.closeNavSearchDropdowns = function (root) {
5313        if (!root || typeof bootstrap === "undefined" || !bootstrap.Dropdown) return;
5314        root.querySelectorAll('[data-bs-toggle="dropdown"]').forEach((toggle) => {
5315            const dropdownInstance = bootstrap.Dropdown.getInstance(toggle);
5316            if (dropdownInstance) dropdownInstance.hide();
5317        });
5318    };
5319
5320    kombi.closeNavSearchFlatpickrs = function (root) {
5321        if (!root || !kombi.flatpickrInstances) return;
5322        kombi.flatpickrInstances.forEach((flatpickrInstance) => {
5323            if (flatpickrInstance.isOpen && root.contains(flatpickrInstance.element)) {
5324                flatpickrInstance.close();
5325            }
5326        });
5327    };
5328
5329    document
5330        .querySelectorAll(".nav-search, #SearchForm .pattern-form")
5331        .forEach((root) => {
5332            root.addEventListener("show.bs.dropdown", (e) => {
5333                const openingToggle = e.target?.closest?.(
5334                    '[data-bs-toggle="dropdown"]'
5335                );
5336                if (!openingToggle || !root.contains(openingToggle)) {
5337                    return;
5338                }
5339                root.querySelectorAll('[data-bs-toggle="dropdown"]').forEach((toggle) => {
5340                    if (toggle !== openingToggle) {
5341                        const dropdownInstance = bootstrap.Dropdown.getInstance(toggle);
5342                        if (dropdownInstance) dropdownInstance.hide();
5343                    }
5344                });
5345                kombi.closeNavSearchFlatpickrs(root);
5346            });
5347        });
5348
5349    document.querySelectorAll(".guests .closeIcon").forEach((icon) => {
5350        icon.addEventListener("click", () => {
5351            const allGuestFormContainers = document.querySelectorAll(SELECTORS.GUEST_FORM_CONTAINER);
5352            if (!allGuestFormContainers.length) return;
5353            const desktopGuestForm = allGuestFormContainers[0];
5354            const desktopSearchWidgetWrapper = desktopGuestForm.closest(SELECTORS.SEARCH_WIDGET_WRAPPER);
5355            if (!desktopSearchWidgetWrapper) return;
5356            const roomCardElements = Array.from(
5357                desktopGuestForm.querySelectorAll(SELECTORS.ROOM_SELECTION_CARD)
5358            );
5359            if (roomCardElements.length) {
5360                initializeRoomCardFields(roomCardElements[0], true);
5361                for (let i = 1; i < roomCardElements.length; i++) {
5362                    roomCardElements[i].remove();
5363                }
5364            }
5365            updateRoomSearchState(desktopGuestForm, desktopSearchWidgetWrapper);
5366            updateTravelerSummary(desktopGuestForm, desktopSearchWidgetWrapper);
5367            applyPackageGuestCap(desktopGuestForm, desktopSearchWidgetWrapper);
5368            kombi._lastGuestFormSource = desktopGuestForm;
5369            if (allGuestFormContainers.length > 1) {
5370                syncPeerGuestFormFrom(desktopGuestForm);
5371            }
5372            markPackageGuestsDirtyIfNeeded();
5373        });
5374    });
5375
5376    return initSearchWidget();
5377};
5378
5379;
5380kombi.password = function (event) {
5381    const password = event.currentTarget.closest('.pattern-form-floating').querySelector('input');
5382    const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
5383    password.setAttribute('type', type);
5384    const eyeIcon = event.currentTarget.querySelector('.eye-icon');
5385    eyeIcon.classList.toggle('fa-eye-slash');
5386    eyeIcon.classList.toggle('fa-eye');
5387};
5388kombi.copyTransferArea = function (event) {
5389    const copyElement = event.target.closest('.copy-div');
5390    const copyText = copyElement.querySelector('.copy-text');
5391    const transferArea = document.createElement("textarea");
5392
5393    transferArea.value = copyText.innerText;
5394    document.body.appendChild(transferArea);
5395    transferArea.select();
5396
5397    navigator.clipboard.writeText(transferArea.value)
5398        .then(() => {
5399            if (event.target.matches('button.copy-button') || event.target.closest('button.copy-button')) {
5400                const btn = event.target.closest('button.copy-button');
5401                if (btn) {
5402                    const btnText = btn.querySelector('span');
5403                    btnText.textContent = 'Copiado!';
5404                    btn.classList.remove('btn-primary');
5405                    btn.classList.add('btn-success', 'text-white');
5406                }
5407            }
5408        })
5409        .catch(function (err) {
5410            console.error('Erro ao copiar para a área de transferência:', err);
5411        })
5412        .finally(function () {
5413            document.body.removeChild(transferArea);
5414            const copiedElement = copyElement.querySelector('.copy-check') || document.querySelector('.copy-check');
5415
5416            if (copiedElement) {
5417                copiedElement.classList.remove("d-none", "fade-out");
5418                copiedElement.classList.add("fade-in");
5419
5420                setTimeout(function () {
5421                    copiedElement.classList.remove("fade-in");
5422                    copiedElement.classList.add("fade-out");
5423
5424                    setTimeout(function () {
5425                        copiedElement.classList.add("d-none");
5426                    }, 500);
5427                }, 2000);
5428            }
5429        });
5430};
5431;
5432kombi.fullNameValidator = function (full_name) {
5433    /**
5434     * Validation ensures that the string:
5435     * - Has at least two non-space characters before a whitespace
5436     * - Has at least two non-space characters after the whitespace
5437     * - The rest of the string contains only letters (uppercase or lowercase) and spaces
5438     */
5439    const regex = /^[A-Za-z]{2,}\s[A-Za-z]{2,}(?:\s[A-Za-z]*)*$/;
5440    return regex.test(full_name);
5441};;
5442(function () {
5443    "use strict";
5444
5445    if (window.hotelSortListenerAdded) return;
5446    window.hotelSortListenerAdded = true;
5447
5448    const HIDDEN_CLASS = "d-none";
5449    const SORT_FORM_SELECTOR = ".hotel-sort-form";
5450    const SORT_BUTTON_SELECTOR = ".sort-option-btn";
5451    const POLL_TARGET_IDS = ["search-poll-div", "collection-poll-div"];
5452
5453    function updateSortBadges() {
5454        const hasSort = !!document.querySelector(SORT_BUTTON_SELECTOR + '[aria-pressed="true"]');
5455        document.querySelectorAll(".sort-active-badge").forEach(function (badge) {
5456            badge.classList.toggle(HIDDEN_CLASS, !hasSort);
5457        });
5458    }
5459
5460    function pushSortSelectionDatalayer(button) {
5461        if (!button || typeof push_to_datalayer !== "function") return;
5462
5463        const sortKey = button.getAttribute("data-sort-value");
5464        if (!sortKey) return;
5465
5466        push_to_datalayer({
5467            event: "hotel_sort_list",
5468            sort_type: [sortKey],
5469        });
5470    }
5471
5472    function onSortButtonClick(event) {
5473        const button = event.target.closest(SORT_BUTTON_SELECTOR);
5474        if (!button || button.disabled) return;
5475
5476        const form = button.closest(SORT_FORM_SELECTOR);
5477        if (!form) return;
5478
5479        pushSortSelectionDatalayer(button);
5480
5481        form.querySelectorAll(SORT_BUTTON_SELECTOR).forEach(function (option) {
5482            option.removeAttribute("data-sort-loading");
5483        });
5484        button.setAttribute("data-sort-loading", "true");
5485    }
5486
5487    function isPollTarget(target) {
5488        return !!(target && target.id && POLL_TARGET_IDS.indexOf(target.id) !== -1);
5489    }
5490
5491    function onHtmxBeforeSwap(event) {
5492        if (!isPollTarget(event.detail && event.detail.target)) return;
5493        if (kombi.closeOffcanvas) kombi.closeOffcanvas("offcanvasSort");
5494        if (kombi.closeModal) kombi.closeModal("sortModal");
5495    }
5496
5497    function cleanupOrphanBackdrops() {
5498        document.body.classList.remove("modal-open");
5499        document.body.style.overflow = "";
5500        document.body.style.paddingRight = "";
5501        document.querySelectorAll(".modal-backdrop, .offcanvas-backdrop").forEach(function (backdrop) {
5502            backdrop.remove();
5503        });
5504    }
5505
5506    function onHtmxAfterSettle(event) {
5507        if (isPollTarget(event.detail && event.detail.target)) {
5508            cleanupOrphanBackdrops();
5509        }
5510        updateSortBadges();
5511    }
5512
5513    function isSortFormRequest(event) {
5514        const elt = event.detail && event.detail.elt;
5515        return !!(elt && elt.closest && elt.closest(SORT_FORM_SELECTOR));
5516    }
5517
5518    function resetSortLoading(form) {
5519        const scope = form && form.querySelectorAll ? form : document;
5520        scope.querySelectorAll(SORT_BUTTON_SELECTOR).forEach(function (option) {
5521            option.removeAttribute("data-sort-loading");
5522        });
5523    }
5524
5525    function onHtmxAfterRequest(event) {
5526        if (!isSortFormRequest(event)) return;
5527        const successful = event.detail && event.detail.successful;
5528        if (!successful) {
5529            resetSortLoading(event.detail && event.detail.elt && event.detail.elt.closest(SORT_FORM_SELECTOR));
5530        }
5531    }
5532
5533    document.addEventListener("click", onSortButtonClick);
5534    document.addEventListener("htmx:beforeSwap", onHtmxBeforeSwap);
5535    document.addEventListener("htmx:afterSettle", onHtmxAfterSettle);
5536    document.addEventListener("htmx:afterRequest", onHtmxAfterRequest);
5537
5538    updateSortBadges();
5539})();
5540
5541kombi.hotel = function (root) {
5542    const scope = root || document;
5543    scope.querySelectorAll(".toggleRoomsLink").forEach(function (toggleLink) {
5544        if (kombi.lifecycle.isInitialized(toggleLink, "room-toggle")) {
5545            return;
5546        }
5547        const targetId = toggleLink.getAttribute("href");
5548        if (!targetId) return;
5549
5550        const collapseElement =
5551            scope.querySelector(targetId) || document.querySelector(targetId);
5552        if (!collapseElement) return;
5553
5554        collapseElement.addEventListener("shown.bs.collapse", function () {
5555            toggleLink.textContent = "Ver menos quartos";
5556        });
5557
5558        collapseElement.addEventListener("hidden.bs.collapse", function () {
5559            toggleLink.textContent = "Ver mais quartos";
5560        });
5561        kombi.lifecycle.markInitialized(toggleLink, "room-toggle");
5562    });
5563};
5564
5565(function () {
5566    const PACKAGE_POST_RESULTS_SKELETON_MS = 2000;
5567
5568    kombi.packageSearchPollingLoader = function () {
5569        const polls = [...document.querySelectorAll('#search-poll-div[data-package-flight-poll-loader="1"]')];
5570        if (polls.length === 0) {
5571            return;
5572        }
5573        polls.forEach(function (pollDiv) {
5574            if (kombi.flight && kombi.flight.initLoaderTextRotation) {
5575                kombi.flight.initLoaderTextRotation(pollDiv);
5576            }
5577        });
5578    };
5579
5580    function revealPackageSearchPostResultsSkeleton(root) {
5581        if (!root || root.getAttribute("data-package-post-results-skeleton") !== "1") {
5582            return;
5583        }
5584        window.setTimeout(function () {
5585            const live = document.getElementById("search-poll-div");
5586            if (!live || live.getAttribute("data-package-post-results-skeleton") !== "1") {
5587                return;
5588            }
5589            const skeleton = live.querySelector(".package-search-post-results-skeleton");
5590            const panel = live.querySelector(".hotel-search-results-panel");
5591            if (!skeleton || !panel) {
5592                return;
5593            }
5594            skeleton.classList.add("d-none");
5595            panel.classList.remove("d-none");
5596            live.removeAttribute("data-package-post-results-skeleton");
5597        }
5597, PACKAGE_POST_RESULTS_SKELETON_MS);
5598    }
5599
5600    // outerHTML swap: event.detail.target is the replaced (detached) node.
5601    // Always resolve #search-poll-div from the live DOM for skeleton reveal.
5602    kombi.lifecycle.onBody(
5603        "htmx:afterSwap",
5604        function () {
5605            kombi.packageSearchPollingLoader();
5606            const root = document.getElementById("search-poll-div");
5607            if (root) {
5608                revealPackageSearchPostResultsSkeleton(root);
5609            }
5610        },
5611        { targetId: "search-poll-div", onceKey: "hotel-package-poll" }
5612    );
5613})();
5614;
5615kombi.selectRooms = function(button) {
5616    if (!button) {
5617        return;
5618    }
5619
5620    const accordion = document.getElementById('accordionResulOccupation');
5621    if (accordion) {
5622        accordion.scrollIntoView({ behavior: 'smooth', block: 'start' });
5623        const firstItem = accordion.querySelector('.accordion-collapse');
5624        if (firstItem && !firstItem.classList.contains('show')) {
5625            new bootstrap.Collapse(firstItem, { toggle: true });
5626        }
5627    }
5628
5629    const switchButtons = button.closest('#switch-buttons');
5630    const cartButton = switchButtons.querySelector('#cart-button');
5631    if(cartButton) {
5632        button.classList.add('d-none')
5633        cartButton.classList.remove('d-none');
5634    }
5635}
5636
5637kombi.highlightElement = function() {
5638    const el = document.querySelector(".d-sm-block .nav-search");
5639    el.classList.add("highlight");
5640    
5641    setTimeout(() => {
5642      el.classList.remove("highlight");
5643    }, 2000);
5644}
5645
5646kombi.clearSearchField = function(field) {
5647    if (field == "guests") {
5648        const clearGuestsButtons = document.querySelectorAll(".clear-guests .closeIcon")
5649        clearGuestsButtons.forEach(button => {
5650            button.click()
5651        })    
5652    } else {
5653        const clearCalendarButtons = document.querySelectorAll(".clear-calendar")
5654        clearCalendarButtons.forEach(button => {
5655            button.click()
5656        })
5657    }
5658}
5659kombi.hideCartToast = function() {
5660    const cartToast = document.getElementById("cart-toast");
5661    cartToast?.classList.add("d-none");
5662}
5663
5664kombi.suppressCartToast = function() {
5665    const cartToast = document.getElementById("cart-toast");
5666    if (!cartToast) return;
5667
5668    cartToast.dataset.displayToast = "false";
5669    kombi.hideCartToast();
5670}
5671
5672kombi.changeSearch = function(field, options) {
5673    if (field && typeof field === "object") {
5674        options = field;
5675        field = undefined;
5676    }
5677    options = options || {};
5678
5679    if (options.suppressCartToast) {
5680        kombi.suppressCartToast();
5681    }
5682    
5683    kombi.clearSearchField(field)
5684
5685    const offcanvasEl = document.querySelector("#malaOffcanvas")
5686    const offcanvas = bootstrap.Offcanvas.getOrCreateInstance(offcanvasEl);
5687
5688    if (offcanvasEl.classList.contains("show")) {
5689        offcanvas.hide()
5690    }
5691
5692    kombi.closeModal('malaModal');
5693
5694    window.scrollTo({
5695        top: 0,
5696    }); 
5697
5698    if (window.matchMedia("(min-width: 992px)").matches) {
5699        kombi.highlightElement();     
5700        if (field != "guests") {
5701            const checkIfAtTop = () => {
5702                if (window.scrollY <= 5) {
5703                    kombi.openDatePicker();
5704                } else {
5705                    requestAnimationFrame(checkIfAtTop);
5706                }
5707            };    
5708            requestAnimationFrame(checkIfAtTop);
5709        }
5710    } else {
5711        const modalElement = document.getElementById("SearchForm")
5712        let modalInstance = bootstrap.Modal.getInstance(modalElement)
5713        if (!modalInstance) {
5714            modalInstance = new bootstrap.Modal(modalElement)
5715        }
5716        if (field != "guests") {
5717            const showDatePickerAfterModal = () => {
5718                kombi.openDatePicker();
5719                modalElement.removeEventListener("shown.bs.modal", showDatePickerAfterModal);
5720            };
5721            const checkIfAtTop = () => {
5722                if (window.scrollY <= 5) {
5723                    modalElement.addEventListener("shown.bs.modal", showDatePickerAfterModal);
5724                    modalInstance.show();
5725                } else {
5726                    requestAnimationFrame(checkIfAtTop);
5727                }
5728            };
5729            requestAnimationFrame(checkIfAtTop);
5730        }
5731    }      
5732}
5733
5734kombi.isTravelSuitcaseOpen = function () {
5735    const malaOffcanvas = document.getElementById("malaOffcanvas");
5736    const malaModal = document.getElementById("malaModal");
5737    return (
5738        !!(malaOffcanvas && malaOffcanvas.classList.contains("show")) ||
5739        !!(malaModal && malaModal.classList.contains("show"))
5740    );
5741};
5742
5743kombi.showCartToastIfFull = function() {
5744    const cartToast = document.getElementById("cart-toast");
5745    if (!cartToast) return;
5746    // afterOnLoad can race with cart:open-offcanvas (afterSettle)
5747    if (kombi.isTravelSuitcaseOpen()) return;
5748
5749    const isCartFull = cartToast.dataset.isCartFull === "true";
5750    const dataDisplay = cartToast.dataset.displayToast === "true";
5751    if (isCartFull && dataDisplay) {
5752        cartToast.classList.remove("d-none");
5753    }
5754}
5755
5756kombi.travelSuitcaseNavLinkMobile = function() {
5757    // Delegate on body: #travel-suitcase innerHTML swap replaces #malaOffcanvas,
5758    // so listeners on the element itself would be lost after bindOnce.
5759    kombi.lifecycle.bindOnce("travel-suitcase-mobile", function () {
5760        const EXTRA_MODALS =
5761            "#PackageModal, #PackageModalMoreThan9Guests, [id^=\"FlightModal\"]";
5762
5763        function isExtraModal(el) {
5764            return !!(el && el.matches && el.matches(EXTRA_MODALS));
5765        }
5766
5767        function getMalaNav() {
5768            const malaLink = document.querySelector(
5769                '.nav-link[data-bs-target="#malaOffcanvas"]'
5770            );
5771            return {
5772                malaLink: malaLink,
5773                statusColor: malaLink?.querySelector(".status-color"),
5774            };
5775        }
5776
5777        document.body.addEventListener("shown.bs.offcanvas", function (event) {
5778            if (event.target.id !== "malaOffcanvas") return;
5779            const nav = getMalaNav();
5780            nav.malaLink?.classList.add("active");
5781            nav.statusColor?.classList.add("d-none");
5782            kombi.hideCartToast();
5783        });
5784
5785        document.body.addEventListener("hidden.bs.offcanvas", function (event) {
5786            if (event.target.id !== "malaOffcanvas") return;
5787            const nav = getMalaNav();
5788            nav.malaLink?.classList.remove("active");
5789            nav.statusColor?.classList.remove("d-none");
5790            kombi.showCartToastIfFull();
5791        });
5792
5793        document.body.addEventListener("shown.bs.modal", function (event) {
5794            if (!isExtraModal(event.target)) return;
5795            kombi.hideCartToast();
5796        });
5797
5798        document.body.addEventListener("hidden.bs.modal", function (event) {
5799            if (!isExtraModal(event.target)) return;
5800            kombi.showCartToastIfFull();
5801        });
5802    });
5803}
5804
5805kombi.travelSuitcaseNavLinkDesktop = function() {
5806    // Delegate on body: #travel-suitcase swap recreates #malaModal.
5807    // Extra modals toast hide/show is already handled by travelSuitcaseNavLinkMobile.
5808    kombi.lifecycle.bindOnce("travel-suitcase-desktop", function () {
5809        function getMalaNav() {
5810            const malaItem = document.querySelector(
5811                'li[data-bs-target="#malaModal"]'
5812            );
5813            return {
5814                malaItem: malaItem,
5815                statusColor: malaItem?.querySelector(".status-color"),
5816            };
5817        }
5818
5819        document.body.addEventListener("shown.bs.modal", function (event) {
5820            if (event.target.id !== "malaModal") return;
5821            const nav = getMalaNav();
5822            nav.malaItem?.classList.add("text-primary");
5823            nav.statusColor?.classList.add("d-none");
5824            kombi.hideCartToast();
5825        });
5826
5827        document.body.addEventListener("hidden.bs.modal", function (event) {
5828            if (event.target.id !== "malaModal") return;
5829            const nav = getMalaNav();
5830            nav.malaItem?.classList.remove("text-primary");
5831            nav.statusColor?.classList.remove("d-none");
5832            kombi.showCartToastIfFull();
5833        });
5834    });
5835};
5836kombi.toast = function (toastId) {
5837    const toast = document.getElementById(toastId);
5838    if (!toast) return;
5839    toast.classList.remove("d-none");
5840    setTimeout(() => {
5841        toast.classList.add("d-none");
5842    }, 5000);
5843};;
5844kombi.closeOffcanvas = function(offcanvasId) {
5845    const offcanvasElement = document.getElementById(offcanvasId);
5846    const offcanvasInstance = offcanvasElement && bootstrap.Offcanvas.getInstance(offcanvasElement);
5847    if (offcanvasInstance) {
5848        offcanvasInstance.hide();
5849    }
5850};
5851;
5852kombi.closeModal = function(modalId) {
5853    const modalElement = document.getElementById(modalId);
5854    const modalInstance = modalElement && bootstrap.Modal.getInstance(modalElement);
5855    if (modalInstance) modalInstance.hide();
5856}
5857//# sourceMappingURL=kombi-all.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.