1(function(){'use strict';var DATA_SCRIPT_ID='tp-hotels-data';var FALLBACK_URL='/wp-content/plugins/travel-planner/js/hotels.json';var HOTELS=[];var firstLoad=true;var RATING_SCALE=10;var AMADEUS_OFFERS={};var PRICE_TIERS=[{key:'Budget',label:'Budget (⤠CHF 150)'},{key:'Mid',label:'Mid (CHF 150â250)'},{key:'Premium',label:'Premium (CHF 250â400)'},{key:'Luxury',label:'Luxury (CHF 400+)'}];var TYPE_OPTIONS=['Hotel','Apartment','Hostel','Camping'];var PLACEHOLDER_IMG='data:image/svg+xml;utf8,'+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600"><rect width="100%" height="100%" fill="#eaeaea"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#888" font-family="sans-serif" font-size="28">Photo</text></svg>');function num(v){if(v===null||v===undefined)return null;var n=parseFloat(String(v).replace(',','.'));return isNaN(n)?null:n;} 2function splitFacilities(s){if(!s)return[];return String(s).split(/[,;|]+/).map(function(x){return x.trim();}).filter(Boolean);} 3function uniq(arr){return Array.from(new Set(arr));} 4function toTitleCase(s){return String(s||'').toLowerCase().replace(/\b\w/g,function(m){return m.toUpperCase();});} 5function tierFromStars(stars){var s=num(stars);if(s===null)return null;if(s>=4.5)return'Luxury';if(s>=4.0)return'Premium';if(s>=3.0)return'Mid';return'Budget';} 6function normalizeScraped(rows){var out=[];for(var i=0;i<rows.length;i++){var d=rows[i]||{};var name=d.name||'(Unnamed)';var city=d.town||'';var rating=num(d.rating);var stars=num(d.stars);var type=toTitleCase(d.type||'Hotel');out.push({name:name,city:city,rating:rating,stars:stars,type:type,features:splitFacilities(d.facilities),image:d.image||'',url:d.url||'',tier:tierFromStars(stars)||'',approxPrice:''});} 7return out;} 8function collectAllPlaces(){return uniq(HOTELS.map(function(h){return h.city;}).filter(Boolean)).sort();} 9function collectAllFeatures(){var all=[];for(var i=0;i<HOTELS.length;i++){var f=HOTELS[i].features||[];for(var j=0;j<f.length;j++){if(f[j])all.push(f[j]);}} 10return uniq(all).sort();} 11function collectTypesFromData(){var all=[];for(var i=0;i<HOTELS.length;i++){if(HOTELS[i].type)all.push(HOTELS[i].type);} 12var present=uniq(all).sort();var merged=uniq(present.concat(TYPE_OPTIONS));return merged.filter(function(t){return present.indexOf(t)!==-1;});} 13function detectRatingScale(){var maxSeen=0;for(var i=0;i<HOTELS.length;i++){var r=HOTELS[i].rating;if(typeof r==='number'&&isFinite(r)){if(r>maxSeen)maxSeen=r;}} 14RATING_SCALE=(maxSeen>5.0)?10:5;} 15function renderCheckboxList($wrap,items,groupPrefix,checkedDefault){$wrap.empty();for(var i=0;i<items.length;i++){var it=items[i];var value=(typeof it==='string')?it:it.key;var label=(typeof it==='string')?it:it.label;var id=groupPrefix+'-'+String(value).toLowerCase().replace(/\s+/g,'-');var $chk=jQuery('<input/>',{type:'checkbox',id:id,value:value,checked:!!checkedDefault});var $lbl=jQuery('<label/>',{for:id}).append($chk).append(document.createTextNode(' '+label));$wrap.append($lbl);}} 16function renderTypeSelect($sel,options,defaultVal){$sel.empty();options.forEach(function(opt){var sel=(opt===defaultVal)?' selected':'';$sel.append('<option value="'+opt+'"'+sel+'>'+opt+'</option>');});} 17function getSelectedValues(selector){var out=[];jQuery(selector+' input[type=checkbox]').each(function(){if(this.checked)out.push(this.value);});return out;} 18function setAll(selector,checked){jQuery(selector+' input[type=checkbox]').prop('checked',checked).trigger('change');} 19function classStars(stars){var n=Number.isFinite(stars)?stars:parseInt(stars,10);if(!Number.isFinite(n))n=0;n=Math.max(0,Math.min(5,Math.floor(n)));var out='';for(var i=0;i<n;i++)out+='â ';for(var j=n;j<5;j++)out+='â';return out;} 20function ratingString(r,scale){var n=(r===0||r)?parseFloat(r):NaN;if(!Number.isFinite(n))return'';var s=(Math.round(n*10)/10).toFixed(1).replace(/\.0$/,'');return s+'/'+(scale||5);} 21function matchesFilters(h,txt,places,tiers,features,typeSel,stars,ratings){if(txt){var hay=(h.name+' '+h.city+' '+(h.features||[]).join(' ')).toLowerCase();if(hay.indexOf(txt)===-1)return false;} 22if(places&&places.length&&places.indexOf(h.city)===-1)return false;if(typeSel&&typeSel!==''&&typeSel!=='All'&&toTitleCase(h.type)!==typeSel)return false;if(tiers&&tiers.length&&tiers.indexOf(h.tier)===-1)return false;if(features&&features.length){var hf=h.features||[];var any=false;for(var i=0;i<features.length;i++){if(hf.indexOf(features[i])!==-1){any=true;break;}} 23if(!any)return false;} 24if(stars&&stars.length){var hStars=h.stars;if(!(typeof hStars==='number'&&isFinite(hStars)))return false;var matches=false;for(var i=0;i<stars.length;i++){var starLevel=parseInt(stars[i],10);if(isFinite(starLevel)&&Math.floor(hStars)===starLevel){matches=true;break;}} 25if(!matches)return false;} 26if(ratings&&ratings.length){var hRating=h.rating;if(!(typeof hRating==='number'&&isFinite(hRating)))return false;var matches=false;for(var i=0;i<ratings.length;i++){var ratingRange=ratings[i];if(matchesRatingRange(hRating,ratingRange)){matches=true;break;}} 27if(!matches)return false;} 28return true;} 29function matchesRatingRange(rating,range){var minRating=parseFloat(range);return rating>=minRating;} 30function cardHtml(h){var img=h.image||PLACEHOLDER_IMG;var feat=(h.features||[]).map(function(f){return'<span class="badge">
30'+f+'</span>';}).join(' ');var nameEsc=String(h.name||'').replace(/"/g,'"');var offer=AMADEUS_OFFERS[h.name]||null;if(!offer){var normalizedName=h.name.toLowerCase().replace(/[^a-z0-9]/g,'');offer=AMADEUS_OFFERS['__normalized__'+normalizedName]||null;} 31var livePrice='';var availability='';if(offer&&offer.price){livePrice='<span class="live-price" style="color:#e74c3c;font-weight:bold;font-size:1.1em;">'+ 32offer.price.total+' '+offer.price.currency+'</span>';availability='<span class="availability-badge" style="background:#27ae60;color:white;padding:3px 8px;border-radius:3px;font-size:0.85em;margin-left:8px;">Available</span>';}else if(offer===null){}else if(offer&&offer.error){availability='<span class="availability-badge" style="background:#95a5a6;color:white;padding:3px 8px;border-radius:3px;font-size:0.85em;margin-left:8px;">Check website</span>';} 33var href=(h.url&&String(h.url).trim())?String(h.url).trim():'';var openTag=href?'<a class="card card-link" href="'+href+'" target="_blank" rel="nofollow noopener noreferrer">':'<div class="card">';var closeTag=href?'</a>':'</div>';var btn=href?'<span class="btn btn-primary">See Prices & Availability</span>':'<span class="btn" aria-disabled="true" title="No booking link">See Prices & Availability</span>';var pillBits=[];if(h.type)pillBits.push(h.type);if(h.tier)pillBits.push(h.tier);if(livePrice){pillBits.push(livePrice+availability);}else if(h.approxPrice){pillBits.push(h.approxPrice);} 34return(openTag+' <div class="media"><img src="'+img+'" alt="'+nameEsc+'" loading="lazy"></div>'+' <div class="body">'+' <div class="title-row">'+' <div>'+' <div class="title">'+(h.name||'')+'</div>'+' <div class="sub">'+(h.city||'')+'</div>'+' </div>'+' <div class="ratings-stars"><div class="rating" aria-label="Guest rating">'+ 35(h.rating!=null?'<span class="rating-pill">Rating: '+ratingString(h.rating,RATING_SCALE)+'</span>':'')+' </div>\n'+' <div class="stars" aria-label="Hotel class '+(h.stars||'')+' stars">'+ 36(h.stars!=null?classStars(h.stars):'')+' </div></div>'+' </div>'+' <div class="features">'+feat+'</div>'+' <div class="actions">'+' <span class="pill">'+pillBits.join(' · ')+'</span>'+' </div>'+ 37btn+' </div>'+ 38closeTag);} 39function render(){var places=getSelectedValues('#hg-places');if(firstLoad&&places.length===0){jQuery('#hg-results').html('<div class="muted">Choose a location to begin.</div>');return;} 40var txt=(jQuery('#hg-filter').val()||'').trim().toLowerCase();var tiers=getSelectedValues('#hg-prices');var feats=getSelectedValues('#hg-features');var typeSel=jQuery('#hg-type').val()||'Hotel';var stars=getSelectedValues('#hg-stars');var ratings=getSelectedValues('#hg-ratings');var html='';for(var i=0;i<HOTELS.length;i++){var h=HOTELS[i];if(!matchesFilters(h,txt,places,tiers,feats,typeSel,stars,ratings))continue;html+=cardHtml(h);} 41jQuery('#hg-results').html(html||'<div class="muted">
41No hotels match your filters. Try adjusting your options.</div>');} 42function initFiltersFromData(){if(window.console)console.log('initFiltersFromData() called');var places=collectAllPlaces();var features=collectAllFeatures();var types=collectTypesFromData();var starOptions=[{key:'1',label:'1 Star'},{key:'2',label:'2 Stars'},{key:'3',label:'3 Stars'},{key:'4',label:'4 Stars'},{key:'5',label:'5 Stars'}];var ratingOptions=RATING_SCALE===10?[{key:'7',label:'7+ Rating'},{key:'8',label:'8+ Rating'},{key:'9',label:'9+ Rating'}]:[{key:'3.5',label:'3.5+ Rating'},{key:'4',label:'4+ Rating'},{key:'4.5',label:'4.5+ Rating'}];renderCheckboxList(jQuery('#hg-places'),places,'hg-place',false);renderCheckboxList(jQuery('#hg-stars'),starOptions,'hg-star',false);renderCheckboxList(jQuery('#hg-ratings'),ratingOptions,'hg-rating',false);renderCheckboxList(jQuery('#hg-prices'),PRICE_TIERS,'hg-price',false);renderCheckboxList(jQuery('#hg-features'),features,'hg-feature',false);renderTypeSelect(jQuery('#hg-type'),types.length?types:TYPE_OPTIONS,'Hotel');jQuery('#hg-places').off('change').on('change','input[type=checkbox]',function(){var any=getSelectedValues('#hg-places').length>0;if(any)firstLoad=false;render();fetchAmadeusAvailability();});jQuery('#hg-type').off('change').on('change',render);jQuery('#hg-stars').off('change').on('change','input[type=checkbox]',render);jQuery('#hg-ratings').off('change').on('change','input[type=checkbox]',render);jQuery('#hg-prices').off('change').on('change','input[type=checkbox]',render);jQuery('#hg-features').off('change').on('change','input[type=checkbox]',render);jQuery('#hg-filter').off('input').on('input',render);if(window.console)console.log('Setting up date field event handlers...',{checkInExists:jQuery('#hg-checkin').length,checkOutExists:jQuery('#hg-checkout').length});jQuery('#hg-checkin, #hg-checkout').off('change').on('change',function(){if(window.console)console.log('Date changed!',this.id,this.value);clearTimeout(window.amadeusTimer);window.amadeusTimer=setTimeout(fetchAmadeusAvailability,500);});if(window.console)console.log('Date event handlers set up complete');jQuery('#hg-places-all').off('click').on('click',function(){setAll('#hg-places',true);firstLoad=false;});jQuery('#hg-places-none').off('click').on('click',function(){setAll('#hg-places',false);render();});jQuery('#hg-stars-all').off('click').on('click',function(){setAll('#hg-stars',true);});jQuery('#hg-stars-none').off('click').on('click',function(){setAll('#hg-stars',false);});jQuery('#hg-ratings-all').off('click').on('click',function(){setAll('#hg-ratings',true);});jQuery('#hg-ratings-none').off('click').on('click',function(){setAll('#hg-ratings',false);});jQuery('#hg-prices-all').off('click').on('click',function(){setAll('#hg-prices',true);});jQuery('#hg-prices-none').off('click').on('click',function(){setAll('#hg-prices',false);});jQuery('#hg-features-all').off('click').on('click',function(){setAll('#hg-features',true);});jQuery('#hg-features-none').off('click').on('click',function(){setAll('#hg-features',false);});} 43function fetchAmadeusAvailability(){var checkIn=jQuery('#hg-checkin').val();var checkOut=jQuery('#hg-checkout').val();if(window.console){console.log('fetchAmadeusAvailability called',{checkIn:checkIn,checkOut:checkOut,hasCheckInField:jQuery('#hg-checkin').length>0,hasCheckOutField:jQuery('#hg-checkout').length>0});} 44if(!checkIn||!checkOut){if(window.console)console.log('No dates provided, skipping fetch');return;} 45var places=getSelectedValues('#hg-places');if(places.length===0){if(window.console)console.log('No places selected, skipping fetch');return;} 46var txt=(jQuery('#hg-filter').val()||'').trim().toLowerCase();var tiers=getSelectedValues('#hg-prices');var feats=getSelectedValues('#hg-features');var typeSel=jQuery('#hg-type').val()||'Hotel';var stars=getSelectedValues('#hg-stars');var ratings=getSelectedValues('#hg-ratings');var visibleHotels=[];for(var i=0;i<HOTELS.length&&visibleHotels.length<10;i++){var h=HOTELS[i];if(matchesFilters(h,txt,places,tiers,feats,typeSel,stars,ratings)){visibleHotels.push({name:h.name,city:h.city});}} 47if(visibleHotels.length===0){if(window.console)console.log('No visible hotels, skipping fetch');return;} 48if(window.console)console.log('Fetching availability for hotels:',visibleHotels);jQuery.ajax({url:'/wp-admin/admin-ajax.php',type:'POST',data:{action:'fetch_hotel_availability',hotels:visibleHotels,checkIn:checkIn,checkOut:checkOut,adults:2},beforeSend:function(){if(window.console)console.log('AJAX request starting...');},success:function(response){if(window.console)console.log('AJAX response:',response);if(response.success&&response.data){AMADEUS_OFFERS=response.data;if(window.console){console.log('Updated AMADEUS_OFFERS:',AMADEUS_OFFERS);console.log('Number of offers received:',Object.keys(AMADEUS_OFFERS).length);console.log('Offer hotel names:',Object.keys(AMADEUS_OFFERS).filter(function(k){return!k.startsWith('__normalized__');}));console.log('Our hotels:',HOTELS.map(function(h){return h.name;}));} 49render();}else{if(window.console)console.warn('Response not successful or no data',response);}},error:function(xhr,status,error){if(window.console)console.error('Amadeus AJAX error:',{xhr:xhr,status:status,error:error,responseText:xhr.responseText});}});} 50function loadHotels(){var inline=document.getElementById(DATA_SCRIPT_ID);if(inline&&inline.textContent.trim()){try{return jQuery.Deferred().resolve(JSON.parse(inline.textContent)).promise();}catch(e){if(window.console)console.warn('Invalid inline hotel JSON',e);}} 51return jQuery.getJSON(FALLBACK_URL);} 52jQuery(function(){jQuery('#hg-results').html('<div class="muted">Loading hotelsâ¦</div>');loadHotels().done(function(raw){var arr=Array.isArray(raw)?raw:(Array.isArray(raw.data)?raw.data:[]);HOTELS=normalizeScraped(arr);detectRatingScale();initFiltersFromData();render();}).fail(function(xhr){jQuery('#hg-results').html('<div class="muted">Could not load hotels ('+xhr.status+').</div>');});});})();function toggleCollapse(sectionId){const content=document.getElementById(sectionId+'-content');const section=content.closest('.collapsible');const textElement=section.querySelector('.collapse-text');const iconElement=section.querySelector('.collapse-icon');if(section.classList.contains('collapsed')){section.classList.remove('collapsed');content.style.maxHeight=content.scrollHeight+'px';textElement.textContent='Hide';iconElement.textContent='â½';}else{section.classList.add('collapsed');content.style.maxHeight='0px';textElement.textContent='Show';iconElement.textContent='â·';}}
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.