PageSourceSearch

https://encounterstravel.com/js/my-custom.js?v=1790241853

js encounterstravel.com collected 2026-10-01 12:38:08 UTC 104,811 bytes, 2,916 lines download raw bytes

1window.addEventListener("scroll", function(e) {
2    var t = this.scrollY;
3    var scrollToTop = document.getElementById("scrollToTop");
4    if (scrollToTop) {
5        scrollToTop.style.display = t >= 100 ? "block" : "none";
6    }
7}),
8
9window.onscroll = function() {
10    let nav = document.getElementById('navbar');
11    // Pages without the main navbar (e.g. the voucher form) must not
12    // throw on every scroll event.
13    if (!nav) {
14        return;
15    }
16    let navLogo = document.getElementById('navbarLogo')
17    let sub = document.getElementById('subNavbar')
18    let options = document.getElementById('destination-options')
19    let logo = document.getElementById('navbar-image');
20    let mobileSearch = document.getElementById('mobile-search');
21    // let navItem = document.getElementsByClassName('dropdown');
22    let dropDownItem = document.getElementById('dropdown');
23    let tourDropDown = document.getElementById('tourdropdown')
24    let navBar = document.getElementById('navbarCollapse')
25
26    if ( window.scrollY > 100 ) {
27        nav.classList.add("fixed");
28        navLogo.classList.remove("d-none")
29        // mobileSearch.classList.remove('d-none');
30        if (logo) {
31            logo.src = "/images/logo_menu.png";
32        }
33        if (sub) {
34          sub.classList.add("fixed-sub")
35          dropDownItem.addEventListener('mouseenter', function() {
36            sub.classList.remove("fixed-sub")
37          });
38          sub.classList.add("fixed-sub")
39          tourDropDown.addEventListener('mouseenter', function() {
40            sub.classList.remove("fixed-sub")
41          });
42        } 
43      } else {
44        nav.classList.remove("fixed");
45        navLogo.classList.add("d-none")
46        // mobileSearch.classList.add('d-none');
47        if (logo) {
48            logo.src = "/images/logo.png";
49        }
50        if(sub ) {
51          sub.classList.remove("fixed-sub")
52        }
53    }
54  }
55
56
57  let myCarousel = document.querySelectorAll('#featureContainer .carousel .carousel-item');
58
59const updateCarousel = () => {
60  let minPerSlide;
61
62  // Check screen width and set minPerSlide accordingly
63  if (window.matchMedia("(max-width: 767.98px)").matches) {
64    minPerSlide = 1; // 1 item per slide for screens less than 768px
65  } else if (window.matchMedia("(min-width: 768px) and (max-width: 1023.98px)").matches) {
66    minPerSlide = 2; // 2 items per slide for screens between 768px and 1023px
67  } else {
68    minPerSlide = 3; // 3 items per slide for screens 1024px and larger
69  }
70
71  // When there is only one item, show it on its own (centered) instead of cloning it
72  if (myCarousel.length <= 1) {
73    minPerSlide = 1;
74  }
75
76  myCarousel.forEach((el) => {
77    let next = el.nextElementSibling;
78
79    // Clear any previously cloned items
80    while (el.children.length > 1) {
81      el.removeChild(el.lastChild);
82    }
83
84    // Clone and append items based on minPerSlide
85    for (let i = 1; i < minPerSlide; i++) {
86      if (!next) {
87        next = myCarousel[0]; // Loop back to the first item if no next sibling
88      }
89      let cloneChild = next.cloneNode(true);
90      el.appendChild(cloneChild.children[0]);
91      next = next.nextElementSibling;
92    }
93  });
94
95  // Center the item whenever only one is shown per slide (single item or mobile)
96  var featureInner = myCarousel.length ? myCarousel[0].parentElement : null;
97  if (featureInner) {
98    featureInner.classList.toggle('is-single', minPerSlide === 1);
99  }
100};
101
102// Initial call to set up the carousel
103updateCarousel();
104
105//tourtype carousel
106
107let tourTypeCarousel = document.querySelectorAll('#tourTypeContainer .carousel .carousel-item');
108
109const updateTourtypeCarousel = () => {
110  let minPerSlide;
111
112  // Check screen width and set minPerSlide accordingly
113  if (window.matchMedia("(max-width: 767.98px)").matches) {
114    minPerSlide = 1; // 1 item per slide for screens less than 768px
115  } else if (window.matchMedia("(min-width: 768px) and (max-width: 1023.98px)").matches) {
116    minPerSlide = 2; // 2 items per slide for screens between 768px and 1023px
117  } else {
118    minPerSlide = 3; // 3 items per slide for screens 1024px and larger
119  }
120
121  // When there is only one item, show it on its own (centered) instead of cloning it
122  if (tourTypeCarousel.length <= 1) {
123    minPerSlide = 1;
124  }
125
126  tourTypeCarousel.forEach((el) => {
127    let next = el.nextElementSibling;
128
129    // Clear any previously cloned items
130    while (el.children.length > 1) {
131      el.removeChild(el.lastChild);
132    }
133
134    // Clone and append items based on minPerSlide
135    for (let i = 1; i < minPerSlide; i++) {
136      if (!next) {
137        next = tourTypeCarousel[0]; // Loop back to the first item if no next sibling
138      }
139      let cloneChild = next.cloneNode(true);
140      el.appendChild(cloneChild.children[0]);
141      next = next.nextElementSibling;
142    }
143  });
144
145  // Center the item whenever only one is shown per slide (single item or mobile)
146  var tourTypeInner = tourTypeCarousel.length ? tourTypeCarousel[0].parentElement : null;
147  if (tourTypeInner) {
148    tourTypeInner.classList.toggle('is-single', minPerSlide === 1);
149  }
150};
151
152// Initial call to set up the carousel
153updateTourtypeCarousel();
154
155// Update the carousels on window resize
156window.addEventListener('resize', updateCarousel);
157window.addEventListener('resize', updateTourtypeCarousel);
158
159  // blog carousel
160  let blogCarousel = document.querySelectorAll('#blogContainer .carousel .carousel-item');
161    blogCarousel.forEach((el) => {
162      const minPerSlide = 2
163      let next = el.nextElementSibling
164      for (var i=1; i<minPerSlide; i++) {
165        if (!next) {
166          next = blogCarousel[0]
167        }
168        let cloneChild = next.cloneNode(true)
169        el.appendChild(cloneChild.children[0])
170        next = next.nextElementSibling
171      }
172  })
173
174  let tourViews = document.querySelectorAll('#tourContainer .carousel .carousel-item');
175    tourViews.forEach((el) => {
176      const minPerSlide = 3
177      let next = el.nextElementSibling
178      for (var i=1; i<minPerSlide; i++) {
179        if (!next) {
180          next = tourViews[0]
181        }
182        let cloneChild = next.cloneNode(true)
183        el.appendChild(cloneChild.children[0])
184        next = next.nextElementSibling
185      }
186  })
187
188  function loginIcon(anchor) {
189    var icon = anchor.querySelector("i");
190    let plusIcon = document.getElementsByClassName('fa-plus');
191  }
192
193  function changeIcon(anchor, showLogin = false) {
194    var icon = document.getElementById('faPlus');
195    let login = document.getElementById('show-login');
196
197    if (showLogin) {
198        icon.classList.remove('fa-plus');
199        icon.classList.add('fa-minus');
200        login.classList.remove('visually-hidden');
201    } else {
202        icon.classList.toggle('fa-plus');
203        icon.classList.toggle('fa-minus');
204
205        login.classList.toggle('visually-hidden', icon.classList.contains('fa-plus'));
206    }
207}
208
209function checkAll(e) {
210  var checkboxes = document.querySelectorAll('.destinationCheck');
211  if (e.checked) {
212      for (var i = 0; i < checkboxes.length; i++) {
213          if (checkboxes[i].type == 'checkbox') {
214              checkboxes[i].checked = true;
215          }
216      }
217  } else {
218      for (var i = 0; i < checkboxes.length; i++) {
219          if (checkboxes[i].type == 'checkbox') {
220              checkboxes[i].checked = false;
221          }
222      }
223  }
224}
225
226const currentUrl = window.location.href;
227const newUrl = new URL(currentUrl);
228const pathname = newUrl.pathname;
229const segments = pathname.split('/');
230const segment = segments[1]; 
231
232// Debounce helper to avoid firing a search request on every keystroke
233function debounce(fn, delayMs) {
234  let timeoutId;
235
236  function debounced(...args) {
237    const ctx = this;
238    clearTimeout(timeoutId);
239    timeoutId = setTimeout(() => fn.apply(ctx, args), delayMs);
240  }
241
242  debounced.cancel = () => {
243    clearTimeout(timeoutId);
244    timeoutId = undefined;
245  };
246
247  return debounced;
248}
249
250// Shared config (avoids global const/let redeclare issues if scripts are included multiple times)
251window.__UTW_SEARCH_CONFIG = window.__UTW_SEARCH_CONFIG || { debounceMs: 2000, minChars: 2 };
252
253$(function() {
254  const { debounceMs, minChars } = window.__UTW_SEARCH_CONFIG;
255  const $searchInput = $('#search-input')
256  const $navSearch = $('.nav-search')
257  const $hitsContainer = $('#hits-container')
258  const $searchResults = $('.search-results')
259  let debouncedPerformSearch = null
260  let activeSearchController = null
261
262  $searchInput.on('focus', () => {
263    if ($searchInput.val() !== '') {
264      $navSearch.addClass('active')
265    }
266  })
267
268  $searchInput.on('keyup', () => {
269    $hitsContainer.scrollTop(0)
270  })
271
272  $('.close-search').on('click', evt => {
273    evt.preventDefault()
274    $searchInput.val('')
275    $navSearch.removeClass('active')
276    $searchResults.hide()  // Hide search results when input is cleared
277    debouncedPerformSearch?.cancel?.()
278    activeSearchController?.abort?.()
279  })
280
281  $searchInput.on('blur', evt => {
282    if (!evt.isDefaultPrevented) {
283      $navSearch.removeClass('active')
284    }
285  })
286
287  const performSearch = async (query) => {
288    const trimmedQuery = String(query || '').trim()
289    if (trimmedQuery.length < minChars) {
290      $searchResults.hide()
291      return
292    }
293
294    try {
295      const controller = new AbortController()
296      activeSearchController?.abort?.()
297      activeSearchController = controller
298
299      const response = await fetch(`/api/blog-search/${encodeURIComponent(trimmedQuery)}`, {
300        signal: controller.signal
301      })
302      const results = await response.json()
303      
304      if (results.length > 0) {
305        renderHits(results)
306        $searchResults.show()
307      } else {
308        renderNoResultsMessage(trimmedQuery)
309        $searchResults.show()
310      }
311    } catch (error) {
312      if (error?.name === 'AbortError') return
313      console.error('Error fetching search results:', error)
314      $searchResults.hide()
315    } finally {
316      activeSearchController = null
317    }
318  }
319
320  debouncedPerformSearch = debounce(performSearch, debounceMs)
321  
322  const renderHits = (hits) => {
323    const newBlogPosts = `<div class="p-0 m-0 search-results-heading"><i class="text-primary-100 p-1 fa-regular fa-newspaper" size="xl"></i>Blog Posts</div>`
324    const hitsHtml = hits.map(blog => `
325      <a href="${microsite === 'us' ? `/blog/${blog.slug}` : `/${microsite}/blog/${blog.slug}`}">
326        <div class="hit">
327          <div class="hit-content d-flex flex-column">
328            <small class="fw-semibold">${blog.name.toUpperCase()}</small>
329            <small>
330              Author: ${blog.author}
331            </small>
332          </div>
333        </div>
334      </a>
335    `).join('')
336
337    $hitsContainer.html(newBlogPosts + hitsHtml)
338  }
339
340  const renderNoResultsMessage = (query) => {
341    $hitsContainer.html(`
342      <div id="no-results-message">
343        <a href="/searchalltours">We didn't find any results for the search <em>"${query}"</em>.</a>
344      </div>
345    `)
346  }
347
348  $searchInput.on('input', () => {
349    const query = String($searchInput.val() || '').trim()
350    if (query !== '') {
351      $navSearch.addClass('active')
352    } else {
353      $navSearch.removeClass('active')
354      $searchResults.hide()
355      debouncedPerformSearch?.cancel?.()
356      activeSearchController?.abort?.()
357      return
358    }
359    debouncedPerformSearch(query)
360  })
361})
362
363const toTitleCase = str => str.replace(/(?:^|\s)\w/g, match => match.toUpperCase())
364
365$(function() {
366  const { debounceMs, minChars } = window.__UTW_SEARCH_CONFIG;
367  const $searchInput = $('#search-input1')
368  const $navSearch = $('.nav-search')
369  const $hitsContainer = $('#hits-container1')
370  const $searchResults = $('.search-results')
371  let debouncedPerformSearch = null
372  let activeSearchController = null
373
374  $searchInput.on('focus', () => {
375    if ($searchInput.val() !== '') {
376      $navSearch.addClass('active')
377    }
378  })
379
380  $searchInput.on('keyup', () => {
381    $hitsContainer.scrollTop(0)
382  })
383
384  $('.close-search').on('click', evt => {
385    evt.preventDefault()
386    $searchInput.val('')
387    $navSearch.removeClass('active')
388    $searchResults.hide()  // Hide search results when input is cleared
389    debouncedPerformSearch?.cancel?.()
390    activeSearchController?.abort?.()
391  })
392
393  $searchInput.on('blur', evt => {
394    if (!evt.isDefaultPrevented) {
395      $navSearch.removeClass('active')
396    }
397  })
398
399  const performSearch = async (query) => {
400    const trimmedQuery = String(query || '').trim()
401    if (trimmedQuery.length < minChars) {
402      $searchResults.hide()
403      return
404    }
405
406    try {
407      const controller = new AbortController()
408      activeSearchController?.abort?.()
409      activeSearchController = controller
410
411      const response = await fetch(`/api/blog-search/${encodeURIComponent(trimmedQuery)}`, {
412        signal: controller.signal
413      })
414      const results = await response.json()
415      
416      if (results.length > 0) {
417        renderHits(results)
418        $searchResults.show()
419      } else {
420        renderNoResultsMessage(trimmedQuery)
421        $searchResults.show()
422      }
423    } catch (error) {
424      if (error?.name === 'AbortError') return
425      console.error('Error fetching search results:', error)
426      $searchResults.hide()
427    } finally {
428      activeSearchController = null
429    }
430  }
431
432  debouncedPerformSearch = debounce(performSearch, debounceMs)
433  
434  const renderHits = (hits) => {
435    const newBlogPosts = `<div class="p-0 m-0 search-results-heading"><i class="text-primary-100 p-1 fa-regular fa-newspaper" size="xl"></i>Blog Posts</div>`
436    const hitsHtml = hits.map(blog => `
437      <a href="${microsite === 'us' ? `/blog/${blog.slug}` : `/${microsite}/blog/${blog.slug}`}">
438        <div class="hit">
439          <div class="hit-content d-flex flex-column">
440            <small class="fw-semibold">${blog.name.toUpperCase()}</small>
441            <small>
442              Author: ${blog.author}
443            </small>
444          </div>
445        </div>
446      </a>
447    `).join('')
448
449    $hitsContainer.html(newBlogPosts + hitsHtml)
450  }
451
452  const renderNoResultsMessage = (query) => {
453    $hitsContainer.html(`
454      <div id="no-results-message">
455        <a href="${microsite === 'us' ? `/blog` : `/${microsite}/blog`}">We didn't find any results for the search <em>"${query}"</em>.</a>
456      </div>
457    `)
458  }
459
460  $searchInput.on('input', () => {
461    const query = String($searchInput.val() || '').trim()
462    if (query !== '') {
463      $navSearch.addClass('active')
464    } else {
465      $navSearch.removeClass('active')
466      $searchResults.hide()
467      debouncedPerformSearch?.cancel?.()
468      activeSearchController?.abort?.()
469      return
470    }
471    debouncedPerformSearch(query)
472  })
473})
474
475const toTitleCase1 = str => str.replace(/(?:^|\s)\w/g, match => match.toUpperCase())
476
477$(function() {
478  const { debounceMs, minChars } = window.__UTW_SEARCH_CONFIG;
479  const $searchInput = $('#search-input2')
480  const $navSearch = $('.nav-search')
481  const $hitsContainer = $('#hits-container2')
482  const $searchResults = $('.search-results')
483  let debouncedPerformSearch = null
484  let activeSearchController = null
485
486  $searchInput.on('focus', () => {
487    if ($searchInput.val() !== '') {
488      $navSearch.addClass('active')
489    }
490  })
491
492  $searchInput.on('keyup', () => {
493    $hitsContainer.scrollTop(0)
494  })
495
496  $('.close-search').on('click', evt => {
497    evt.preventDefault()
498    $searchInput.val('')
499    $navSearch.removeClass('active')
500    $searchResults.hide()  // Hide search results when input is cleared
501    debouncedPerformSearch?.cancel?.()
502    activeSearchController?.abort?.()
503  })
504
505  $searchInput.on('blur', evt => {
506    if (!evt.isDefaultPrevented) {
507      $navSearch.removeClass('active')
508    }
509  })
510
511  const performSearch = async (query) => {
512    const trimmedQuery = String(query || '').trim()
513    if (trimmedQuery.length < minChars) {
514      $searchResults.hide()
515      return
516    }
517
518    try {
519      const controller = new AbortController()
520      activeSearchController?.abort?.()
521      activeSearchController = controller
522
523      const response = await fetch(`/api/blog-search/${encodeURIComponent(trimmedQuery)}`, {
524        signal: controller.signal
525      })
526      const results = await response.json()
527      
528      if (results.length > 0) {
529        renderHits(results)
530        $searchResults.show()
531      } else {
532        renderNoResultsMessage(trimmedQuery)
533        $searchResults.show()
534      }
535    } catch (error) {
536      if (error?.name === 'AbortError') return
537      console.error('Error fetching search results:', error)
538      $searchResults.hide()
539    } finally {
540      activeSearchController = null
541    }
542  }
543
544  debouncedPerformSearch = debounce(performSearch, debounceMs)
545  
546  const renderHits = (hits) => {
547    const newBlogPosts = `<div class="p-0 m-0 search-results-heading"><i class="text-primary-100 p-1 fa-regular fa-newspaper" size="xl"></i>Blog Posts</div>`
548    const hitsHtml = hits.map(blog => `
549      <a href="${microsite === 'us' ? `/blog/${blog.slug}` : `/${microsite}/blog/${blog.slug}`}">
550        <div class="hit">
551          <div class="hit-content d-flex flex-column">
552            <small class="fw-semibold">${blog.name.toUpperCase()}</small>
553            <small>
554              Author: ${blog.author}
555            </small>
556          </div>
557        </div>
558      </a>
559    `).join('')
560
561    $hitsContainer.html(newBlogPosts + hitsHtml)
562  }
563
564  const renderNoResultsMessage = (query) => {
565    $hitsContainer.html(`
566      <div id="no-results-message">
567        <a href="/searchalltours">We didn't find any results for the search <em>"${query}"</em>.</a>
568      </div>
569    `)
570  }
571
572  $searchInput.on('input', () => {
573    const query = String($searchInput.val() || '').trim()
574    if (query !== '') {
575      $navSearch.addClass('active')
576    } else {
577      $navSearch.removeClass('active')
578      $searchResults.hide()
579      debouncedPerformSearch?.cancel?.()
580      activeSearchController?.abort?.()
581      return
582    }
583    debouncedPerformSearch(query)
584  })
585})
586
587const toTitleCase2 = str => str.replace(/(?:^|\s)\w/g, match => match.toUpperCase())
588
589
590// navbar global search
591$(function() {
592  const { debounceMs, minChars } = window.__UTW_SEARCH_CONFIG;
593  const $globalSearchInput = $('#navbar-search-input')
594  const $globalNavSearch = $('.navbar-search')
595  const $globalContainer = $('#navbar-container')
596  const $globalSearch = $('.navbar-search-results')
597  let debouncedPerformSearch = null
598  let activeSearchController = null
599
600  // If this input was already initialized elsewhere (e.g. inline navbar scripts), skip to avoid duplicate requests
601  if ($globalSearchInput.data('searchInit')) return
602  $globalSearchInput.data('searchInit', true)
603
604  $globalSearchInput.on('focus', () => {
605    if ($globalSearchInput.val() !== '') {
606      $globalNavSearch.addClass('active')
607    }
608  })
609
610  $globalSearchInput.on('keyup', () => {
611    $globalContainer.scrollTop(0)
612  })
613
614  $('.close-search').on('click', evt => {
615    evt.preventDefault()
616    $globalSearchInput.val('')
617    $globalNavSearch.removeClass('active')
618    $globalSearch.hide()  // Hide search results when input is cleared
619    debouncedPerformSearch?.cancel?.()
620    activeSearchController?.abort?.()
621  })
622
623  $globalSearchInput.on('blur', evt => {
624    if (!evt.isDefaultPrevented) {
625      $globalNavSearch.removeClass('active')
626    }
627  })
628
629  const performSearch = async (query) => {
630    const trimmedQuery = String(query || '').trim()
631    if (trimmedQuery.length < minChars) {
632      $globalSearch.hide()
633      return
634    }
635
636    try {
637      const controller = new AbortController()
638      activeSearchController?.abort?.()
639      activeSearchController = controller
640
641      const validFallbackMicrosites = ['eu', 'us', 'za', 'ca', 'au', 'nz', 'in', 'uk'];
642      const fallbackMicrosite = validFallbackMicrosites.includes(microsite) ? microsite : 'us';
643      const response = await fetch(window.UTWGlobalSearch ? window.UTWGlobalSearch.searchUrl(trimmedQuery) : `/api/${fallbackMicrosite}/global-filter/${encodeURIComponent(trimmedQuery)}`, {
644        signal: controller.signal
645      })
646      const results = await response.json()
647
648      if (results.tours.length > 0 || results.blogPosts.length > 0 || results.destinations.length > 0) {
649        renderHits(results)
650        $globalSearch.show()
651      } else {
652        renderNoResultsMessage(trimmedQuery)
653        $globalSearch.show()
654      }
655    } catch (error) {
656      if (error?.name === 'AbortError') return
657      console.error('Error fetching search results:', error)
658      $globalSearch.hide()
659    } finally {
660      activeSearchController = null
661    }
662  }
663
664  debouncedPerformSearch = debounce(performSearch, debounceMs)
665
666  const renderHits = (hits) => {
667    // Convert ALL-CAPS API names to Title Case to match trust bar heading style
668    const toTitleCase = (str) => {
669      if (!str) return '';
670      return str.toLowerCase().replace(/(?:^|\s|–|-)\S/g, ch => ch.toUpperCase());
671    };
672    
673    const availableToursHeader = `<div class="search-results-heading"><i class="fa-regular fa-route"></i>Available Tours</div>`
674    const countriesHeader = `<div class="search-results-heading"><i class="fa-regular fa-globe"></i>Countries</div>`
675    const blogPostsHeader = `<div class="search-results-heading"><i class="fa-regular fa-newspaper"></i>Blog Posts</div>`
676    
677    const toursHtml = hits.tours.map(tour => {
678      const href = window.UTWGlobalSearch
679        ? window.UTWGlobalSearch.itemUrl('tour', tour.slug)
680        : (microsite === 'us' ? `/tour/${tour.slug}` : `/${microsite}/tour/${tour.slug}`);
681      return `
682        <a href="${href}">
683          <div class="hit">
684            <div class="hit-icon-wrap"><i class="fa-regular fa-map"></i></div>
685            <div class="hit-content">
686              <span class="hit-title">${toTitleCase(tour.name)}</span>
687              <span class="hit-meta">${tour.days} Days &bull; From ${tour.price || 'On Request'}</span>
688            </div>
689          </div>
690        </a>
691      `;
692    }).join('')
693  
694    const destinationsHtml = hits.destinations.map(destination => {
695      const slug = destination.slug || destination.name.toLowerCase().replace(/\s+/g, '-');
696      const href = window.UTWGlobalSearch
697        ? window.UTWGlobalSearch.itemUrl('destination', slug)
698        : (microsite === 'us' ? `/destination/${slug}` : `/${microsite}/destination/${slug}`);
699      return `
700        <a href="${href}">
701          <div class="hit">
702            <div class="hit-icon-wrap"><i class="fa-regular fa-map-pin"></i></div>
703            <div class="hit-content">
704              <span class="hit-title">${toTitleCase(destination.name)}</span>
705              <span class="hit-meta">${destination.tours_count || 0} Tours</span>
706            </div>
707          </div>
708        </a>
709      `;
710    }).join('')
711  
712    const blogPostsHtml = hits.blogPosts.map(blogPost => {
713      const href = window.UTWGlobalSearch ? window.UTWGlobalSearch.itemUrl('blog', blogPost.slug) : (microsite === 'us' ? `/blog/${blogPost.slug}` : `/${microsite}/blog/${blogPost.slug}`);
714      return `
715        <a href="${href}">
716          <div class="hit">
717            <div class="hit-icon-wrap"><i class="fa-regular fa-file-lines"></i></div>
718            <div class="hit-content">
719              <span class="hit-title">${toTitleCase(blogPost.name)}</span>
720              <span class="hit-meta">Author: ${blogPost.author || 'Staff'}</span>
721            </div>
722          </div>
723        </a>
724      `;
725    }).join('')
726  
727    let html = '';
728    if (hits.destinations.length > 0) {
729      html += countriesHeader + destinationsHtml;
730    }
731    if (hits.tours.length > 0) {
732      html += availableToursHeader + toursHtml;
733    }
734    if (hits.blogPosts.length > 0) {
735      html += blogPostsHeader + blogPostsHtml;
736    }
737    
738    $globalContainer.html(html);
739  }
740  
741
742  const renderNoResultsMessage = (query) => {
743    $globalContainer.html(`
744      <div id="no-results-message">
745        <p>We didn't find any results for the search <em>"${query}"</em>.</p>
746      </div>
747    `)
748  }
749
750  $globalSearchInput.on('input', () => {
751    const query = String($globalSearchInput.val() || '').trim()
752    if (query !== '') {
753      $globalNavSearch.addClass('active')
754    } else {
755      $globalNavSearch.removeClass('active')
756      $globalSearch.hide()
757      debouncedPerformSearch?.cancel?.()
758      activeSearchController?.abort?.()
759      return
760    }
761    debouncedPerformSearch(query)
762  })
763})
764
765const navbarGlobalInput = str => str.replace(/(?:^|\s)\w/g, match => match.toUpperCase())
766
767
768JsLoadingOverlay.setOptions({
769  overlayBackgroundColor: "#FFFFFF",
770  overlayOpacity: 0.6,
771  spinnerIcon: "ball-spin-clockwise-fade",
772  spinnerColor: "#EE3D23",
773  spinnerSize: "2x",
774  overlayIDName: "overlay",
775  spinnerIDName: "spinner",
776  offsetX: 0,
777  offsetY: 0,
778  containerID: null,
779  lockScroll: true,
780  overlayZIndex: 9998,
781  spinnerZIndex: 9999,
782});
783
784var tourCards;
785var lastPage;
786function scrollToID() {
787  var scrollToHere = document.getElementById("emptycontainer");
788  var scrollToY =
789      scrollToHere.getBoundingClientRect().top + window.pageYOffset;
790  window.scrollTo({
791      top: scrollToY - 150,
792      behavior: "smooth",
793  });
794}
795
796var originalTourImages = [];
797let currentMicrosite = microsite === "us" ? "" : microsite;
798function toggle(index, newImageUrl) {
799  // Select all span elements
800  var spans = document.getElementsByClassName("tourElement");
801  var images = document.querySelectorAll("#tourImage img");
802
803  if (index >= 0 && index < images.length) {
804      // Toggle between original and new source
805      if (!originalTourImages[index]) {
806          originalTourImages[index] = images[index].src;
807          images[index].src = newImageUrl;
808      } else {
809          // Revert to original source
810          images[index].src = originalTourImages[index];
811          originalTourImages[index] = null; // Reset original source for future toggles
812      }
813  }
814
815  // Check if index is valid
816  if (index >= 0 && index < spans.length) {
817      // Store original text if not already stored
818      if (!spans[index].originalText) {
819          spans[index].originalText = spans[index].innerHTML;
820      }
821
822      // Toggle between original and new text
823      if (spans[index].innerHTML === spans[index].originalText) {
824          spans[index].innerHTML = "Images";
825      } else {
826          spans[index].innerHTML = spans[index].originalText;
827      }
828  }
829}
830
831var newOriginalImageResources = [];
832
833function toggleTextAtIndex(index, newImageUrl) {
834  // Select all span elements
835  var spans = document.getElementsByClassName("myElement");
836  var images = document.querySelectorAll("#imageContainer img");
837
838  if (index >= 0 && index < images.length) {
839      // Toggle between original and new source
840      if (!newOriginalImageResources[index]) {
841          newOriginalImageResources[index] = images[index].src;
842          images[index].src = newImageUrl;
843      } else {
844          // Revert to original source
845          images[index].src = newOriginalImageResources[index];
846          newOriginalImageResources[index] = null; // Reset original source for future toggles
847      }
848  }
849
850  // Check if index is valid
851  if (index >= 0 && index < spans.length) {
852      // Store original text if not already stored
853      if (!spans[index].originalText) {
854          spans[index].originalText = spans[index].innerHTML;
855      }
856
857      // Toggle between original and new text
858      if (spans[index].innerHTML === spans[index].originalText) {
859          spans[index].innerHTML = "Map";
860      } else {
861          spans[index].innerHTML = spans[index].originalText;
862      }
863  }
864}
865
866let sliderData = {
867  priceRange: { from: 0, to: 100 },
868  durationMonth: { from: 1, to: 30 },
869  monthYearRange: { from: null, to: null }
870}
871
872const getTours = (
873  microsite,
874  destination,
875  tourTypes,
876  on_sale = Boolean,
877  sort,
878  min_duration,
879  max_duration,
880  start_date,
881  end_date,
882  min_price,
883  max_price,
884  per_page,
885  page
886) => {
887  JsLoadingOverlay.show();
888  const hidePriceCheckbox = document.querySelector('input.hidepoa:checked');
889  axios
890      .get(
891          `/api/${microsite && microsite !== 'us' ? microsite + '/' : ''}tour-filter`, {
892              params: {
893                  destination: destination,
894                  tourTypes: tourTypes,
895                  per_page: per_page,
896                  sort: sort,
897                  min_duration: min_duration,
898                  max_duration: max_duration,
899                  start_date: start_date,
900                  end_date: end_date,
901                  on_sale: on_sale,
902                  min_price: min_price,
903                  max_price: max_price,
904                  page: page,
905                  hide_price_on_request: hidePriceCheckbox ? 'true' : 'false',
906              }
907          }
908      )
909      .then((response) => {
910          document.getElementById("tours-cards").innerHTML = "";
911          this.tourCards = response.data.data;
912          
913          document.getElementById("tour-number").innerHTML =
914              response.data.total;
915
916          document.getElementById("mobile-tour-number").innerHTML =
917            response.data.total;
918          document.getElementById("mobile-tour-number1").innerHTML =
919            response.data.total;
920          document.getElementById("mobile-tour-number2").innerHTML =
921          response.data.total;
922          this.lastPage = response.data.last_page;
923
924
925          function stripHtml(html) {
926            const temp = document.createElement('div');
927            temp.innerHTML = html;
928            return temp.textContent || temp.innerText || '';
929          }
930
931          function escapeAttribute(value) {
932            return String(value ?? '')
933              .replace(/&/g, '&amp;')
934              .replace(/"/g, '&quot;')
935              .replace(/</g, '&lt;')
936              .replace(/>/g, '&gt;');
937          }
938
939          const currencySymbol = response.data.microsite.currency_symbol
940          const listType = window.utwAnalytics?.currentListType?.() || 'tour_listing';
941
942          window.utwAnalytics?.trackTourListResponse?.(response.data, {
943            destination,
944            tourTypes,
945            on_sale,
946            sort,
947            min_duration,
948            max_duration,
949            start_date,
950            end_date,
951            min_price,
952            max_price,
953            page,
954            hide_price_on_request: hidePriceCheckbox ? 'true' : 'false',
955          });
956
957
958          function createPagination(totalPages, currentPage) {
959              const container = document.getElementById(
960                  "pagination-container"
961              );
962              container.innerHTML = "";
963
964              if (totalPages <= 1) {
965                  return;
966              }
967
968              const maxVisiblePages = response.data.per_page;
969              const halfMaxVisiblePages = Math.floor(maxVisiblePages / 2);
970              let startPage = Math.max(1, currentPage - halfMaxVisiblePages);
971              const endPage = Math.min(
972                  totalPages,
973                  startPage + maxVisiblePages - 1
974              );
975
976              // Adjust startPage to keep currentPage in the center if possible
977              if (endPage - startPage + 1 < maxVisiblePages) {
978                  startPage = Math.max(1, endPage - maxVisiblePages + 1);
979              }
980
981              const paginationList = document.createElement("ul");
982              paginationList.classList.add("pagination");
983
984              // Add Previous Button
985              const previousButton = createPaginationItem(
986                  currentPage - 1,
987                  "<",
988                  currentPage === 1
989              );
990              paginationList.appendChild(previousButton);
991
992              // Add First Page
993              if (startPage > 1) {
994                  const firstPage = createPaginationItem(1, "1", false);
995                  paginationList.appendChild(firstPage);
996                  if (startPage > 2) {
997                      paginationList.appendChild(createTripDot());
998                  }
999              }
1000
1001              // Add Pages
1002              for (let i = startPage; i <= endPage; i++) {
1003                  const pageItem = createPaginationItem(
1004                      i,
1005                      i.toString(),
1006                      i === currentPage
1007                  );
1008                  paginationList.appendChild(pageItem);
1009              }
1010
1011              // Add Last Page
1012              if (endPage < totalPages) {
1013                  if (endPage < totalPages - 1) {
1014                      paginationList.appendChild(createTripDot());
1015                  }
1016                  const lastPage = createPaginationItem(
1017                      totalPages,
1018                      totalPages.toString(),
1019                      false
1020                  );
1021                  paginationList.appendChild(lastPage);
1022              }
1023
1024              // Add Next Button
1025              const nextButton = createPaginationItem(
1026                  currentPage + 1,
1027                  ">",
1028                  currentPage === totalPages
1029              );
1030              paginationList.appendChild(nextButton);
1031
1032              container.appendChild(paginationList);
1033          }
1034
1035          function createPaginationItem(pageNumber, text, disabled) {
1036              const listItem = document.createElement("li");
1037              listItem.classList.add("page-item");
1038              if (disabled) {
1039                  listItem.classList.add("disabled");
1040              }
1041
1042              const link = document.createElement("a");
1043              link.classList.add("page-link");
1044              link.href = "#";
1045              link.textContent = text;
1046
1047              if (!disabled && pageNumber !== 0) {
1048                  link.addEventListener("click", (event) => {
1049                      event.preventDefault();
1050                      getTours(
1051                          microsite,
1052                          destination,
1053                          tourTypes,
1054                          on_sale = on_sale,
1055                          sort = "popularity_asc",
1056                          sliderData.durationMonth.from,
1057                          sliderData.durationMonth.to, 
1058                          sliderData.monthYearRange.from, 
1059                          sliderData.monthYearRange.to,
1060                          sliderData.priceRange.from, 
1061                          sliderData.priceRange.to,
1062                          per_page,
1063                          pageNumber
1064                      );
1065                  });
1066              }
1067
1068              listItem.appendChild(link);
1069              return listItem;
1070          }
1071
1072          function createTripDot() {
1073              const listItem = document.createElement("li");
1074              listItem.classList.add("page-item");
1075              listItem.classList.add("disabled");
1076              listItem.innerHTML = '<span class="page-link">&hellip;</span>';
1077              return listItem;
1078          }
1079
1080          
1081
1082          createPagination(
1083              response.data.last_page,
1084              response.data.current_page
1085          );
1086
1087          document.getElementById("tour-min-price").innerHTML = 
1088              response.data.lowest_price ? `${response.data.microsite.currency_symbol} ${response.data.lowest_price}` : 'On Request';
1089          
1090          document.getElementById("mobile-min-price").innerHTML = 
1091              response.data.lowest_price ? `${response.data.microsite.currency_symbol} ${response.data.lowest_price}` : 'On Request';
1092
1093          document.getElementById("mobile-min-price1").innerHTML = 
1094              response.data.lowest_price ? `${response.data.microsite.currency_symbol} ${response.data.lowest_price}` : 'On Request';
1095
1096          document.getElementById("mobile-min-price2").innerHTML = 
1097              response.data.lowest_price ? `${response.data.microsite.currency_symbol} ${response.data.lowest_price}` : 'On Request';
1098
1099
1100
1101          const defaultMin = 470;
1102          const defaultMax = 11175;
1103
1104          // Get the price range values from response or use defaults
1105          const maxPrice = response.data.highest_price || defaultMax;
1106          const minPrice = response.data.lowest_price || defaultMin;
1107
1108          // Update both desktop and mobile price range elements
1109          const updatePriceRange = (elementId) => {
1110              const element = document.getElementById(elementId);
1111              if (element) {
1112                  element.setAttribute('data-max', maxPrice);
1113                  element.setAttribute('data-min', minPrice);
1114              }
1115          };
1116
1117          // Update both elements
1118          updatePriceRange("price_range");
1119          updatePriceRange("mobile_price_range");
1120
1121          function getUniqueCountriesFromObject(inputObject) {
1122            // Extract values from the object, then split, trim, and convert them to lowercase
1123            const countries = Object.values(inputObject)
1124              .flat() // In case the values are arrays of countries, flatten them
1125              .map(country => country.trim().toLowerCase());
1126          
1127            // Use a Set to remove duplicates and then convert it back to an array
1128            const uniqueCountries = [...new Set(countries)];
1129          
1130            // Return the country names with the first letter capitalized as an array of HTML <a> tags
1131            return uniqueCountries.map(country => {
1132              const countrySlug = country.replace(/\s+/g, '-'); // Replace spaces with dashes
1133              const capitalizedCountry = country.charAt(0).toUpperCase() + country.slice(1); // Capitalize first letter
1134              return `<a class="link-orange" href="${microsite === 'us' ? `/destination/${countrySlug}` : `/${microsite}/destination/${countrySlug}`}">${capitalizedCountry}</a>`;
1135            }).join(', '); // Join them with commas to appear as a list
1136          }
1137          
1138                       
1139
1140          function getFirstNumber(inputString) {
1141              var regex = /\d+/;
1142              var match = inputString.match(regex);
1143              return match ? parseInt(match[0], 10) : "";
1144          }
1145          
1146          if (response.data.data.length === 0) {
1147              console.log("No tours found");
1148              let tourCard = `
1149              <div class="d-flex justify-content-center mt-5">
1150              <p class="text-center lead-big noresults">
1151                  <strong>Sorry, we couldn't find any tours matching your requirements. <br> Please <span>reset</span> your search results and <span class="text-primary-100">try again</span>.</strong>
1152              </p>
1153              </div>
1154              `;
1155              document.getElementById("tours-cards").innerHTML = tourCard;
1156              JsLoadingOverlay.hide();
1157          } else {
1158              tourCards.forEach((tour, index) => {
1159                  const tourId = tour.tour_id || tour.id || tour.slug;
1160                  const position = ((Number(response.data.current_page || 1) - 1) * Number(response.data.per_page || tourCards.length || 1)) + index + 1;
1161                  const discountPercent = Number(tour.price_details?.discount_percent ?? tour.price_details?.saving ?? 0);
1162                  let tourCard = `
1163                  <div class="col-xl-4 col-lg-6 col-md-6 col-xs-6 col-12 bg-white tour-search-info mx-auto p-md-1 pb-2 mb-3">
1164                          <div class="m-0 p-0 tour-box box-shadow-dark d-flex flex-column justify-content-between" style="min-height: 100%;">
1165                              <div>
1166                                  <div class="tour-box-title p-2 text-uppercase"><a class="link-orange"
1167                                      data-track="select_item"
1168                                      data-tour-name="${escapeAttribute(tour.name)}"
1169                                      data-tour-id="${escapeAttribute(tourId)}"
1170                                      data-position="${position}"
1171                                      data-list-type="${escapeAttribute(listType)}"
1172                                      href="${microsite === 'us' ? `/tour/${tour.slug}` : `/${microsite}/tour/${tour.slug}`}">${
1173                      tour.name
1174                  }</a></div>
1175                                  <div class="tour-search-thumb-container" id="imageContainer">
1176                                      ${ discountPercent > 0 ? `<div class="corner" id="corner"><span>- ${discountPercent}%</span></div>` : ""}
1177                                      <a class="link-orange togglemapimage px-2 myElement" style="cursor: pointer;" onclick="toggleTextAtIndex(${index}, '${
1178                      tour.coupon_image
1179                  }')">Image</a>
1180                                      <a href="${microsite === 'us' ? `/tour/${tour.slug}` : `/${microsite}/tour/${tour.slug}`}">
1181                                          <img src="${
1182                                              tour.map_image
1183                                          }" width="96%" height="220px"
1184                                              alt="Map" class="tour-search-thumb show myImage"
1185                                              data-track="gallery_view"
1186                                              data-image-index="1"
1187                                              data-tour-name="${escapeAttribute(tour.name)}
1187"
1188                                              data-tour-id="${escapeAttribute(tourId)}">
1189                                      </a>
1190                                  </div>
1191                                  <div class="tour-box-destination px-1 m-1">
1192                                  </div>
1193                                  <div class="col-md-12 p-2 toursummary">
1194                                      <div class="row">
1195                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Duration: </p>
1196                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0">${
1197                                              tour.days
1198                                          } Days</p>
1199                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Country(ies): </p>
1200                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0">
1201                                              <a class="link-orange" href="${microsite === 'us' ? `/destination/${tour.slug}` : `/${microsite}/destination/${tour.slug}`}">
1202                                                ${getUniqueCountriesFromObject(tour.countries)}
1203                                              </a>
1204                                          </p>
1205                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Tour Type: </p>
1206                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0"><a class="link-orange"
1207                                                  href="${microsite === 'us' ? `/tourtype/${tour.tour_type.toLowerCase().replace(/\s+/g, '-')}` : `/${microsite}/tourtype/${tour.tour_type.toLowerCase().replace(/\s+/g, '-')}`}">${
1208                                                      tour.tour_type
1209                                                  }</a></p>
1210                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Group size:</p>
1211                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0">Min: ${
1212                                              tour.group_min_size
1213                                          } | Max: ${tour.group_max_size}</p>
1214                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Meals:</p>
1215                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0">
1216                                              B - ${tour.meals.breakfast || 0}, 
1217                                              L - ${tour.meals.lunch || 0}, 
1218                                              D - ${tour.meals.dinner || 0}
1219                                          </p>
1220                                          <p class="leadsmallheading col-4 col-lg-5 p-0 m-0"> Highlights: </p>
1221                                          <p class="leadsmall col-8 col-lg-7 p-0 m-0">${
1222                                              tour.highlights
1223                                          }</p>
1224                                      </div>
1225                                  </div>
1226                              </div>
1227                              <div class="row p-2" style="background:#d7d7d7;">
1228                                  <div class="col-1 p-0 m-0 mt-2 d-flex jusitfy-content-center align-items-center">
1229                                      <p class="vertical-text px-1 pb-2 m-0">
1230                                           ${discountPercent > 0 ? "Deal" : "From"}
1231                                      </p>
1232                                  </div>
1233                                  <div class="col-5 p-0 m-0 mb-2" style="height:50px;">
1234                                      <p class="${tour.price_details.offer_price !== null ? 'text-left m-0 p-0 mt-2 tour-box-bold' : 'mt-2'}">
1235                                              ${tour.price_details.offer_price !== null 
1236                                                  ? `<span class="text-black">${response.data.microsite.currency_symbol}${tour.price_details.offer_price}</span>
1237                                                     <span class="tour-box-bold-f">(p/p)</span>` 
1238                                                  : '<span class="text-left m-0 p-0 tour-box-request text-black">On Request</span>'}
1239                                      </p>
1240                                      ${discountPercent > 0 ? `<del class="text-danger"><small>${response.data.microsite.currency_symbol} ${tour.price_details.price}</small></del>` : ""}
1241                                  </div>
1242                                  <div class="col-6 align-self-center text-right d-flex justify-content-end">
1243                                  <a href="${microsite === 'us' ? `/tour/${tour.slug}` : `/${microsite}/tour/${tour.slug}`}" class="btn btn-orange booknow-btn"
1244                                          data-track="select_item"
1245                                          data-tour-name="${escapeAttribute(tour.name)}"
1246                                          data-tour-id="${escapeAttribute(tourId)}"
1247                                          data-position="${position}"
1248                                          data-list-type="${escapeAttribute(listType)}"
1249                                          title="Book Now">
1249View Tour</a>
1250                                  </div>
1251                              </div>
1252                          </div>
1253                      </div>
1254                  `;
1255                  document.getElementById("tours-cards").innerHTML +=
1256                      tourCard;
1257                  JsLoadingOverlay.hide();
1258                  scrollToID();
1259              });
1260
1261              var selectedValueDisplay = document.getElementById(
1262                  "selectedValueDisplay"
1263              );
1264
1265                    var priceRange = $("#price_range")
1266                    .ionRangeSlider({
1267                        grid: true,
1268                        min: 0,
1269                        max: 100,
1270                        from: 50,
1271                        step: 5,
1272                        prefix: response.data.microsite.currency_symbol,
1273                        onFinish: function (data) {
1274                            sliderData.priceRange = { from: data.from, to: data.to };
1275
1276                            const durationData = sliderData.durationMonth;
1277
1278                            getTours(
1279                                microsite,
1280                                [...new Set(newCheckedValues)].join(','),
1281                                [...new Set(newSelectedValues)].join(','),
1282                                onSale.checked ? true : false,
1283                                regionSort.value + sortSelecet.value,
1284                                durationData.from,
1285                                durationData.to,
1286                                start_date,
1287                                end_date,
1288                                data.from,
1289                                data.to,
1290                                selectElement.value,
1291                                page
1292                            );
1293                        },
1294                    })
1295                    .data("priceRange");
1296
1297                    var priceRange = $("#mobile_price_range")
1298                    .ionRangeSlider({
1299                        grid: true,
1300                        min: 0,
1301                        max: 100,
1302                        from: 50,
1303                        step: 5,
1304                        prefix: response.data.microsite.currency_symbol,
1305                        onFinish: function (data) {
1306                            data.from, data.to;
1307                            getTours(
1308                                microsite,
1309                                [...new Set(newCheckedValues)].join(','),
1310                                [...new Set(newSelectedValues)].join(','),
1311                                onSale.checked ? true : false,
1312                                regionSort.value + sortSelecet.value,
1313                                min_duration,
1314                                max_duration,
1315                                start_date,
1316                                end_date,
1317                                data.from,
1318                                data.to,
1319                                selectElement.value,
1320                                page
1321                            );
1322                        },
1323                    })
1324                    .data("mobilePriceRange");
1325  
1326                    var durationMonth = $("#duration")
1327                    .ionRangeSlider({
1328                        type: "double",
1329                        grid: true,
1330                        min: 1,
1331                        max: 30,
1332                        from: 1,
1333                        step: 1,
1334                        postfix: " days",
1335                        onFinish: function (data) {
1336                            sliderData.durationMonth = { from: data.from, to: data.to };
1337  
1338                            const priceData = sliderData.priceRange;
1339                            getTours(
1340                                microsite,
1341                                [...new Set(newCheckedValues)].join(','),
1342                                [...new Set(newSelectedValues)].join(','),
1343                                onSale.checked ? true : false,
1344                                regionSort.value + sortSelecet.value,
1345                                data.from,
1346                                data.to,
1347                                start_date,
1348                                end_date,
1349                                priceData.from,
1350                                priceData.to,
1351                                selectElement.value,
1352                                page
1353                            );
1354                        },
1355                    })
1356                    .data("durationMonth");
1357
1358                  var durationMonth = $("#mobile_duration")
1359                  .ionRangeSlider({
1360                      type: "double",
1361                      grid: true,
1362                      min: 1,
1363                      max: 30,
1364                      from: 1,
1365                      step: 1,
1366                      postfix: " days",
1367                      onFinish: function (data) {
1368                          // getTours(microsite, destinations)
1369                          getTours(
1370                              microsite,
1371                              [...new Set(newCheckedValues)].join(','),
1372                              [...new Set(newSelectedValues)].join(','),
1373                              onSale.checked ? true : false,
1374                              regionSort.value + sortSelecet.value,
1375                              data.from,
1376                              data.to,
1377                              start_date,
1378                              end_date,
1379                              min_price,
1380                              max_price,
1381                              selectElement.value,
1382                              page
1383                          );
1384                      },
1385                  })
1386                  .data("mobileDurationMonth");
1387  
1388                  $(document).ready(function () {
1389                    var today = new Date();
1390                    var minYear = today.getFullYear();
1391                    var minMonth = today.getMonth();
1392                    var maxYear = minYear + 2;
1393                    var maxMonth = minMonth;
1394
1395                    var minDate = new Date(minYear, minMonth, 1).getTime();
1396                    var maxDate = new Date(maxYear, maxMonth, 1).getTime();
1397                
1398                    $("#month-year-range-slider").ionRangeSlider({
1399                        type: "double",
1400                        min: minDate,
1401                        max: maxDate,
1402                        from: minDate,
1403                        to: maxDate,
1404                        prettify: function (num) {
1405                            var date = new Date(num);
1406                            var month = date.toLocaleString("default", { month: "short" });
1407                            var year = date.getFullYear();
1408                            return month + " " + year;
1409                        },
1410                        onFinish: function (data) {
1411                            var fromDate = new Date(data.from);
1412                            var toDate = new Date(data.to);
1413                
1414                            var fromMonth = ("0" + (fromDate.getMonth() + 1)).slice(-2);
1415                            var fromYear = fromDate.getFullYear();
1416                            var toMonth = ("0" + (toDate.getMonth() + 1)).slice(-2);
1417                            var toYear = toDate.getFullYear();
1418                
1419                            sliderData.monthYearRange = {
1420                                from: `${fromYear}-${fromMonth}`,
1421                                to: `${toYear}-${toMonth}`
1422                            };
1423                
1424                            const priceData = sliderData.priceRange;
1425                            const durationData = sliderData.durationMonth;
1426                
1427                            getTours(
1428                                microsite,
1429                                [...new Set(newCheckedValues)].join(','),
1430                                [...new Set(newSelectedValues)].join(','),
1431                                onSale.checked ? true : false,
1432                                regionSort.value + sortSelecet.value,
1433                                durationData.from,
1434                                durationData.to,
1435                                sliderData.monthYearRange.from,
1436                                sliderData.monthYearRange.to,
1437                                priceData.from,
1438                                priceData.to,
1439                                selectElement.value,
1440                                page
1441                            );
1442                        },
1443                    });
1444                });
1445              
1446  
1447              function hasNumber(inputString) {
1448                  var regex = /\d/;
1449                  return regex.test(inputString);
1450              }
1451          }
1452
1453
1454          $(document).ready(function () {
1455            var today = new Date();
1456            var minYear = today.getFullYear();
1457            var minMonth = today.getMonth();
1458            var maxYear = minYear + 2;
1459            var maxMonth = minMonth;
1460
1461            var minDate = new Date(minYear, minMonth, 1).getTime();
1462            var maxDate = new Date(maxYear, maxMonth, 1).getTime();
1463
1464            $("#mobile-month-year-range-slider").ionRangeSlider({
1465                type: "double",
1466                min: minDate,
1467                max: maxDate,
1468                from: minDate,
1469                to: maxDate,
1470                prettify: function (num) {
1471                  var date = new Date(num);
1472                  var month = date.toLocaleString("default", { month: "short" });
1473                  var year = date.getFullYear();
1474                  return month + " " + year;
1475                },
1476                onFinish: function (data) {
1477                    var fromDate = new Date(data.from);
1478                    var toDate = new Date(data.to);
1479
1480                    var fromMonth = ("0" + (fromDate.getMonth() + 1)).slice(
1481                        -2
1482                    );
1483                    var fromYear = fromDate.getFullYear();
1484                    var toMonth = ("0" + (toDate.getMonth() + 1)).slice(-2);
1485                    var toYear = toDate.getFullYear();
1486
1487                    const priceData = sliderData.priceRange;
1488                    const durationData = sliderData.durationMonth;
1489
1490                    getTours(
1491                        microsite,
1492                        [...new Set(newCheckedValues)].join(','),
1493                        [...new Set(newSelectedValues)].join(','),
1494                        onSale.checked ? true : false,
1495                        regionSort.value + sortSelecet.value,
1496                        min_duration,
1497                        max_duration,
1498                        fromYear + "-" + fromMonth,
1499                        toYear + "-" + toMonth,
1500                        min_price,
1501                        max_price,
1502                        selectElement.value,
1503                        page
1504                    );
1505                },
1506            });
1507        });
1508      })
1509      .catch((error) => {
1510          console.log(error);
1511      });
1512};
1513
1514document.addEventListener("DOMContentLoaded", function() {
1515  const datePriceContainers = document.querySelectorAll('.list-group-flush.bg-white');
1516  const showMoreBtn = document.getElementById('showMoreDates');
1517  const showAllBtn = document.getElementById('showAllDates');
1518
1519  if (!datePriceContainers.length || !showMoreBtn || !showAllBtn) {
1520    return;
1521  }
1522
1523  let visibleItems = 5;
1524  
1525  // Initialize display
1526  function updateDisplay() {
1527      datePriceContainers.forEach((container, index) => {
1528          container.style.display = index < visibleItems ? 'block' : 'none';
1529      });
1530      
1531      // Show/hide buttons based on remaining items
1532      if (datePriceContainers.length <= visibleItems) {
1533          showMoreBtn.style.display = 'none';
1534          showAllBtn.style.display = 'none';
1535      } else {
1536          showMoreBtn.style.display = 'inline-block';
1537          showAllBtn.style.display = 'inline-block';
1538      }
1539      
1540      // Disable "Show More" if we're showing all or nearly all
1541      if (visibleItems >= datePriceContainers.length) {
1542          showMoreBtn.disabled = true;
1543          showAllBtn.disabled = true;
1544      }
1545  }
1546  
1547  // Event listeners
1548  showMoreBtn.addEventListener('click', function() {
1549      visibleItems = Math.min(visibleItems + 3, datePriceContainers.length);
1550      updateDisplay();
1551  });
1552  
1553  showAllBtn.addEventListener('click', function() {
1554      visibleItems = datePriceContainers.length;
1555      updateDisplay();
1556  });
1557  
1558  // Initial display
1559  updateDisplay();
1560});
1561
1562 // show all the tour notes
1563 document.addEventListener("DOMContentLoaded", function () {
1564  const showMoreBtnDesktop = document.getElementById("showDateAndPrices");
1565  const showAllBtnDesktop = document.getElementById("showAllDateAndPrices");
1566  const showMoreBtnMobile = document.getElementById("showMoreDates");
1567  const showAllBtnMobile = document.getElementById("showAllDates");
1568
1569  const allBtn = document.querySelector('[data-filter="all"]');
1570  const buttons = document.querySelectorAll("[data-filter]");
1571
1572  let numItemsToShow = 5;
1573  let currentFilter = "all";
1574
1575  function getVisibleItems() {
1576    if (window.innerWidth >= 576) {
1577      return document.querySelectorAll(".desktop-item");
1578    } else {
1579      return document.querySelectorAll(".mobile-item");
1580    }
1581  }
1582
1583  function updateDisplay() {
1584    const items = getVisibleItems();
1585    let visibleCount = 0;
1586    const isAll = currentFilter === "all";
1587
1588    items.forEach(item => {
1589      const rawCategory = item.getAttribute("data-category") || "all";
1590      const category = rawCategory.trim().toLowerCase();
1591      const match = isAll || category === currentFilter.toLowerCase();
1592
1593      if (match) {
1594        if (isAll && visibleCount >= numItemsToShow) {
1595          item.style.display = "none";
1596        } else {
1597          item.style.display = "block";
1598          visibleCount++;
1599        }
1600      } else {
1601        item.style.display = "none";
1602      }
1603    });
1604
1605    const activeShowMoreBtn = window.innerWidth >= 576 ? showMoreBtnDesktop : showMoreBtnMobile;
1606    const activeShowAllBtn = window.innerWidth >= 576 ? showAllBtnDesktop : showAllBtnMobile;
1607
1608    // Hide all first
1609    if (showMoreBtnDesktop) showMoreBtnDesktop.style.display = "none";
1610    if (showAllBtnDesktop) showAllBtnDesktop.style.display = "none";
1611    if (showMoreBtnMobile) showMoreBtnMobile.style.display = "none";
1612    if (showAllBtnMobile) showAllBtnMobile.style.display = "none";
1613
1614    if (isAll) {
1615      if (items.length <= numItemsToShow) {
1616        // Keep them hidden
1617      } else {
1618        if (activeShowMoreBtn) activeShowMoreBtn.style.display = "inline-block";
1619        if (activeShowAllBtn) activeShowAllBtn.style.display = "inline-block";
1620      }
1621    }
1622  }
1623
1624  function setActiveButton(btn) {
1625    buttons.forEach(b => {
1626      b.classList.remove("btn-orange", "active");
1627      b.classList.add("bg-gray-100", "text-white");
1628    });
1629    btn.classList.remove("bg-gray-100", "text-white");
1630    btn.classList.add("btn-orange", "active");
1631  }
1632
1633  if (allBtn) {
1634    setActiveButton(allBtn);
1635  }
1636  updateDisplay();
1637
1638  buttons.forEach(btn => {
1639    btn.addEventListener("click", function () {
1640      currentFilter = this.getAttribute("data-filter");
1641
1642      if (currentFilter === "all") {
1643        numItemsToShow = 5;
1644      } else {
1645        numItemsToShow = getVisibleItems().length;
1646      }
1647
1648      setActiveButton(this);
1649      updateDisplay();
1650    });
1651  });
1652
1653  const onShowMoreClick = function () {
1654    numItemsToShow += 5;
1655    updateDisplay();
1656  };
1657
1658  const onShowAllClick = function () {
1659    numItemsToShow = getVisibleItems().length;
1660    updateDisplay();
1661  };
1662
1663  showMoreBtnDesktop?.addEventListener("click", onShowMoreClick);
1664  showMoreBtnMobile?.addEventListener("click", onShowMoreClick);
1665
1666  showAllBtnDesktop?.addEventListener("click", onShowAllClick);
1667  showAllBtnMobile?.addEventListener("click", onShowAllClick);
1668
1669  window.addEventListener("resize", function () {
1670    updateDisplay();
1671  });
1672});
1673
1674document.addEventListener("DOMContentLoaded", function () {
1675  const accordionButtons = document.querySelectorAll('.accordion-button-dates');
1676
1677  function syncDateAccordionChrome(button, bookNowButton, icon, expanded) {
1678      if (expanded) {
1679          if (button.contains(bookNowButton)) {
1680              button.removeChild(bookNowButton);
1681          }
1682          if (!button.contains(icon)) {
1683              button.appendChild(icon);
1684          }
1685      } else {
1686          if (!button.contains(bookNowButton)) {
1687              button.appendChild(bookNowButton);
1688          }
1689          if (button.contains(icon)) {
1690              button.removeChild(icon);
1691          }
1692      }
1693  }
1694
1695  accordionButtons.forEach(button => {
1696      const targetSel = button.getAttribute('data-bs-target');
1697      if (!targetSel) {
1698          return;
1699      }
1700      const collapseEl = document.querySelector(targetSel);
1701      if (!collapseEl) {
1702          return;
1703      }
1704
1705      const bookNowButton = document.createElement('span');
1706      bookNowButton.classList.add('btn-orange', 'btn', 'text-white', 'text-uppercase', 'p-2', 'rounded', 'badge-date', 'cursor-click', 'border-0', 'booknow-btn', 'fs-7');
1707      bookNowButton.textContent = 'book now';
1708
1709      const icon = document.createElement('i');
1710      icon.classList.add('fa-solid', 'fa-square-minus', 'cursor-click', 'fs-5', 'text-black');
1711
1712      button.appendChild(bookNowButton);
1713
1714      syncDateAccordionChrome(button, bookNowButton, icon, collapseEl.classList.contains('show'));
1715
1716      collapseEl.addEventListener('shown.bs.collapse', function () {
1717          syncDateAccordionChrome(button, bookNowButton, icon, true);
1718      });
1719      collapseEl.addEventListener('hidden.bs.collapse', function () {
1720          syncDateAccordionChrome(button, bookNowButton, icon, false);
1721      });
1722  });
1723});
1724
1725
1726var perPage = document.getElementById('perpage')
1727var sortSelect = document.getElementById('sortorder')
1728var regionSelect = document.getElementById('regionsort')
1729var onSale = document.getElementById('on_sale')
1730var hidePrice = document.getElementById('hide_price')
1731// const onSale = document.getElementById('on_sale');
1732
1733var selectElement = document.getElementById('perpage')
1734var sortSelecet = document.getElementById('sortorder')
1735var regionSort = document.getElementById('regionsort')
1736
1737var mobileSelectElement = document.getElementById('mobileperpage')
1738var mobileSortSelecet = document.getElementById('mobilesortorder')
1739var mobileRegionSort = document.getElementById('mobileregion')
1740var mobileonSale = document.getElementById('mobile_on_sale')
1741
1742var newCheckedDestination = []
1743var newCheckedTourTypes = []
1744let checkedDestinations = ""
1745let checkedTourTypes = ""
1746var newSelectedValues = []
1747var newCheckedValues = []
1748var toggleSelectAllData = []
1749
1750let sort = "popularity_asc"
1751let min_duration = 1
1752let max_duration = 30
1753let start_date = ""
1754let end_date = ""
1755let min_price;
1756let max_price;
1757let per_page = 9
1758let page = 1
1759
1760// Define this globally or persist it somewhere between calls
1761let selectedCountries = new Set();
1762
1763function toggleSelectAll(inputName, value = null) {
1764  const checkboxes = document.querySelectorAll('input[name="' + inputName + '"]');
1765  let areAllChecked = true;
1766  
1767  checkboxes.forEach(function (checkbox) {
1768      if (!checkbox.checked) {
1769          areAllChecked = false;
1770      }
1771  });
1772
1773  checkboxes.forEach(function (checkbox) {
1774      checkbox.checked = !areAllChecked;
1775  });
1776
1777  getCheckedValues(inputName, value);
1778}
1779
1780
1781
1782var url = window.location.pathname;
1783var parts = url.split('/').filter(part => part.trim() !== '');
1784var lastParam = parts[parts.length - 1];
1785var newSelectedValues = false;
1786
1787const regionCheck = {
1788  'egypt-middle-east': 'egypt-middle-east',
1789  'africa': 'regionAfrica',
1790  'indian-subcontinent': 'indian-subcontinent',
1791  'asia': 'regionAsia',
1792  'southeast-asia': 'southeast-asia',
1793  'south-america': 'south-america',
1794  'europe': 'regionEurope',
1795  'oceania': 'regionOceania'
1796};
1797
1798if (lastParam && regionCheck.hasOwnProperty(lastParam)) {
1799  const checkboxId = regionCheck[lastParam];
1800  document.getElementById(checkboxId).checked = true;
1801}
1802
1803const regionToCheckboxMap = {
1804  'egypt-middle-east': 'egyptCheck',
1805  'africa': 'africaCheck',
1806  'indian-subcontinent': 'subcontinentCheck',
1807  'asia': 'asiaCheck',
1808  'southeast-asia': 'southeastAsia',
1809  'south-america': 'southAmerica',
1810  'europe': 'europeCheck',
1811  'oceania': 'oceaniaCheck'
1812};
1813
1814const tourTypeMap = {
1815  'small-group': 'Small Group',
1816  'shoestring': 'Shoestring',
1817  'city-break': 'City Break',
1818  'private': 'Private',
1819  'family': 'Family',
1820  'overland': 'Overland'
1821};
1822
1823var newCheckedValues = [];
1824var newSelectedValues = [];
1825
1826function getCheckedValues(inputName, value = null) {
1827  // Define all region checkbox names used for destinations
1828  const allRegionNames = [
1829    'egyptCheck', 'africaCheck', 'subcontinentCheck', 'asiaCheck',
1830    'southeastAsia', 'southAmerica', 'europeCheck', 'oceaniaCheck'
1831  ];
1832
1833  // We always scan all regions and tour type checkboxes to keep global filter state fully synchronized
1834  const selectedCheckboxes = document.querySelectorAll('input[name="tourChecked"]');
1835  
1836  function normalizeValue(val) {
1837    return val ? val.replace(/\s+/g, '-').toLowerCase() : '';
1838  }
1839
1840  // Handle onload URL param activation (value === 2 or 3)
1841  if (value === 2 || value === 3) {
1842    const normalizedParam = normalizeValue(lastParam);
1843    if (value === 2) {
1844      let selectedCheck = false;
1845      selectedCheckboxes.forEach(function(checkbox) {
1846        if (normalizeValue(checkbox.value) === lastParam.toLowerCase()) {
1847          if (!selectedCheck) {
1848            checkbox.checked = true;
1849            selectedCheck = true;
1850          }
1851        }
1852      });
1853    } else if (value === 3) {
1854      let selectedChecked = false;
1855      allRegionNames.forEach(function(name) {
1856        var checkboxes = document.querySelectorAll('input[name="' + name + '"]');
1857        checkboxes.forEach(function(checkbox) {
1858          if (normalizeValue(checkbox.value) === lastParam.toLowerCase()) {
1859            if (!selectedChecked) {
1860              checkbox.checked = true;
1861              selectedChecked = true;
1862            }
1863          }
1864        });
1865      });
1866    }
1867  }
1868
1869  // Gather checked tour types from DOM
1870  var selectedValues = [];
1871  selectedCheckboxes.forEach(function(checkbox) {
1872    if (checkbox.checked) {
1873      selectedValues.push(normalizeValue(checkbox.value));
1874    }
1875  });
1876
1877  // Gather checked destinations across ALL regions from DOM
1878  var checkedValues = [];
1879  allRegionNames.forEach(function(name) {
1880    var checkboxes = document.querySelectorAll('input[name="' + name + '"]');
1881    checkboxes.forEach(function(checkbox) {
1882      if (checkbox.checked) {
1883        checkedValues.push(normalizeValue(checkbox.value));
1884      }
1885    });
1886  });
1887
1888  // Update global state variables
1889  newCheckedValues = [...new Set(checkedValues)];
1890  newSelectedValues = [...new Set(selectedValues)];
1891
1892  let destinations = newCheckedValues.join(",");
1893  let tourTypes = newSelectedValues.join(",");
1894  
1895  checkedDestinations = destinations;
1896  checkedTourTypes = tourTypes;
1897  newCheckedDestination = destinations;
1898  newCheckedTourTypes = tourTypes;
1899
1900  // Run the getTours API fetch with the synchronized states
1901  getTours(
1902    microsite, 
1903    destinations, 
1904    tourTypes, 
1905    onSale.checked ? true : false, 
1906    regionSort.value + sortSelecet.value, 
1907    sliderData.durationMonth.from,
1908    sliderData.durationMonth.to, 
1909    sliderData.monthYearRange.from, 
1910    sliderData.monthYearRange.to,
1911    sliderData.priceRange.from, 
1912    sliderData.priceRange.to, 
1913    per_page, 
1914    page
1915  );
1916}
1917
1918function getCheckedTourTypes(inputName, value = null) {
1919  var checkboxes = document.querySelectorAll('input[name="' + inputName + '"]');
1920  var selectedCheckboxes = document.querySelectorAll('input[name="tourChecked"]');
1921
1922  // Helper function to normalize values
1923  function normalizeValue(value) {
1924    return value.replace(/\s+/g, '-').toLowerCase();
1925  }
1926
1927  // Process tourChecked checkboxes
1928  var selectedValues = []
1929  var checkedValues = []
1930  selectedCheckboxes.forEach(function (checkbox) {
1931    if (checkbox.checked) {
1932      selectedValues.push(normalizeValue(checkbox.value));
1933    }
1934    return checkedValues
1935  });
1936
1937  // Process inputName checkboxes
1938
1939  checkboxes.forEach(function (checkbox) {
1940    if (checkbox.checked) {
1941      checkedValues.push(normalizeValue(checkbox.value));
1942    }
1943    return selectedValues
1944  });
1945
1946  // If there is a special case for specific values
1947  var selectedCheck = false;
1948  var selectedChecked = false;
1949  var normalizedValue = normalizeValue(lastParam);
1950
1951  // Process the checkboxes based on the value
1952  if (value === 2 || value === 3) {
1953    var targetCheckboxes = (value === 2) ? selectedCheckboxes : checkboxes;
1954    var targetValues = (value === 2) ? selectedValues : checkedValues;
1955    var isSelectedCheck = (value === 2) ? selectedCheck : selectedChecked;
1956
1957    targetCheckboxes.forEach(function (checkbox) {
1958      if (checkbox.value.replace(/\s+/g, '-').toLowerCase() === lastParam.toLowerCase()) {
1959        // If the checkbox is not already checked, update the checkbox and add value
1960        if (!isSelectedCheck) {
1961          checkbox.checked = true;
1962          isSelectedCheck = true; // Ensure the condition is met only once
1963          targetValues.push(normalizedValue); // Add the normalized value to the array
1964        }
1965      }
1966    });
1967  }
1968
1969
1970  // Update the tour options
1971  let destinations = [...new Set(checkedValues)].join(",");
1972  let tourTypes = [...new Set(selectedValues)].join(",");
1973
1974  checkedDestinations = destinations;
1975  checkedTourTypes = tourTypes;
1976
1977  newCheckedDestination = destinations;
1978  newCheckedTourTypes = tourTypes;
1979
1980  newCheckedValues = checkedValues
1981  newSelectedValues = selectedValues
1982
1983
1984  getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), onSale.checked ? true : false, regionSort.value + sortSelecet.value, sliderData.durationMonth.from,
1985    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
1986    sliderData.priceRange.from, sliderData.priceRange.to, per_page, page)
1987
1988  if (checkedTourTypes.length > 0) {
1989    getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), onSale.checked ? true : false, regionSort.value + sortSelecet.value, sliderData.durationMonth.from,
1990      sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
1991      sliderData.priceRange.from, sliderData.priceRange.to, per_page, page)
1992  }
1993}
1994
1995function normalizeForComparison(str) {
1996  if (!str) return '';
1997  
1998  return String(str).toLowerCase()
1999    .replace(/&/g, 'and')
2000    .replace(/[^a-z0-9]+/g, '-')
2001    .replace(/^-+|-+$/g, '');
2002}
2003
2004function matchesDestination(destinationSlugs, param) {
2005  const normalizedParam = normalizeForComparison(param);
2006  return destinationSlugs.some(slug => normalizeForComparison(slug) === normalizedParam);
2007}
2008
2009function checkRegionCheckboxes(regionParam) {
2010  const normalizedParam = normalizeForComparison(regionParam);
2011  
2012  for (const [region, checkboxName] of Object.entries(regionToCheckboxMap)) {
2013    if (normalizeForComparison(region) === normalizedParam) {
2014      const checkboxes = document.querySelectorAll(`input[name="${checkboxName}"]`);
2015      checkboxes.forEach(checkbox => {
2016        checkbox.checked = true;
2017      });
2018      getCheckedValues(checkboxName, 3);
2019      return true;
2020    }
2021  }
2022  return false;
2023}
2024
2025function checkTourTypeCheckboxes(tourTypeParam) {
2026  const normalizedParam = normalizeForComparison(tourTypeParam);
2027  
2028  for (const [typeSlug, typeValue] of Object.entries(tourTypeMap)) {
2029    if (normalizeForComparison(typeSlug) === normalizedParam) {
2030      const checkboxes = document.querySelectorAll('input[name="tourChecked"]');
2031      checkboxes.forEach(checkbox => {
2032        if (normalizeForComparison(checkbox.value) === normalizeForComparison(typeValue)) {
2033          checkbox.checked = true;
2034        }
2035      });
2036      getCheckedValues('', 2);
2037      return true;
2038    }
2039  }
2040  return false;
2041}
2042
2043if (!checkRegionCheckboxes(lastParam) || !checkTourTypeCheckboxes(lastParam)) {
2044  const destinations = window.destinations || {};
2045  
2046  if (destinations.egyptMiddleEast?.length && matchesDestination(destinations.egyptMiddleEast, lastParam)) {
2047    getCheckedValues('egyptCheck', 3);
2048  } 
2049  else if (destinations.africa?.length && matchesDestination(destinations.africa, lastParam)) {
2050    getCheckedValues('africaCheck', 3);
2051  } 
2052  else if (destinations.subcontinent?.length && matchesDestination(destinations.subcontinent, lastParam)) {
2053    getCheckedValues('subcontinentCheck', 3);
2054  } 
2055  else if (destinations.asia?.length && matchesDestination(destinations.asia, lastParam)) {
2056    getCheckedValues('asiaCheck', 3);
2057  } 
2058  else if (destinations.southeastAsia?.length && matchesDestination(destinations.southeastAsia, lastParam)) {
2059    getCheckedValues('southeastAsia', 3);
2060  } 
2061  else if (destinations.southAmerica?.length && matchesDestination(destinations.southAmerica, lastParam)) {
2062    getCheckedValues('southAmerica', 3);
2063  } 
2064  else if (destinations.europe?.length && matchesDestination(destinations.europe, lastParam)) {
2065    getCheckedValues('europeCheck', 3);
2066  } 
2067  else if (destinations.oceania?.length && matchesDestination(destinations.oceania, lastParam)) {
2068    getCheckedValues('oceaniaCheck', 3);
2069  } 
2070  else if (['antarctic', 'arctic'].includes(lastParam)) {
2071    getCheckedValues('polarRegions', 3);
2072  }
2073}
2074
2075if (['small-group', 'shoestring', 'city-break', 'private', 'family', 'overland'].includes(lastParam)) {
2076  getCheckedValues('', 2)
2077}
2078
2079
2080
2081document.addEventListener('DOMContentLoaded', (event) => {
2082    let destinationsArray = checkedDestinations.split(',');
2083    let uniqueDestinations = [...new Set(destinationsArray)];
2084    const onSaleCheckbox = document.getElementById('on_sale');
2085    if (window.location.pathname === `/${microsite}/travel-deals` || window.location.pathname ===
2086        `/travel-deals`) {
2087        getTours(microsite, checkedDestinations, checkedTourTypes, onSale.checked = true, regionSort.value + sortSelecet.value,
2088          sliderData.durationMonth.from,
2089          sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2090          sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2091
2092    }
2093});
2094
2095document.addEventListener('DOMContentLoaded', (event) => {
2096  if (window.location.pathname === `/${microsite}/safer-encounters` || window.location.pathname ===
2097      `/safer-encounters`) {
2098      // checkedDestinations = checkedDestinations ? `${checkedDestinations},laos` : 'laos';
2099      getTours(microsite, checkedDestinations, checkedTourTypes, onSale.checked, regionSort.value + sortSelecet.value,
2100        sliderData.durationMonth.from,
2101        sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2102        sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2103  }
2104});
2105
2106document.addEventListener('DOMContentLoaded', (event) => {
2107  let destinationsArray = checkedDestinations.split(',');
2108  let uniqueDestinations = [...new Set(destinationsArray)];
2109  const onSaleCheckbox = document.getElementById('on_sale');
2110  if (window.location.pathname === `/${microsite}/destinations` || window.location.pathname ===
2111      `/destinations`) {
2112      getTours(microsite, checkedDestinations, checkedTourTypes, onSale.checked ? true : false, regionSort.value + sortSelecet.value,
2113        sliderData.durationMonth.from,
2114        sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2115        sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2116
2117  }
2118});
2119
2120
2121function getSale() {
2122    // remove duplicate
2123    let destinationsArray = checkedDestinations.split(',');
2124    let uniqueDestinations = [...new Set(destinationsArray)];
2125
2126
2127    getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), onSale.checked ? true : false, regionSort.value + sortSelecet.value,
2128    sliderData.durationMonth.from,
2129    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2130    sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2131}
2132
2133function mobileGetSale() {
2134  // remove duplicate
2135  let destinationsArray = checkedDestinations.split(',');
2136  let uniqueDestinations = [...new Set(destinationsArray)];
2137
2138
2139  getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), mobileonSale.checked ? true : false, regionSort.value + sortSelecet.value,
2140      min_duration, max_duration, start_date, end_date, min_price, max_price, selectElement.value, page)
2141}
2142
2143function getHidePrices() {
2144  getTours(
2145    microsite, 
2146    [...new Set(newCheckedValues)].join(','), 
2147    [...new Set(newSelectedValues)].join(','), 
2148    onSale.checked ? true : false, 
2149    regionSort.value + sortSelecet.value,
2150    sliderData.durationMonth.from,
2151    sliderData.durationMonth.to, 
2152    sliderData.monthYearRange.from, 
2153    sliderData.monthYearRange.to,
2154    sliderData.priceRange.from, 
2155    sliderData.priceRange.to, 
2156    selectElement.value, 
2157    page
2158  );
2159}
2160
2161function getAllTours() {
2162  // remove duplicate
2163  let destinationsArray = checkedDestinations.split(',');
2164  let uniqueDestinations = [...new Set(destinationsArray)];
2165  getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), onSale.checked ? true : false, regionSort.value + sortSelecet.value,
2166    sliderData.durationMonth.from,
2167    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2168    sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2169  }
2170
2171  function mobileAllTours() {
2172    // remove duplicate
2173    let destinationsArray = checkedDestinations.split(',');
2174    let uniqueDestinations = [...new Set(destinationsArray)];
2175    getTours(microsite, [...new Set(newCheckedValues)].join(','), [...new Set(newSelectedValues)].join(','), mobileonSale.checked ? true : false, regionSort.value + sortSelecet.value,
2176      min_duration, max_duration, start_date, end_date, min_price, max_price, selectElement.value, page)
2177    }
2178
2179document.addEventListener('DOMContentLoaded', (event) => {
2180  let destinationsArray = checkedDestinations.split(',');
2181  let uniqueDestinations = [...new Set(destinationsArray)];
2182
2183  if (window.location.pathname === `/${microsite}/searchalltours` || window.location.pathname === `/searchalltours`) {
2184      // Get URL parameters
2185      const urlParams = new URLSearchParams(window.location.search);
2186      const countryParam = urlParams.get('countries');
2187      
2188      // Initialize checkedDestinations as a string
2189      checkedDestinations = '';
2190      
2191      // Define Egypt & Middle East countries with proper capitalization
2192      const egyptMiddleEastCountries = {
2193          'egypt': 'Egypt',
2194          'jordan': 'Jordan',
2195          'israel': 'Israel',
2196          'lebanon': 'Lebanon',
2197          'sudan': 'Sudan'
2198      };
2199      
2200      // If countries parameter exists
2201      if (countryParam) {
2202          checkedDestinations = countryParam;
2203          const countryLower = countryParam.toLowerCase();
2204          
2205          // Check if it's an Egypt & Middle East country
2206          if (Object.keys(egyptMiddleEastCountries).includes(countryLower)) {
2207              // Check the Egypt & Middle East main checkbox
2208              const egyptCheckbox = document.querySelector('#egyptCheck');
2209              if (egyptCheckbox) {
2210                  egyptCheckbox.checked = true;
2211              }
2212              
2213              // Find and check the specific country checkbox using proper capitalization
2214              const properCountryName = egyptMiddleEastCountries[countryLower];
2215              const countryCheckbox = document.querySelector(`input[value="${properCountryName}"][name="egyptCheck"]`);
2216              if (countryCheckbox) {
2217                  countryCheckbox.checked = true;
2218              }
2219              
2220              // Call getCheckedValues after checking boxes
2221              getCheckedValues('egyptCheck', 3);
2222          }
2223      }
2224
2225      // Continue with existing getTours call
2226      getTours(
2227          microsite, 
2228          checkedDestinations,
2229          checkedTourTypes, 
2230          onSale.checked ? true : false, 
2231          regionSort.value + sortSelecet.value,
2232          min_duration, 
2233          max_duration, 
2234          start_date, 
2235          end_date, 
2236          min_price, 
2237          max_price, 
2238          selectElement.value, 
2239          page
2240      );
2241  }
2242  
2243  if (window.location.pathname === `/${microsite}/tourtypes` || window.location.pathname === `/tourtypes`) {
2244      getTours(
2245          microsite, 
2246          checkedDestinations, 
2247          checkedTourTypes, 
2248          onSale.checked ? true : false, 
2249          regionSort.value + sortSelecet.value,
2250          min_duration, 
2251          max_duration, 
2252          start_date, 
2253          end_date, 
2254          min_price, 
2255          max_price, 
2256          selectElement.value, 
2257          page
2258      );
2259  }
2260});
2261
2262function getSelectedOption() {
2263  let destinations = '';
2264  let tourTypes = '';
2265  // let onSale = true
2266  let min_duration = 1
2267  let max_duration = 30
2268  let start_date = ""
2269  let end_date = ""
2270  let min_price;
2271  let max_price;
2272  let page = 1
2273  // remove duplicate
2274  let destinationsArray = checkedDestinations.split(',');
2275  let uniqueDestinations = [...new Set(destinationsArray)];
2276  const checkedValues = newCheckedValues.length > 0 
2277      ? [...new Set(newCheckedValues)].join(',') 
2278      : ''
2279
2280  const selectedValues = newSelectedValues.length > 0 
2281      ? [...new Set(newSelectedValues)].join(',') 
2282      : ''
2283  getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, regionSort.value + sortSelecet.value,
2284    sliderData.durationMonth.from,
2285    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2286    sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2287
2288}
2289function mobileSelecectedOption() {
2290let destinations = '';
2291let tourTypes = '';
2292let min_duration = 1
2293let max_duration = 30
2294let start_date = ""
2295let end_date = ""
2296let min_price;
2297let max_price;
2298let page = 1
2299// remove duplicate
2300let destinationsArray = checkedDestinations.split(',');
2301let uniqueDestinations = [...new Set(destinationsArray)];
2302const checkedValues = newCheckedValues.length > 0 
2303      ? [...new Set(newCheckedValues)].join(',') 
2304      : ''
2305
2306  const selectedValues = newSelectedValues.length > 0 
2307      ? [...new Set(newSelectedValues)].join(',') 
2308      : ''
2309getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, mobileRegionSort.value + mobileSortSelecet.value, min_duration,
2310    max_duration, start_date, end_date,
2311    min_price, max_price, mobileSelectElement.value, page)
2312
2313}
2314
2315function getSortSelected() {
2316  let destinations = '';
2317  let tourTypes = '';
2318  // let onSale = true
2319  let min_duration = 1
2320  let max_duration = 30
2321  let start_date = ""
2322  let end_date = ""
2323  let min_price;
2324  let max_price;
2325  let per_page = 9
2326  let page = 1
2327  // remove duplicate
2328  let destinationsArray = checkedDestinations.split(',');
2329  let uniqueDestinations = [...new Set(destinationsArray)];
2330  const checkedValues = newCheckedValues.length > 0 
2331      ? [...new Set(newCheckedValues)].join(',') 
2332      : ''
2333
2334  const selectedValues = newSelectedValues.length > 0 
2335      ? [...new Set(newSelectedValues)].join(',') 
2336      : ''
2337  getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, regionSort.value + sortSelecet.value, 
2338    sliderData.durationMonth.from,
2339    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2340    sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2341
2342}
2343
2344function mobileSortSelected() {
2345let destinations = '';
2346let tourTypes = '';
2347// let onSale = true
2348let min_duration = 1
2349let max_duration = 30
2350let start_date = ""
2351let end_date = ""
2352let min_price;
2353let max_price;
2354let per_page = 9
2355let page = 1
2356// remove duplicate
2357let destinationsArray = checkedDestinations.split(',');
2358let uniqueDestinations = [...new Set(destinationsArray)];
2359const checkedValues = newCheckedValues.length > 0 
2360      ? [...new Set(newCheckedValues)].join(',') 
2361      : ''
2362
2363  const selectedValues = newSelectedValues.length > 0 
2364      ? [...new Set(newSelectedValues)].join(',') 
2365      : ''
2366getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, mobileRegionSort.value + mobileSortSelecet.value, min_duration,
2367    max_duration, start_date, end_date,
2368    min_price, max_price, mobileSelectElement.value, page)
2369
2370}
2371
2372
2373function getSortedRegion() {
2374  let destinations = '';
2375  let tourTypes = '';
2376  // let onSale = true
2377  let min_duration = 1
2378  let max_duration = 30
2379  let start_date = ""
2380  let end_date = ""
2381  let min_price;
2382  let max_price;
2383  let per_page = 9
2384  let page = 1
2385  // remove duplicate
2386  let destinationsArray = checkedDestinations.split(',');
2387  let uniqueDestinations = [...new Set(destinationsArray)];
2388  const checkedValues = newCheckedValues.length > 0 
2389      ? [...new Set(newCheckedValues)].join(',') 
2390      : ''
2391
2392  const selectedValues = newSelectedValues.length > 0 
2393      ? [...new Set(newSelectedValues)].join(',') 
2394      : ''
2395  getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, regionSort.value + sortSelecet.value, 
2396    sliderData.durationMonth.from,
2397    sliderData.durationMonth.to, sliderData.monthYearRange.from, sliderData.monthYearRange.to,
2398    sliderData.priceRange.from, sliderData.priceRange.to, selectElement.value, page)
2399
2400}
2401
2402function mobileGetSorted() {
2403let destinations = '';
2404let tourTypes = '';
2405// let onSale = true
2406let min_duration = 1
2407let max_duration = 30
2408let start_date = ""
2409let end_date = ""
2410let min_price;
2411let max_price;
2412let per_page = 9
2413let page = 1
2414// remove duplicate
2415let destinationsArray = checkedDestinations.split(',');
2416let uniqueDestinations = [...new Set(destinationsArray)];
2417const checkedValues = newCheckedValues.length > 0 
2418    ? [...new Set(newCheckedValues)].join(',') 
2419    : ''
2420
2421const selectedValues = newSelectedValues.length > 0 
2422    ? [...new Set(newSelectedValues)].join(',') 
2423    : ''
2424getTours(microsite, checkedValues, selectedValues, onSale.checked ? true : false, mobileRegionSort.value + mobileSortSelecet.value, min_duration,
2425    max_duration, start_date, end_date,
2426    min_price, max_price, mobileSelectElement.value, page)
2427
2428}
2429
2430document.addEventListener('DOMContentLoaded', function () {
2431  const currentUrl = new URL(window.location.href);
2432  const pathParts = currentUrl.pathname.split('/').filter(Boolean);
2433  const indexOfP = pathParts.indexOf('p');
2434  if (indexOfP !== -1 && pathParts[indexOfP + 1] === 'safer-encounters') {
2435    const prefixParts = pathParts.slice(0, indexOfP);
2436    const newPath = '/' + [...prefixParts, 'safer-encounters'].join('/');
2437    window.history.replaceState(null, '', newPath);
2438  }
2439});
2440
2441document.addEventListener('DOMContentLoaded', function() {
2442  // Initialize all accordion icons
2443  updateAllAccordionIcons('.accordion-button-itinerary');
2444  updateAllAccordionIcons('.accordion-button-notes');
2445
2446  // Set up event listeners for itinerary accordions
2447  document.querySelectorAll('.accordion-button-itinerary').forEach(button => {
2448      button.addEventListener('click', function() {
2449          setTimeout(() => {
2450              updateAllAccordionIcons('.accordion-button-itinerary');
2451          }, 100);
2452      });
2453  });
2454
2455  // Set up event listeners for notes accordions
2456  document.querySelectorAll('.accordion-button-notes').forEach(button => {
2457      button.addEventListener('click', function() {
2458          setTimeout(() => {
2459              updateAllAccordionIcons('.accordion-button-notes');
2460          }, 100);
2461      });
2462  });
2463});
2464
2465// Generic function to update icons for a specific accordion type
2466function updateAllAccordionIcons(accordionClass) {
2467  document.querySelectorAll(accordionClass).forEach(button => {
2468      const icon = button.querySelector('.accordion-icon');
2469      const isExpanded = button.getAttribute('aria-expanded') === 'true';
2470
2471      if (isExpanded) {
2472          icon.classList.replace('fa-plus-circle', 'fa-minus-circle');
2473      } else {
2474          icon.classList.replace('fa-minus-circle', 'fa-plus-circle');
2475      }
2476  });
2477}
2478
2479// Original function for notes accordion (unchanged)
2480function toggleItineraryAccordion(action) {
2481  const accordions = document.querySelectorAll('.accordion-button-notes');
2482  
2483  accordions.forEach((accordion, index) => {
2484      if (index === 0) {
2485          const isExpanded = accordion.getAttribute('aria-expanded') === 'true';
2486          if ((action === 'expand' && !isExpanded) || (action === 'collapse' && isExpanded)) {
2487              accordion.click();
2488          }
2489      } else {
2490          if (action === 'expand' && accordion.classList.contains('collapsed')) {
2491              accordion.click();
2492          } else if (action === 'collapse' && !accordion.classList.contains('collapsed')) {
2493              accordion.click();
2494          }
2495      }
2496  });
2497
2498  // Update icons after toggling
2499  setTimeout(() => {
2500      updateAllAccordionIcons('.accordion-button-notes');
2501  }, 150);
2502}
2503
2504document.addEventListener("DOMContentLoaded", () => {
2505  const divMap = {
2506      toDiv1: "div1",
2507      toDiv2: "div2",
2508      toDiv3: "div3",
2509      toDiv4: "div4",
2510      toAfrica: "africa",
2511      toIndianSubcontinent: "indianSubcontinent",
2512      toAsia: "asia",
2513      toSoutheastAsia: "southeastAsia",
2514      toSouthAmerica: "southAmerica",
2515      toEurope: "europe",
2516      toOceania: "oceania",
2517      goback1: "div1"
2518  };
2519
2520
2521  const showDiv = (showId) => {
2522      document.querySelectorAll(".modal-content").forEach(div => {
2523          div.classList.add("d-none");
2524      });
2525      document.getElementById(showId)?.classList.remove("d-none");
2526  };
2527
2528  Object.entries(divMap).forEach(([buttonId, showId]) => {
2529      const button = document.getElementById(buttonId);
2530      if (button) {
2531          button.addEventListener("click", () => showDiv(showId));
2532      }
2533  });
2534
2535  document.querySelectorAll(".backDestination").forEach(button => {
2536      button.addEventListener("click", () => showDiv("div3"));
2537  });
2538});
2539
2540(function () {
2541    const STORAGE_KEY = 'utw_wishlist';
2542
2543    function getWishlist() {
2544        try {
2545            const raw = localStorage.getItem(STORAGE_KEY);
2546            const parsed = raw ? JSON.parse(raw) : [];
2547            return Array.isArray(parsed) ? parsed : [];
2548        } catch {
2549            return [];
2550        }
2551    }
2552
2553    function saveWishlist(items) {
2554        localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
2555        document.dispatchEvent(new CustomEvent('wishlist:updated', { detail: { items } }));
2556    }
2557
2558    function buildTourUrl(slug) {
2559        const ms = typeof microsite !== 'undefined' ? microsite : 'us';
2560        return ms === 'us' ? `/tour/${slug}` : `/${ms}/tour/${slug}`;
2561    }
2562
2563    function itemFromElement(el) {
2564        const id = el.dataset.wishlistId;
2565        if (!id) return null;
2566
2567        return {
2568            id,
2569            slug: id,
2570            title: el.dataset.wishlistTitle || '',
2571            subtitle: el.dataset.wishlistSubtitle || '',
2572            category: el.dataset.wishlistCategory || '',
2573            image: el.dataset.wishlistImage || '',
2574            url: el.dataset.wishlistUrl || buildTourUrl(id),
2575            duration: el.dataset.wishlistDuration || '',
2576            price: el.dataset.wishlistPrice || '',
2577        };
2578    }
2579
2580    function isInWishlist(id) {
2581        return getWishlist().some((item) => item.id === id);
2582    }
2583
2584    function addItem(item) {
2585        const items = getWishlist();
2586        if (items.some((i) => i.id === item.id)) return items;
2587        items.push(item);
2588        saveWishlist(items);
2589        return items;
2590    }
2591
2592    function removeItem(id) {
2593        const items = getWishlist().filter((item) => item.id !== id);
2594        saveWishlist(items);
2595        return items;
2596    }
2597
2598    function toggleItem(item) {
2599        if (isInWishlist(item.id)) {
2600            return removeItem(item.id);
2601        }
2602        return addItem(item);
2603    }
2604
2605    function escapeHtml(str) {
2606        const div = document.createElement('div');
2607        div.textContent = str;
2608        return div.innerHTML;
2609    }
2610
2611    function updateToggleButton(btn) {
2612        const id = btn.dataset.wishlistId;
2613        if (!id) return;
2614
2615        const added = isInWishlist(id);
2616        const icon = btn.querySelector('i');
2617        const label = btn.querySelector('.wishlist-btn-label');
2618
2619        btn.classList.toggle('is-added', added);
2620        btn.setAttribute('aria-pressed', added ? 'true' : 'false');
2621
2622        if (icon) {
2623            icon.classList.remove('far', 'fas', 'fa-regular', 'fa-solid');
2624            icon.classList.add(added ? 'fa-solid' : 'fa-regular');
2625            icon.setAttribute('aria-hidden', 'true');
2626        }
2627
2628        if (label) {
2629            label.textContent = added ? 'ADDED TO WISHLIST' : 'ADD TO WISHLIST';
2630        } else if (btn.classList.contains('btn-wishlist')) {
2631            const text = added ? 'ADDED TO WISHLIST' : 'ADD TO WISHLIST';
2632            btn.innerHTML = `<i class="${added ? 'fa-solid' : 'fa-regular'} fa-heart" aria-hidden="true"></i> ${text}`;
2633        }
2634
2635        if (btn.classList.contains('wishlist-card-heart')) {
2636            btn.classList.toggle('is-active', added);
2637        }
2638    }
2639
2640    function updateAllToggleButtons() {
2641        document.querySelectorAll('.js-wishlist-toggle').forEach(updateToggleButton);
2642    }
2643
2644    function updateCount() {
2645        const count = getWishlist().length;
2646        document.querySelectorAll('[data-wishlist-count]').forEach((el) => {
2647            el.textContent = String(count);
2648        });
2649    }
2650
2651    function renderPanel() {
2652        const listEl = document.getElementById('wishlistPanelList');
2653        const emptyEl = document.getElementById('wishlistPanelEmpty');
2654        if (!listEl) return;
2655
2656        const items = getWishlist();
2657        listEl.innerHTML = '';
2658
2659        if (items.length === 0) {
2660            if (emptyEl) emptyEl.classList.remove('d-none');
2661            return;
2662        }
2663
2664        if (emptyEl) emptyEl.classList.add('d-none');
2665
2666        items.forEach((item) => {
2667            const row = document.createElement('div');
2668            row.className = 'wishlist-item';
2669            row.innerHTML = `
2670                <a href="${escapeHtml(item.url)}" class="wishlist-item-link">
2671                    <img src="${escapeHtml(item.image)}" alt="" class="wishlist-item-img" onerror="this.style.display='none'">
2672                    <div class="wishlist-item-text">
2673                        <div class="wishlist-item-title">${escapeHtml(item.title)} Tour</div>
2674                        <div class="wishlist-item-category">${escapeHtml(item.category)}</div>
2675                    </div>
2676                </a>
2677                <button type="button" class="wishlist-item-remove" data-wishlist-remove="${escapeHtml(item.id)}" aria-label="Remove from wishlist">&times;</button>
2678            `;
2679            listEl.appendChild(row);
2680        });
2681    }
2682
2683    function initPanelPortal() {
2684        const panel = document.getElementById('wishlistPanel');
2685        if (panel && panel.parentElement !== document.body) {
2686            document.body.appendChild(panel);
2687        }
2688    }
2689
2690    function positionPanel() {
2691        const trigger = document.getElementById('wishlistTrigger');
2692        const panel = document.getElementById('wishlistPanel');
2693        if (!trigger || !panel || !panel.classList.contains('is-open')) return;
2694
2695        const rect = trigger.getBoundingClientRect();
2696        const gap = 12;
2697        const panelWidth = panel.offsetWidth || 380;
2698        const panelHeight = panel.offsetHeight || 320;
2699        const viewportPadding = 16;
2700
2701        let top = rect.bottom + gap;
2702        if (top + panelHeight > window.innerHeight - viewportPadding) {
2703            top = Math.max(viewportPadding, rect.top - panelHeight - gap);
2704        }
2705
2706        let left = rect.right - panelWidth;
2707        left = Math.max(viewportPadding, Math.min(left, window.innerWidth - panelWidth - viewportPadding));
2708
2709        panel.style.top = `${top}px`;
2710        panel.style.left = `${left}px`;
2711        panel.style.right = 'auto';
2712    }
2713
2714    function openPanel() {
2715        const panel = document.getElementById('wishlistPanel');
2716        if (!panel) return;
2717
2718        panel.classList.add('is-open');
2719        panel.setAttribute('aria-hidden', 'false');
2720        requestAnimationFrame(() => {
2721            positionPanel();
2722            requestAnimationFrame(positionPanel);
2723        });
2724    }
2725
2726    function closePanel() {
2727        const panel = document.getElementById('wishlistPanel');
2728        if (panel) {
2729            panel.classList.remove('is-open');
2730            panel.setAttribute('aria-hidden', 'true');
2731        }
2732    }
2733
2734    function refreshUI() {
2735        updateCount();
2736        updateAllToggleButtons();
2737        renderPanel();
2738    }
2739
2740    function bindEvents() {
2741        document.addEventListener('click', (e) => {
2742            const toggle = e.target.closest('.js-wishlist-toggle');
2743            if (toggle) {
2744                e.preventDefault();
2745                e.stopPropagation();
2746                const item = itemFromElement(toggle);
2747                if (item) toggleItem(item);
2748                return;
2749            }
2750
2751            const removeBtn = e.target.closest('[data-wishlist-remove]');
2752            if (removeBtn) {
2753                e.preventDefault();
2754                removeItem(removeBtn.dataset.wishlistRemove);
2755                return;
2756            }
2757
2758            const trigger = e.target.closest('#wishlistTrigger');
2759            if (trigger) {
2760                e.preventDefault();
2761                const panel = document.getElementById('wishlistPanel');
2762                if (panel?.classList.contains('is-open')) {
2763                    closePanel();
2764                } else {
2765                    openPanel();
2766                }
2767                return;
2768            }
2769
2770            const closeBtn = e.target.closest('#wishlistPanelClose');
2771            if (closeBtn) {
2772                e.preventDefault();
2773                closePanel();
2774                return;
2775            }
2776
2777            // Click outside logic
2778            const panel = document.getElementById('wishlistPanel');
2779            if (!panel?.classList.contains('is-open')) return;
2780            if (document.getElementById('wishlistTrigger')?.contains(e.target) || panel.contains(e.target)) return;
2781            closePanel();
2782        });
2783
2784        document.addEventListener('wishlist:updated', refreshUI);
2785
2786        document.addEventListener('keydown', (e) => {
2787            if (e.key === 'Escape') closePanel();
2788        });
2789
2790        window.addEventListener('resize', positionPanel);
2791        window.addEventListener('scroll', (e) => {
2792            if (e.target === document || e.target === window) {
2793                const nav = document.getElementById('navbar');
2794                if (nav && nav.classList.contains('fixed')) {
2795                    closePanel();
2796                } else {
2797                    positionPanel();
2798                }
2799            } else {
2800                positionPanel();
2801            }
2802        }, true);
2803    }
2804
2805    window.UTWWishlist = {
2806        getWishlist,
2807        addItem,
2808        removeItem,
2809        toggleItem,
2810        isInWishlist,
2811        refreshUI,
2812    };
2813
2814    document.addEventListener('DOMContentLoaded', () => {
2815        initPanelPortal();
2816        bindEvents();
2817        refreshUI();
2818    });
2819})();
2820
2821/* ============================================================
2822   Home Hero Carousel
2823   ============================================================ */
2824(function () {
2825    const hero = document.getElementById('home-hero');
2826    if (!hero) return;
2827
2828    const bg = document.getElementById('home-hero-bg');
2829    const slides = bg ? Array.from(bg.querySelectorAll('.home-hero-slide')) : [];
2830    const thumbs = Array.from(document.querySelectorAll('.home-hero-thumb'));
2831    const prevBtn = document.getElementById('homeHeroPrev');
2832    const nextBtn = document.getElementById('homeHeroNext');
2833    const titleEl = document.getElementById('home-hero-title');
2834    const ctaEl = document.getElementById('home-hero-cta');
2835    const ctaTextEl = document.getElementById('home-hero-cta-text');
2836
2837    if (!slides.length) return;
2838
2839    let current = 0;
2840    let autoTimer = null;
2841    const INTERVAL = 5000;
2842
2843    // Collect slide data from the blade-rendered elements
2844    const slideData = slides.map((slide, i) => ({
2845        desktopBg: slide.dataset.desktopBg || '',
2846        mobileBg: slide.dataset.mobileBg || slide.dataset.desktopBg || '',
2847        heading: slide.dataset.heading || '',
2848        ctaText: slide.dataset.ctaText || '',
2849        ctaUrl: slide.dataset.ctaUrl || '',
2850    }));
2851
2852    function isMobile() {
2853        return window.innerWidth < 768;
2854    }
2855
2856    function applyBg(slide, index) {
2857        const src = isMobile() ? slideData[index].mobileBg : slideData[index].desktopBg;
2858        if (src) slide.style.backgroundImage = `url('${src}')`;
2859    }
2860
2861    // Apply correct bg on resize
2862    window.addEventListener('resize', () => {
2863        slides.forEach((slide, i) => applyBg(slide, i));
2864    });
2865    slides.forEach((slide, i) => applyBg(slide, i));
2866
2867    function goTo(index) {
2868        slides[current].classList.remove('active');
2869        if (thumbs[current]) thumbs[current].classList.remove('active');
2870
2871        current = (index + slides.length) % slides.length;
2872
2873        slides[current].classList.add('active');
2874        if (thumbs[current]) thumbs[current].classList.add('active');
2875
2876        if (titleEl) titleEl.textContent = slideData[current].heading;
2877        if (ctaTextEl) ctaTextEl.textContent = slideData[current].ctaText;
2878        if (ctaEl && slideData[current].ctaUrl) ctaEl.setAttribute('href', slideData[current].ctaUrl);
2879    }
2880
2881    function startAuto() {
2882        clearInterval(autoTimer);
2883        autoTimer = setInterval(() => goTo(current + 1), INTERVAL);
2884    }
2885
2886    function resetAuto() {
2887        startAuto();
2888    }
2889
2890    // Thumbnail clicks
2891    thumbs.forEach((thumb, i) => {
2892        thumb.addEventListener('click', () => {
2893            goTo(i);
2894            resetAuto();
2895        });
2896    });
2897
2898    // Arrow buttons
2899    if (prevBtn) {
2900        prevBtn.addEventListener('click', () => {
2901            goTo(current - 1);
2902            resetAuto();
2903        });
2904    }
2905    if (nextBtn) {
2906        nextBtn.addEventListener('click', () => {
2907            goTo(current + 1);
2908            resetAuto();
2909        });
2910    }
2911
2912    // Only auto-rotate when there is more than one slide
2913    if (slides.length > 1) {
2914        startAuto();
2915    }
2916})();

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.