1(function() { 2 if (window.SearchModern) { 3 return; 4 } 5 6 var searchModernConfig = window.LR_SEARCH_MODERN_CONFIG || {}; 7 var disableLetsRoamSavedSearchPrefill = !!searchModernConfig.disableLetsRoamSavedSearchPrefill; 8 var letsRoamSavedSearchPrefillAllowlist = [ 9 'scavenger-hunt', 10 'outdoor-activities', 11 'bar-crawl', 12 'art-walks', 13 'audio', 14 'ghost' 15 ]; 16 17 // The classic LR header supplies pageLanguage, not mhCurrentLanguage. 18 var currentLanguage = window.mhCurrentLanguage || window.pageLanguage || 'en'; 19 var pageContext = window.mhPageContext || { 20 isBachelorette: window.location.href.includes('bachelorette') || window.location.href.includes('/bach'), 21 isBirthday: window.location.href.includes('birthday') || window.location.href.includes('/birth'), 22 isCorporate: window.location.href.includes('team_building') || window.location.href.includes('team-building') || (typeof IS_CORPORATE !== 'undefined' && IS_CORPORATE) 23 }; 24 var smartCitySearchEnabled = searchModernConfig.websiteDomain === 'sch' 25 || (searchModernConfig.websiteDomain === 'letsroam' && !pageContext.isCorporate && !searchModernConfig.isCorporate); 26 var smartCitySearchParam = searchModernConfig.websiteDomain === 'letsroam' && smartCitySearchEnabled ? '&smart_city_search=1' : ''; 27 28 function translate(text) { 29 return (typeof window.translateText === 'function') ? window.translateText(text) : text; 30 } 31 32 function shouldRestoreLetsRoamSavedSearchPrefill() { 33 if (!disableLetsRoamSavedSearchPrefill) { 34 return true; 35 } 36 37 var currentPath = (window.location.pathname || '').toLowerCase(); 38 for (var i = 0; i < letsRoamSavedSearchPrefillAllowlist.length; i++) { 39 if (currentPath.indexOf(letsRoamSavedSearchPrefillAllowlist[i]) !== -1) { 40 return true; 41 } 42 } 43 44 return false; 45 } 46 47 function shouldRestoreSavedSearchForContainer(container) { 48 if (container && container.getAttribute('data-search-modern-disable-saved-prefill') === '1') { 49 return false; 50 } 51 52 return shouldRestoreLetsRoamSavedSearchPrefill(); 53 } 54 55 function isCityOnlyDropdownMode() { 56 return !!window.LR_SEARCH_CITY_ONLY_DROPDOWN; 57 } 58 59 function escapeHtml(str) { 60 if (!str) return ''; 61 return String(str) 62 .replace(/&/g, '&') 63 .replace(/</g, '<') 64 .replace(/>/g, '>') 65 .replace(/"/g, '"'); 66 } 67 68 function highlightMatch(text, query) { 69 if (!text || !query) return escapeHtml(text); 70 var escaped = escapeHtml(text); 71 var words = query.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 1; }); 72 for (var wordIndex = 0; wordIndex < words.length; wordIndex++) { 73 var word = words[wordIndex]; 74 var regex = new RegExp('(' + word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi'); 75 escaped = escaped.replace(regex, '<strong>$1</strong>'); 76 } 77 return escaped; 78 } 79 80 function getCityDisplayName(city) { 81 var showState = city.state && ( 82 city.country === 'United States' 83 || city.country === 'Canada' 84 || city.country === 'Australia' 85 ); 86 return showState ? city.city + ', ' + city.state : (city.unique_city_name || city.city); 87 } 88 89 function localizeUrl(url) { 90 if (!url) { 91 return url; 92 } 93 94 function appendLocalTestingDomain(targetUrl) { 95 var hostname = window.location.hostname; 96 var shouldPreserveTestingParams = hostname.indexOf('.local.com') !== -1 97 || hostname.indexOf('.test-site.') !== -1 98 || hostname.indexOf('test-site.') === 0 99 || hostname.indexOf('.preview.letsroam.com') !== -1; 100 101 if (!targetUrl || !shouldPreserveTestingParams) { 102 return targetUrl; 103 } 104 105 try { 106 var currentUrl = new URL(window.location.href, window.location.origin); 107 var preservedParams = [ 108 'autoTest', 109 'ignore_tracking', 110 'testing_website_domain', 111 'utm_source', 112 'utm_medium', 113 'utm_campaign' 114 ]; 115 var hasPreservedParam = preservedParams.some(function (paramName) { 116 return currentUrl.searchParams.has(paramName); 117 }); 118 119 if (!hasPreservedParam) { 120 return targetUrl; 121 } 122 123 var resolvedUrl = new URL(targetUrl, window.location.origin);
124 preservedParams.forEach(function (paramName) { 125 if (!resolvedUrl.searchParams.has(paramName) && currentUrl.searchParams.has(paramName)) { 126 resolvedUrl.searchParams.set(paramName, currentUrl.searchParams.get(paramName)); 127 } 128 }); 129 130 if (/^https?:\/\//i.test(targetUrl)) { 131 return resolvedUrl.toString(); 132 } 133 134 return resolvedUrl.pathname + resolvedUrl.search + resolvedUrl.hash; 135 } catch (error) { 136 return targetUrl; 137 } 138 } 139 140 if (typeof window.base_url === 'function') { 141 var baseUrlResult = window.base_url(url); 142 if (baseUrlResult && baseUrlResult !== url) { 143 return appendLocalTestingDomain(baseUrlResult); 144 } 145 } 146 147 if (currentLanguage === 'en' || /^(mailto:|tel:|javascript:|#)/i.test(url)) { 148 return appendLocalTestingDomain(url); 149 } 150 151 if (url === '/' + currentLanguage || url.indexOf('/' + currentLanguage + '/') === 0) { 152 return appendLocalTestingDomain(url); 153 } 154 155 if (/^https?:\/\//i.test(url)) { 156 try { 157 var parsedUrl = new URL(url, window.location.origin); 158 if (parsedUrl.pathname !== '/' + currentLanguage && parsedUrl.pathname.indexOf('/' + currentLanguage + '/') !== 0) { 159 parsedUrl.pathname = '/' + currentLanguage + parsedUrl.pathname; 160 } 161 return appendLocalTestingDomain(parsedUrl.toString()); 162 } catch (error) { 163 return appendLocalTestingDomain(url); 164 } 165 } 166 167 if (url.charAt(0) === '/') { 168 return appendLocalTestingDomain('/' + currentLanguage + url); 169 } 170 171 return appendLocalTestingDomain(url); 172 } 173 174 function getSearchUrl(city) { 175 if (pageContext.huntOccasion && window.citySearchUtils) { 176 return localizeUrl(window.citySearchUtils.getOccasionSearchUrl(city)); 177 } 178 var path = window.location.pathname || ''; 179 var host = window.location.hostname || ''; 180 var forceThingsToDoRoutes = !!window.LR_USE_THINGS_TO_DO_ROUTES; 181 var isLetsRoamHost = host.indexOf('letsroam.') !== -1 || host.indexOf('letsroam.local') !== -1; 182 var isThingsToDoContext = path.indexOf('/things-to-do') === 0 || path.indexOf('/activities/find_city') === 0 || path.indexOf('/tours') === 0; 183 var useThingsToDoRoutes = forceThingsToDoRoutes || isLetsRoamHost || isThingsToDoContext; 184 var huntCitySlug = (city && (city.hunt_city_slug || city.slug || city.hunt_region_slug)) ? (city.hunt_city_slug || city.slug || city.hunt_region_slug) : ''; 185 186 if (pageContext.isBachelorette || pageContext.isBirthday || pageContext.isCorporate) { 187 var url = (useThingsToDoRoutes ? '/things-to-do/search?lat=' : '/outdoor-activities/search?latitude=') + encodeURIComponent(city.lat) + 188 (useThingsToDoRoutes ? '&long=' : '&longitude=') + encodeURIComponent(city.long) + 189 '&keyword=' + encodeURIComponent(city.city || city.unique_city_name || ''); 190 if (pageContext.isBachelorette) url += '&is_bach=true'; 191 else if (pageContext.isBirthday) url += '&is_birthday=true'; 192 else if (pageContext.isCorporate) url += '&is_corporate=true'; 193 return localizeUrl(url); 194 } 195 196 if (useThingsToDoRoutes) { 197 if (city.source === 'geocode' || !city.id || !huntCitySlug) { 198 return localizeUrl('/things-to-do/search?lat=' + encodeURIComponent(city.lat) + '&long=' + encodeURIComponent(city.long)); 199 } 200 return localizeUrl('/things-to-do/' + huntCitySlug); 201 } 202 203 if (city.source === 'geocode' || !city.id) { 204 return localizeUrl('/outdoor-activities/search?latitude=' + encodeURIComponent(city.lat) + '&longitude=' + encodeURIComponent(city.long)); 205 } 206 return localizeUrl('/outdoor-activities/' + huntCitySlug); 207 } 208 209 function fetchJsonWithStandardClient(url, requestOptions) { 210 if (typeof window.fetchWithParams === 'function') { 211 return window.fetchWithParams(url, {}, {}, 'GET').then(function(data) { 212 if (data && data.status === false) { 213 throw new Error(data.error || 'api_error'); 214 } 215 return data || {}; 216 }); 217 } 218 219 return fetch(url, requestOptions || {}) 220 .then(function(res) { 221 if (!res.ok) throw new Error('api_error'); 222 return res.json(); 223 }); 224 } 225 226 function showSearchButtonLoading(searchBtn) { 227 if (!searchBtn) { 228 return; 229 } 230 searchBtn.classList.add('mh-search-btn--loading'); 231 setTimeout(function() { 232 searchBtn.classList.remove('mh-search-btn--loading'); 233 }, 5000); 234 } 235 236 function hideLegacyContinueCta(element) { 237 if (!element) { 238 return; 239 } 240 element.classList.add('lr-search-modern-hidden-continue'); 241 element.setAttribute('aria-hidden', 'true'); 242 element.setAttribute('hidden', 'hidden'); 243 element.style.setProperty('display', 'none', 'important'); 244 element.style.setProperty('visibility', 'hidden', 'important');
245 element.style.setProperty('pointer-events', 'none', 'important'); 246 } 247 248 function updateClearButton(state) { 249 if (!state || !state.clearBtn || !state.input) { 250 return; 251 } 252 if (state.input.value.length > 0) { 253 state.clearBtn.classList.add('visible'); 254 } else { 255 state.clearBtn.classList.remove('visible'); 256 } 257 } 258 259 function showLoading(state) { 260 state.isLoading = true; 261 if (!state.dropdown) return; 262 var skeletonHtml = ''; 263 for (var i = 0; i < 4; i++) { 264 skeletonHtml += '<div class="mh-skeleton-item">' + 265 '<div class="mh-skeleton-icon"></div>' + 266 '<div class="mh-skeleton-text">' + 267 '<div class="mh-skeleton-line"></div>' + 268 '<div class="mh-skeleton-line short"></div>' + 269 '</div>' + 270 '<div class="mh-skeleton-line count"></div>' + 271 '</div>'; 272 } 273 state.dropdown.innerHTML = skeletonHtml; 274 openDropdown(state); 275 } 276 277 function showEngagementState(state, iconCount) { 278 if (!state || !state.dropdown) { 279 return; 280 } 281 var html = ''; 282 var iconSvg = '<svg class="mh-dropdown-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' + 283 '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>' + 284 '<circle cx="12" cy="10" r="3"></circle>' + 285 '</svg>'; 286 for (var i = 0; i < iconCount; i++) { 287 html += '<div class="mh-skeleton-item">' + 288 iconSvg + 289 '<div class="mh-skeleton-text">' + 290 '<div class="mh-skeleton-line"></div>' + 291 '<div class="mh-skeleton-line short"></div>' + 292 '</div>' + 293 '<div class="mh-skeleton-line count"></div>' + 294 '</div>'; 295 } 296 for (var j = iconCount; j < 4; j++) { 297 html += '<div class="mh-skeleton-item">' + 298 '<div class="mh-skeleton-icon"></div>' + 299 '<div class="mh-skeleton-text">' + 300 '<div class="mh-skeleton-line"></div>' + 301 '<div class="mh-skeleton-line short"></div>' + 302 '</div>' + 303 '</div>'; 304 } 305 state.dropdown.innerHTML = html; 306 } 307 308 function openDropdown(state) { 309 if (!state.dropdown) return; 310 state.dropdown.classList.add('open'); 311 state.isOpen = true; 312 if (window.citySearchUtils && typeof window.citySearchUtils.updateScrollFade === 'function') { 313 setTimeout(function() { window.citySearchUtils.updateScrollFade(state.dropdown); }, 50); 314 } 315 } 316 317 function closeDropdown(state) { 318 if (!state.dropdown) return; 319 state.dropdown.classList.remove('open'); 320 state.isOpen = false; 321 state.isLoading = false; 322 state.highlightedIndex = -1; 323 if (window.citySearchUtils && typeof window.citySearchUtils.clearScrollFade === 'function') { 324 window.citySearchUtils.clearScrollFade(state.dropdown); 325 } 326 } 327 328 function applyTopHuntsData(state, city, index, data) { 329 // The query can change before this lazy request finishes. 330 if (state.cities[index] !== city) return; 331 var hunts = data.hunts || []; 332 var nearbyCount = parseInt(data.nearby_hunts) || 0; 333 var activityCount = parseInt(city.activities_in_city) || 0; 334 335 var container = state.dropdown.querySelector('.mh-dropdown-hunts-container[data-city-index="' + index + '"]'); 336 if (container) container.innerHTML = ''; 337 338 state.cities[index].top_hunts = hunts; 339 state.cities[index].nearby_hunts = nearbyCount; 340 341 var countEl = state.dropdown.querySelector('.mh-dropdown-item[data-index="' + index + '"] .mh-dropdown-item-count'); 342 if (countEl) { 343 var badgeEl = countEl.querySelector('.mh-count-badge'); 344 var nearbyEl = countEl.querySelector('.mh-nearby-text'); 345 if (activityCount === 0 && nearbyCount > 0) { 346 if (!badgeEl) { 347 countEl.insertAdjacentHTML('afterbegin', '<span class="mh-count-badge">' + nearbyCount + ' nearby</span>'); 348 } else { 349 badgeEl.textContent = nearbyCount + ' ' + translate('nearby'); 350 } 351 if (nearbyEl) nearbyEl.textContent = ''; 352 } else if (activityCount > 0 && nearbyCount > activityCount && nearbyEl) { 353 nearbyEl.textContent = '(' + nearbyCount + ' ' + translate('nearby') + ')'; 354 } 355 } 356 357 if (!container || hunts.length === 0) return; 358 359 var searchQuery = state.input.value || ''; 360 container.innerHTML = hunts.map(function(hunt, huntIndex) { 361 var isInCity = parseInt(hunt.is_in_city) === 1; 362 var showBadge = huntIndex === 0 && isInCity && activityCount >= 1; 363 var badge = showBadge ? '<span class="mh-top-hunt-badge">' + translate('Top Hunt') + '</span>' : ''; 364 var distance = parseFloat(hunt.distance_miles) || 0; 365 var distanceText = (!isInCity && distance > 0) ? '<span class="mh-hunt-distance">
365' + distance.toFixed(1) + ' mi</span>' : ''; 366 367 return '<div class="mh-dropdown-hunt" data-hunt-url="' + escapeHtml(hunt.hunt_activity_url || '') + '" data-city-index="' + index + '">' + 368 '<span class="mh-dropdown-hunt-name">' + highlightMatch(hunt.name || '', searchQuery) + '</span>' + 369 '<span class="mh-hunt-meta">' + distanceText + badge + '</span>' + 370 '</div>'; 371 }).join(''); 372 373 var huntItems = container.querySelectorAll('.mh-dropdown-hunt'); 374 for (var huntIndex = 0; huntIndex < huntItems.length; huntIndex++) { 375 (function(item) { 376 item.addEventListener('touchend', function(e) { 377 handleHuntClick(state, item, e); 378 }); 379 item.addEventListener('mousedown', function(e) { 380 handleHuntClick(state, item, e); 381 }); 382 })(huntItems[huntIndex]); 383 } 384 } 385 386 function lazyLoadTopHunts(state) { 387 if (pageContext.huntOccasion || pageContext.isBachelorette || pageContext.isBirthday || pageContext.isCorporate) { 388 var firstCities = state.cities.slice(0, 5); 389 for (var idx = 0; idx < firstCities.length; idx++) { 390 var city = firstCities[idx]; 391 var container = state.dropdown.querySelector('.mh-dropdown-hunts-container[data-city-index="' + idx + '"]'); 392 if (container) container.innerHTML = ''; 393 } 394 return; 395 } 396 397 var topCities = state.cities.slice(0, 5); 398 for (var index = 0; index < topCities.length; index++) { 399 (function(city, cityIndex) { 400 if ((!city.lat || !city.long) && !city.id) { 401 return; 402 } 403 404 if (state.cities[cityIndex] && state.cities[cityIndex].top_hunts && state.cities[cityIndex].top_hunts.length) { 405 applyTopHuntsData(state, city, cityIndex, { hunts: state.cities[cityIndex].top_hunts, nearby_hunts: state.cities[cityIndex].nearby_hunts || 0 }); 406 return; 407 } 408 409 var latRounded = Math.round(parseFloat(city.lat) * 100) / 100; 410 var lngRounded = Math.round(parseFloat(city.long) * 100) / 100; 411 var cacheKey = 'mh_top_hunts_v10_' + currentLanguage + '_' + (city.id || '') + '_' + latRounded + '_' + lngRounded + '_3_30'; 412 try { 413 var cached = localStorage.getItem(cacheKey); 414 if (cached) { 415 var parsed = JSON.parse(cached); 416 if (parsed && parsed.expires > Date.now() && parsed.data) { 417 applyTopHuntsData(state, city, cityIndex, parsed.data); 418 return; 419 } 420 } 421 } catch (e) {} 422 423 var topHuntsUrl = city.id 424 ? ('/api/city/top_hunts?city_id=' + encodeURIComponent(city.id) + '&limit=3&radius=30&lang=' + currentLanguage) 425 : ('/api/city/top_hunts?lat=' + city.lat + '&lng=' + city.long + '&limit=3&radius=30&lang=' + currentLanguage); 426 427 fetchJsonWithStandardClient(topHuntsUrl) 428 .then(function(data) { 429 applyTopHuntsData(state, city, cityIndex, data || {}); 430 try { 431 localStorage.setItem(cacheKey, JSON.stringify({ 432 data: data || {}, 433 expires: Date.now() + (24 * 60 * 60 * 1000) 434 })); 435 } catch (e) {} 436 }) 437 .catch(function() { 438 applyTopHuntsData(state, city, cityIndex, {}); 439 }); 440 })(topCities[index], index); 441 } 442 } 443 444 function setSearchResults(state, data) { 445 state.cities = data.cities || []; 446 state.highlightedIndex = -1; 447 state.recoveryType = data.recovery_type || (state.cities[0] && state.cities[0].recovery_type) || ''; 448 var recoveryLabels = { 449 nearby_hunts: 'Nearby hunts', 450 closest_supported: 'Closest supported cities', 451 popular_alternatives: 'Popular alternatives' 452 }; 453 state.recoveryLabel = data.recovery_label || recoveryLabels[state.recoveryType] || ''; 454 } 455 456 function cacheSearchResults(state, cacheKey, suggestions) { 457 try { 458 localStorage.setItem(cacheKey, JSON.stringify({ 459 cities: state.cities, 460 recovery_type: state.recoveryType, 461 recovery_label: state.recoveryLabel, 462 did_you_mean: suggestions, 463 expires: Date.now() + (60 * 60 * 1000) 464 })); 465 } catch (e) {} 466 } 467
468 function renderSpellingSuggestions(state, suggestions) { 469 if (!suggestions || !suggestions.length || !window.citySearchUtils || typeof window.citySearchUtils.renderDidYouMean !== 'function') { 470 return false; 471 } 472 window.citySearchUtils.renderDidYouMean(suggestions, state.dropdown, function() { openDropdown(state); }, state.input, function(newQuery) { 473 fetchCities(state, newQuery, false); 474 }); 475 return true; 476 } 477 478 function renderDropdown(state) { 479 if (!state.cities.length) { 480 state.dropdown.innerHTML = '<div class="mh-dropdown-empty">No cities found</div>'; 481 openDropdown(state); 482 return; 483 } 484 485 var searchQuery = state.input.value || ''; 486 var recoveryHeading = state.recoveryLabel 487 ? '<div class="mh-recovery-heading h-padding__v--10 h-padding__h--15 h-color--gray3 h-size--13 h-weight--700">' 488 + escapeHtml(translate(state.recoveryLabel)) + '</div>' 489 : ''; 490 state.dropdown.innerHTML = recoveryHeading + state.cities.map(function(city, index) { 491 var displayName = getCityDisplayName(city); 492 var activityCount = parseInt(city.activities_in_city) || 0; 493 var countBadge = activityCount > 0 ? '<span class="mh-count-badge">' + activityCount + ' ' + translate(activityCount !== 1 ? 'hunts' : 'hunt') + '</span>' : ''; 494 495 var cityRow = '<div class="mh-dropdown-item' + (index === state.highlightedIndex ? ' highlighted' : '') + '" data-index="' + index + '">' + 496 '<svg class="mh-dropdown-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' + 497 '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>' + 498 '<circle cx="12" cy="10" r="3"></circle>' + 499 '</svg>' + 500 '<div class="mh-dropdown-item-text">' + 501 '<div class="mh-dropdown-item-city">' + highlightMatch(displayName, searchQuery) + '</div>' + 502 '<div class="mh-dropdown-item-meta">' + highlightMatch(city.country || '', searchQuery) + '</div>' + 503 '</div>' + 504 '<div class="mh-dropdown-item-right">' + 505 '<span class="mh-dropdown-item-count">' + countBadge + '<span class="mh-nearby-text"></span></span>' + 506 '<svg class="mh-dropdown-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>' + 507 '</div>' + 508 '</div>'; 509 510 if (isCityOnlyDropdownMode()) { 511 return cityRow; 512 } 513 514 var huntsSkeleton = index < 5 515 ? '<div class="mh-skeleton-hunt"><div class="mh-skeleton-line" style="width:70%"></div></div><div class="mh-skeleton-hunt"><div class="mh-skeleton-line" style="width:55%"></div></div>' 516 : ''; 517 return cityRow + '<div class="mh-dropdown-hunts-container" data-city-index="' + index + '">' + huntsSkeleton + '</div>'; 518 }).join(''); 519 520 var dropdownItems = state.dropdown.querySelectorAll('.mh-dropdown-item'); 521 for (var dropdownIndex = 0; dropdownIndex < dropdownItems.length; dropdownIndex++) { 522 (function(item) { 523 var handleSelect = function(e) { 524 if (e.type === 'touchend' && state.scrolledDuringTouch) { 525 return; 526 } 527 e.preventDefault(); 528 var index = parseInt(item.dataset.index); 529 if (!state.cities[index]) return; 530 item.style.background = '#e8e8e8'; 531 closeDropdown(state); 532 selectCity(state, state.cities[index]); 533 }; 534 item.addEventListener('touchend', handleSelect); 535 item.addEventListener('mousedown', handleSelect); 536 })(dropdownItems[dropdownIndex]); 537 } 538 539 openDropdown(state); 540 if (!isCityOnlyDropdownMode()) { 541 lazyLoadTopHunts(state); 542 } 543 } 544 545 function handleHuntClick(state, item, event) { 546 if (!state || !item || !event) { 547 return; 548 } 549 if (event.type === 'touchend' && state.scrolledDuringTouch) { 550 return; 551 } 552 event.preventDefault(); 553 event.stopPropagation(); 554 var huntUrl = item.dataset.huntUrl; 555 var cityIndex = parseInt(item.dataset.cityIndex, 10); 556 if (!huntUrl) { 557 return; 558 } 559 560 item.style.background = '#e0e0e0'; 561 closeDropdown(state); 562 showSearchButtonLoading(state.searchBtn); 563 564 if (!isNaN(cityIndex) && state.cities[cityIndex]) { 565 var city = state.cities[cityIndex]; 566 var displayName = getCityDisplayName(city); 567 state.input.value = displayName; 568 updateClearButton(state);
569 try { 570 localStorage.setItem('sch_header_search_label', displayName); 571 } catch (e) {} 572 } 573 574 window.location.href = localizeUrl(huntUrl); 575 } 576 577 function selectCity(state, city) { 578 var displayName = getCityDisplayName(city); 579 state.input.value = displayName; 580 updateClearButton(state); 581 try { localStorage.setItem('sch_header_search_label', displayName); } catch (e) {} 582 closeDropdown(state); 583 showSearchButtonLoading(state.searchBtn); 584 window.location.href = getSearchUrl(city); 585 } 586 587 function fetchFromGeocode(state, query, cacheKey, didYouMeanSuggestions, selectFirstOnLoad) { 588 showLoading(state); 589 fetchJsonWithStandardClient('/api/city/search_geocode?q=' + encodeURIComponent(query) + '&limit=5&lang=' + currentLanguage + smartCitySearchParam) 590 .then(function(data) { 591 if (query !== state.input.value.trim()) return; 592 state.isLoading = false; 593 setSearchResults(state, data); 594 if (state.recoveryType === 'popular_alternatives' && renderSpellingSuggestions(state, didYouMeanSuggestions)) { 595 setSearchResults(state, {}); 596 cacheSearchResults(state, cacheKey, didYouMeanSuggestions); 597 return; 598 } 599 if (state.cities.length > 0) { 600 renderDropdown(state); 601 if (selectFirstOnLoad && !state.recoveryType) selectCity(state, state.cities[0]); 602 cacheSearchResults(state, cacheKey); 603 return; 604 } 605 if (renderSpellingSuggestions(state, didYouMeanSuggestions)) return; 606 state.dropdown.innerHTML = '<div class="mh-dropdown-empty">No cities found. Please check your spelling or try a different search.</div>'; 607 openDropdown(state); 608 }) 609 .catch(function() { 610 if (query !== state.input.value.trim()) return; 611 state.isLoading = false; 612 setSearchResults(state, {}); 613 if (renderSpellingSuggestions(state, didYouMeanSuggestions)) return; 614 state.dropdown.innerHTML = '<div class="mh-dropdown-empty">No cities found. Please check your spelling or try a different search.</div>'; 615 openDropdown(state); 616 }); 617 } 618 619 function fetchCities(state, query, selectFirstOnLoad) { 620 setSearchResults(state, {}); 621 var cacheNamespace = 'city_search_v7_1'; 622 if (smartCitySearchEnabled) { 623 // Smart results can depend on the visitor's country. Keep this component's 624 // cache separate from older results and the navbar's recovery metadata. 625 cacheNamespace = 'city_search_modern_v2_' + searchModernConfig.websiteDomain + '_' + (searchModernConfig.visitorCountryCode || ''); 626 } 627 var cacheKey = cacheNamespace + '_' + currentLanguage + '_' + query.toLowerCase(); 628 try { 629 var cached = localStorage.getItem(cacheKey); 630 if (cached) { 631 var parsed = JSON.parse(cached); 632 if (parsed.expires > Date.now()) { 633 state.isLoading = false; 634 setSearchResults(state, parsed); 635 if (state.cities.length === 0 && renderSpellingSuggestions(state, parsed.did_you_mean)) return; 636 renderDropdown(state); 637 if (selectFirstOnLoad && state.cities.length > 0 && !state.recoveryType) selectCity(state, state.cities[0]); 638 return; 639 } 640 } 641 } catch (e) {} 642 643 var requestToken = String(Date.now()) + '-' + Math.random(); 644 state.currentRequest = requestToken; 645 var dataLoaded = false; 646 var engagementTimers = []; 647 648 engagementTimers.push(setTimeout(function() { 649 if (!dataLoaded && state.isLoading) { 650 showEngagementState(state, 1); 651 } 652 }, 1300)); 653 654 engagementTimers.push(setTimeout(function() { 655 if (!dataLoaded && state.isLoading) { 656 showEngagementState(state, 2); 657 } 658 }, 2000)); 659 660 engagementTimers.push(setTimeout(function() { 661 if (!dataLoaded && state.isLoading) { 662 showEngagementState(state, 3); 663 } 664 }, 3000)); 665 666 fetchJsonWithStandardClient('/api/city/search_autocomplete?q=' + encodeURIComponent(query) + '&limit=8&lang=' + currentLanguage + smartCitySearchParam) 667 .then(function(data) { 668 dataLoaded = true; 669 for (var timerIndex = 0; timerIndex < engagementTimers.length; timerIndex++) { 670 clearTimeout(engagementTimers[timerIndex]); 671 } 672 if (state.currentRequest !== requestToken) return; 673 if (query !== state.input.value.trim()) return; 674 state.isLoading = false;
675 setSearchResults(state, data); 676 if (state.cities.length === 0) { 677 fetchFromGeocode(state, query, cacheKey, data.did_you_mean, selectFirstOnLoad); 678 return; 679 } 680 681 renderDropdown(state); 682 if (selectFirstOnLoad && !state.recoveryType) selectCity(state, state.cities[0]); 683 cacheSearchResults(state, cacheKey); 684 }) 685 .catch(function(err) { 686 dataLoaded = true; 687 for (var timerIndex = 0; timerIndex < engagementTimers.length; timerIndex++) { 688 clearTimeout(engagementTimers[timerIndex]); 689 } 690 if (state.currentRequest !== requestToken) return; 691 state.isLoading = false; 692 setSearchResults(state, {}); 693 state.dropdown.innerHTML = '<div class="mh-dropdown-empty">Search unavailable</div>'; 694 openDropdown(state); 695 }); 696 } 697 698 function mount(config) { 699 if (!config || !config.input || !config.dropdown) return null; 700 var container = config.container || (config.input.closest ? config.input.closest('.mh-search-container') : null); 701 var state = { 702 input: config.input, 703 dropdown: config.dropdown, 704 searchBtn: config.searchBtn || null, 705 clearBtn: config.clearBtn || null, 706 container: container, 707 cities: [], 708 highlightedIndex: -1, 709 recoveryType: '', 710 recoveryLabel: '', 711 isOpen: false, 712 isLoading: false, 713 debounceTimer: null, 714 currentRequest: null, 715 scrolledDuringTouch: false 716 }; 717 718 state.input.addEventListener('input', function() { 719 updateClearButton(state); 720 }); 721 722 state.input.addEventListener('input', function() { 723 var query = state.input.value.trim(); 724 setSearchResults(state, {}); 725 if (query.length < 2) { 726 state.isLoading = false; 727 closeDropdown(state); 728 return; 729 } 730 showLoading(state); 731 clearTimeout(state.debounceTimer); 732 state.debounceTimer = setTimeout(function() { 733 fetchCities(state, query, false); 734 }, 300); 735 }); 736 737 state.input.addEventListener('focus', function() { 738 var query = state.input.value.trim(); 739 if (state.cities.length > 0 && query.length >= 2) { 740 openDropdown(state); 741 return; 742 } 743 if (query.length >= 2) { 744 showLoading(state); 745 fetchCities(state, query, false); 746 } 747 }); 748 749 state.input.addEventListener('blur', function() { 750 if (window.innerWidth < 1000 && state.cities.length > 0) return; 751 setTimeout(function() { closeDropdown(state); }, 200); 752 }); 753 754 state.input.addEventListener('keydown', function(e) { 755 if (!state.isOpen) { 756 if (e.key === 'Enter' && state.input.value.trim().length >= 2) { 757 fetchCities(state, state.input.value.trim(), false); 758 } 759 return; 760 } 761 762 if (e.key === 'ArrowDown') { 763 e.preventDefault(); 764 state.highlightedIndex = Math.min(state.highlightedIndex + 1, state.cities.length - 1); 765 renderDropdown(state); 766 } else if (e.key === 'ArrowUp') { 767 e.preventDefault(); 768 state.highlightedIndex = Math.max(state.highlightedIndex - 1, 0); 769 renderDropdown(state); 770 } else if (e.key === 'Enter') { 771 e.preventDefault(); 772 if (state.highlightedIndex >= 0 && state.cities[state.highlightedIndex]) { 773 selectCity(state, state.cities[state.highlightedIndex]); 774 } else if (state.cities.length > 0 && !state.recoveryType) { 775 selectCity(state, state.cities[0]); 776 } 777 } else if (e.key === 'Escape') { 778 closeDropdown(state); 779 } 780 }); 781 782 state.dropdown.addEventListener('touchstart', function() { 783 state.scrolledDuringTouch = false; 784 }, { passive: true }); 785 786 state.dropdown.addEventListener('touchmove', function() { 787 state.scrolledDuringTouch = true; 788 if (document.activeElement === state.input) { 789 state.input.blur(); 790 } 791 }, { passive: true }); 792 793 state.dropdown.addEventListener('scroll', function() { 794 state.scrolledDuringTouch = true; 795 if (window.citySearchUtils && typeof window.citySearchUtils.updateScrollFade === 'function') { 796 window.citySearchUtils.updateScrollFade(state.dropdown); 797 } 798 }); 799 800 document.addEventListener('click', function(event) { 801 if (!state.isOpen) { 802 return; 803 } 804 if (window.innerWidth >= 1000) { 805 return; 806 } 807 var searchContainer = state.container; 808 if (searchContainer && !searchContainer.contains(event.target)) { 809 closeDropdown(state); 810 } 811 }); 812 813 if (state.clearBtn) { 814 state.clearBtn.addEventListener('click', function(e) { 815 e.preventDefault(); 816 state.input.value = ''; 817 state.isLoading = false;
818 setSearchResults(state, {}); 819 updateClearButton(state); 820 closeDropdown(state); 821 state.input.focus(); 822 try { 823 localStorage.removeItem('sch_header_search_label'); 824 localStorage.removeItem('sch_header_search_city_id'); 825 } catch (err) {} 826 }); 827 } 828 829 if (state.searchBtn) { 830 state.searchBtn.addEventListener('mousedown', function(e) { 831 if (state.isOpen) { 832 e.preventDefault(); 833 e.stopPropagation(); 834 } 835 }); 836 837 state.searchBtn.addEventListener('click', function(e) { 838 e.preventDefault(); 839 e.stopPropagation(); 840 if (state.cities.length > 0) { 841 if (state.recoveryType && state.highlightedIndex < 0) { 842 openDropdown(state); 843 return; 844 } 845 var idx = state.highlightedIndex >= 0 ? state.highlightedIndex : 0; 846 selectCity(state, state.cities[idx]); 847 return; 848 } 849 if (state.isLoading) { 850 return; 851 } 852 var query = state.input.value.trim(); 853 if (query.length >= 2) { 854 fetchCities(state, query, true); 855 } else { 856 state.input.focus(); 857 } 858 }); 859 } 860 861 try { 862 var savedSearchLabel = localStorage.getItem('sch_header_search_label'); 863 if (savedSearchLabel && shouldRestoreSavedSearchForContainer(state.container)) { 864 state.input.value = savedSearchLabel; 865 } 866 } catch (e) {} 867 updateClearButton(state); 868 869 return state; 870 } 871 872 function mountContainer(container) { 873 if (!container || container.getAttribute('data-search-modern-mounted') === '1') { 874 return null; 875 } 876 877 var input = container.querySelector('[data-search-modern-input], input[type="text"], input[type="search"]'); 878 if (!input) { 879 return null; 880 } 881 882 var dropdown = container.querySelector('.mh-dropdown'); 883 if (!dropdown) { 884 dropdown = document.createElement('div'); 885 dropdown.className = 'mh-dropdown lr-amazon-inline-search__dropdown'; 886 container.appendChild(dropdown); 887 } 888 889 var clearBtn = container.querySelector('.mh-clear-btn'); 890 if (!clearBtn) { 891 clearBtn = document.createElement('button'); 892 clearBtn.type = 'button'; 893 clearBtn.className = 'mh-clear-btn'; 894 clearBtn.setAttribute('aria-label', 'Clear search'); 895 clearBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>'; 896 container.appendChild(clearBtn); 897 } 898 899 var searchBtn = container.querySelector('.mh-search-btn'); 900 if (!searchBtn && container.getAttribute('data-search-modern-button') !== 'none') { 901 searchBtn = document.createElement('button'); 902 searchBtn.type = 'button'; 903 searchBtn.className = 'mh-search-btn'; 904 searchBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5"><circle cx="10" cy="10" r="7"></circle><line x1="20" y1="20" x2="15" y2="15"></line></svg>'; 905 container.appendChild(searchBtn); 906 } 907 908 container.classList.add('mh-search-container'); 909 input.classList.add('mh-city-input'); 910 input.setAttribute('autocomplete', 'off'); 911 input.setAttribute('spellcheck', 'false'); 912 913 container.setAttribute('data-search-modern-mounted', '1'); 914 return mount({ 915 container: container, 916 input: input, 917 dropdown: dropdown, 918 clearBtn: clearBtn, 919 searchBtn: searchBtn 920 }); 921 } 922 923 function enhanceGeAutocomplete(geInput, options) { 924 if (!geInput || geInput.getAttribute('data-search-modern-enhanced') === '1') return null; 925 geInput.setAttribute('data-search-modern-enhanced', '1'); 926 options = options || {}; 927 928 var geId = geInput.getAttribute('id') || ''; 929 var placeholder = geInput.getAttribute('placeholder') || 'Enter a city to explore'; 930 var input = null; 931 var dropdown = null; 932 var searchBtn = null; 933 var clearBtn = null; 934 var componentContainer = null; 935 936 if (options.reuseInputSelector) { 937 input = document.querySelector(options.reuseInputSelector); 938 if (!input) return null; 939 componentContainer = input.closest ? input.closest('.mh-search-container') : input.parentNode; 940 if (!componentContainer) { 941 componentContainer = input.parentNode; 942 } 943 if (componentContainer && componentContainer.classList) { 944 componentContainer.classList.add('mh-search-container'); 945 } 946 input.classList.add('mh-city-input'); 947 input.style.fontFamily = '"Plus Jakarta Sans", Arial, sans-serif'; 948 dropdown = document.createElement('div'); 949 dropdown.className = 'mh-dropdown lr-amazon-inline-search__dropdown'; 950 input.parentNode.appendChild(dropdown); 951 if (options.clearSelector) { 952 clearBtn = document.querySelector(options.clearSelector); 953 } 954 if (options.searchButtonSelector) { 955 searchBtn = document.querySelector(options.searchButtonSelector); 956 } 957 } else { 958 var wrapper = document.createElement('div'); 959 wrapper.className = 'lr-amazon-inline-search mh-search-container'; 960 componentContainer = wrapper; 961 input = document.createElement('input'); 962 input.type = 'text'; 963 input.className = 'mh-city-input lr-amazon-inline-search__input'; 964 input.placeholder = placeholder; 965 input.autocomplete = 'off'; 966 input.spellcheck = false;
967 input.style.fontFamily = '"Plus Jakarta Sans", Arial, sans-serif'; 968 969 clearBtn = document.createElement('button'); 970 clearBtn.type = 'button'; 971 clearBtn.className = 'mh-clear-btn'; 972 clearBtn.setAttribute('aria-label', 'Clear search'); 973 clearBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>'; 974 975 dropdown = document.createElement('div'); 976 dropdown.className = 'mh-dropdown lr-amazon-inline-search__dropdown'; 977 978 searchBtn = document.createElement('button'); 979 searchBtn.type = 'button'; 980 searchBtn.className = 'mh-search-btn'; 981 searchBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5"><circle cx="10" cy="10" r="7"></circle><line x1="20" y1="20" x2="15" y2="15"></line></svg>'; 982 983 wrapper.appendChild(input); 984 wrapper.appendChild(clearBtn); 985 wrapper.appendChild(dropdown); 986 wrapper.appendChild(searchBtn); 987 geInput.parentNode.insertBefore(wrapper, geInput); 988 } 989 990 geInput.style.display = 'none'; 991 geInput.setAttribute('aria-hidden', 'true'); 992 993 var mountedState = mount({ 994 container: componentContainer, 995 input: input, 996 dropdown: dropdown, 997 clearBtn: clearBtn, 998 searchBtn: searchBtn 999 }); 1000 1001 if (options.bindLegacyButtonsByGeId !== false && geId && mountedState) { 1002 var legacyTriggers = document.querySelectorAll('button[onclick*="' + geId + '"], a[onclick*="' + geId + '"], [role="button"][onclick*="' + geId + '"]'); 1003 for (var buttonIndex = 0; buttonIndex < legacyTriggers.length; buttonIndex++) { 1004 var trigger = legacyTriggers[buttonIndex]; 1005 var triggerLabel = (trigger.textContent || '').toLowerCase(); 1006 if (options.hideLegacyContinueButtons && triggerLabel.indexOf('continue') !== -1) { 1007 hideLegacyContinueCta(trigger); 1008 continue; 1009 } 1010 trigger.removeAttribute('onclick'); 1011 trigger.addEventListener('click', function(e) { 1012 e.preventDefault(); 1013 e.stopPropagation(); 1014 if (mountedState.cities.length > 0) { 1015 if (mountedState.recoveryType && mountedState.highlightedIndex < 0) { 1016 openDropdown(mountedState); 1017 return; 1018 } 1019 var cityIndex = mountedState.highlightedIndex >= 0 ? mountedState.highlightedIndex : 0; 1020 selectCity(mountedState, mountedState.cities[cityIndex]); 1021 return; 1022 } 1023 var query = mountedState.input.value.trim(); 1024 if (query.length >= 2) { 1025 showLoading(mountedState); 1026 fetchCities(mountedState, query, true); 1027 return; 1028 } 1029 mountedState.input.focus(); 1030 }); 1031 } 1032 1033 if (options.hideLegacyContinueButtons && geInput.parentNode) { 1034 var localSearchActions = geInput.parentNode.querySelectorAll('button, a, [role="button"]'); 1035 for (var actionIndex = 0; actionIndex < localSearchActions.length; actionIndex++) { 1036 var actionEl = localSearchActions[actionIndex]; 1037 var actionLabel = (actionEl.textContent || '').toLowerCase(); 1038 var isContinueAction = actionLabel.indexOf('continue') !== -1; 1039 var isSearchAction = actionEl.classList && actionEl.classList.contains('lr-btn-algolia-submit'); 1040 if (isContinueAction && isSearchAction) { 1041 hideLegacyContinueCta(actionEl); 1042 } 1043 } 1044 } 1045 1046 if (options.hideLegacyContinueButtons) { 1047 var knownContinueSelectors = [ 1048 '#main_anything_where_people_g_o', 1049 '#main_anything_where_people_g_o-button', 1050 '#second_anything_where_people_g_o', 1051 '#second_anything_where_people_g_o-button' 1052 ]; 1053 for (var knownIndex = 0; knownIndex < knownContinueSelectors.length; knownIndex++) { 1054 var knownBtn = document.querySelector(knownContinueSelectors[knownIndex]); 1055 if (knownBtn) { 1056 hideLegacyContinueCta(knownBtn); 1057 } 1058 } 1059 } 1060 } 1061 1062 return mountedState; 1063 } 1064 1065 function enhanceAllGeAutocomplete(selector, optionsFactory) { 1066 var geInputs = document.querySelectorAll(selector || 'ge-autocomplete'); 1067 for (var geIndex = 0; geIndex < geInputs.length; geIndex++) { 1068 var geInput = geInputs[geIndex]; 1069 var options = (typeof optionsFactory === 'function') ? optionsFactory(geInput) : {}; 1070 if (options === false) { 1071 continue; 1072 } 1073 enhanceGeAutocomplete(geInput, options || {}); 1074 } 1075 } 1076 1077 function mountAllContainers(selector) { 1078 var containers = document.querySelectorAll(selector || '.search_modern'); 1079 for (var containerIndex = 0; containerIndex < containers.length; containerIndex++) { 1080 mountContainer(containers[containerIndex]); 1081 } 1082 } 1083 1084 function autoInitContainers() { 1085 mountAllContainers('.search_modern'); 1086 } 1087 1088 function startAutoMounting() { 1089 autoInitContainers(); 1090 if (typeof MutationObserver !== 'undefined' && document.body) { 1091 var searchModernObserver = new MutationObserver(function(mutations) { 1092 for (var i = 0; i < mutations.length; i++) { 1093 if (!mutations[i].addedNodes || !mutations[i].addedNodes.length) { 1094 continue; 1095 } 1096 autoInitContainers(); 1097 break; 1098 } 1099 }); 1100 searchModernObserver.observe(document.body, { childList: true, subtree: true }); 1101 } 1102 } 1103 1104 // Wait for complete template markup before creating any missing controls. 1105 if (document.readyState === 'loading') { 1106 document.addEventListener('DOMContentLoaded', startAutoMounting); 1107 } else { 1108 startAutoMounting(); 1109 } 1110 1111 window.SearchModern = { 1112 mount: mount, 1113 mountContainer: mountContainer, 1114 mountAllContainers: mountAllContainers, 1115 enhanceGeAutocomplete: enhanceGeAutocomplete, 1116 enhanceAllGeAutocomplete: enhanceAllGeAutocomplete 1117 }; 1118})();
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.