1(function($) { 2 $(document).ready(function() { 3 function showPostcodeError(input, message) { 4 input.css('border', '1px solid #ff473e'); 5 $('.installer-input-wrap').after( 6 '<div class="installer-error-msg" style="color: #ff473e; font-size: 13px;">' + message + '</div>' 7 ); 8 } 9 10 var skeletonCardHTML = 11 '<div class="p2s-installer-card">' + 12 '<div class="p2s-card-header">' + 13 '<div class="p2s-skeleton" style="width: 50px; height: 50px; border-radius: 13px; flex-shrink: 0;"></div>' + 14 '<div class="p2s-card-title-area" style="flex: 1;">' + 15 '<div class="p2s-skeleton" style="height: 16px; width: 80%; margin-bottom: 8px; border-radius: 4px;"></div>' + 16 '<div class="p2s-skeleton" style="height: 13px; width: 50%; border-radius: 4px;"></div>' + 17 '</div>' + 18 '</div>' + 19 '<div class="p2s-card-rating">' + 20 '<div class="p2s-skeleton" style="height: 14px; width: 40%; border-radius: 4px;"></div>' + 21 '</div>' + 22 '<div style="margin-top: 5px;">' + 23 '<div class="p2s-skeleton" style="height: 13px; width: 95%; margin-bottom: 6px; border-radius: 4px;"></div>' + 24 '<div class="p2s-skeleton" style="height: 13px; width: 75%; border-radius: 4px;"></div>' + 25 '</div>' + 26 '<div class="p2s-card-badges" style="margin-top: 4px;">' + 27 '<div class="p2s-skeleton" style="height: 22px; width: 130px; border-radius: 7px;"></div>' + 28 '<div class="p2s-skeleton" style="height: 22px; width: 110px; border-radius: 7px;"></div>' + 29 '</div>' + 30 '<div class="p2s-card-meta-bottom">' + 31 '<div class="p2s-skeleton" style="height: 12px; width: 25%; border-radius: 4px;"></div>' + 32 '<div class="p2s-skeleton" style="height: 12px; width: 25%; border-radius: 4px;"></div>' + 33 '<div class="p2s-skeleton" style="height: 12px; width: 25%; border-radius: 4px;"></div>' + 34 '</div>' + 35 '<div class="p2s-card-actions">' + 36 '<div class="p2s-skeleton" style="height: 38px; width: 55px; border-radius: 10px;"></div>' + 37 '<div class="p2s-skeleton" style="height: 38px; flex: 1 1 0%; border-radius: 10px;"></div>' + 38 '<div class="p2s-skeleton" style="height: 38px; flex: 1.3 1 0%; border-radius: 10px;"></div>' + 39 '</div>' + 40 '</div>'; 41
42 function renderSkeletons(count) { 43 $('.p2s-empty-state').hide(); 44 var html = ''; 45 for (var i = 0; i < (count || 3); i++) { 46 html += skeletonCardHTML; 47 } 48 $('.p2s-installers-grid').html(html); 49 } 50 51 var savedLocation = localStorage.getItem('p2s_location'); 52 if (savedLocation) { 53 $('.p2s-location-name').text(savedLocation); 54 $('input[name="installer-postcode"]').val(savedLocation); 55 $('.p2s-installer-step-one').hide(); 56 $('.p2s-installer-step-two').show(); 57 $('.p2s-controls-row, .p2s-filter-chips').show(); 58 fetchInstallers(); 59 } 60 61 $('.p2s-location-change').on('click', function() { 62 $('.p2s-location-text').hide(); 63 $('.p2s-location-input').val(localStorage.getItem('p2s_location') || ''); 64 $('.p2s-location-edit').css('display', 'flex'); 65 $('.p2s-location-input').focus(); 66 }); 67 68 function saveInlineLocation() { 69 var val = $('.p2s-location-input').val().trim(); 70 if (val === '') return; 71 72 var isStrictPostcode = /^BT[0-9]{1,2}\s?[0-9][a-zA-Z]{2}$/i.test(val); 73 var searchType = isStrictPostcode ? 'postcode' : 'city'; 74 75 localStorage.setItem('p2s_location', val); 76 localStorage.setItem('p2s_search_type', searchType); 77 78 var endpoint = isStrictPostcode 79 ? 'https://api.postcodes.io/postcodes/' + encodeURIComponent(val.replace(/\s+/g, '')) 80 : 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(val) + '.json?country=GB&types=place,locality,district&limit=5&access_token='
80+ p2s_installer_vars.mapbox_token; 81 82 $.ajax({ 83 url: endpoint, 84 type: 'GET', 85 success: function(res) { 86 $('.installer-error-msg').remove(); 87 $('.p2s-location-input').css('border', ''); 88 var lat = null, lng = null; 89 if (isStrictPostcode && res && res.result) { 90 lat = res.result.latitude; 91 lng = res.result.longitude; 92 } else if (!isStrictPostcode && res && res.features && res.features.length > 0) { 93 var match = res.features.find(function(f) { 94 return f.text.toLowerCase() === val.toLowerCase(); 95 }) || res.features[0]; 96 lat = match.center[1]; 97 lng = match.center[0]; 98 } 99 100 if (lat && lng) { 101 localStorage.setItem('p2s_installer_lat', lat); 102 localStorage.setItem('p2s_installer_lng', lng); 103 } else { 104 localStorage.removeItem('p2s_installer_lat'); 105 localStorage.removeItem('p2s_installer_lng'); 106 if (!isStrictPostcode) { 107 $('.p2s-location-input').css('border', '1px solid red'); 108 $('.p2s-location-edit').after('<span class="installer-error-msg" style="color:red;font-size:13px;display:block;margin-top:4px;">Location not found. Please enter a valid town or postcode.</span>'); 109 return; 110 } 111 } 112 finishInlineSave(val); 113 }, 114 error: function() { 115 $('.installer-error-msg').remove(); 116 $('.p2s-location-input').css('border', '1px solid red'); 117 $('.p2s-location-edit').after('<span class="installer-error-msg" style="color:red;font-size:13px;display:block;margin-top:4px;">Postcode not found. Please check and try again.</span>'); 118 } 119 }); 120 121 function finishInlineSave(loc) { 122 $('.p2s-location-name').text(loc); 123 $('.p2s-location-edit').hide(); 124 $('.p2s-location-text').show(); 125 fetchInstallers(); 126 } 127 } 128 129 $('.p2s-location-save-btn').on('click', saveInlineLocation); 130 131 $('.p2s-location-input').on('keypress', function(e) { 132 if (e.which === 13) { 133 e.preventDefault(); 134 saveInlineLocation(); 135 } 136 }); 137 138 $('.installer-step-one-btn').on('click', function(e) { 139 e.preventDefault(); 140 141 var locationInput = $('input[name="installer-postcode"]'); 142 var locationVal = locationInput.val().trim(); 143 144 $('.installer-error-msg').remove(); 145 locationInput.css('border', ''); 146 147 var isStrictPostcode = /^BT[0-9]{1,2}\s?[0-9][a-zA-Z]{2}$/i.test(locationVal); 148 var containsNumber = /\d/.test(locationVal); 149 150 if (locationVal === '') { 151 return showPostcodeError(locationInput, 'Please enter a postcode or town.'); 152 } 153 if (containsNumber && !isStrictPostcode) { 154 return showPostcodeError(locationInput, 'Please enter a valid Northern Ireland postcode (e.g. BT1 5GS).'); 155 } 156 if (!containsNumber && locationVal.length < 3) { 157 return showPostcodeError(locationInput, 'Please enter a valid town name.'); 158 } 159 160 var searchType = isStrictPostcode ? 'postcode' : 'city'; 161 162 localStorage.setItem('p2s_location', locationVal); 163 localStorage.setItem('p2s_search_type', searchType); 164 165 var endpoint = isStrictPostcode 166 ? 'https://api.postcodes.io/postcodes/' + encodeURIComponent(locationVal.replace(/\s+/g, '')) 167 : 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(locationVal) + '.json?country=GB&types=place,locality,district&limit=5&access_token='
167+ p2s_installer_vars.mapbox_token; 168 169 $.ajax({ 170 url: endpoint, 171 type: 'GET', 172 success: function(res) { 173 var lat = null, lng = null; 174 if (isStrictPostcode && res && res.result) { 175 lat = res.result.latitude; 176 lng = res.result.longitude; 177 } else if (!isStrictPostcode && res && res.features && res.features.length > 0) { 178 var match = res.features.find(function(f) { 179 return f.text.toLowerCase() === locationVal.toLowerCase(); 180 }) || res.features[0]; 181 lat = match.center[1]; 182 lng = match.center[0]; 183 } 184 185 if (lat && lng) { 186 localStorage.setItem('p2s_installer_lat', lat); 187 localStorage.setItem('p2s_installer_lng', lng); 188 } else { 189 localStorage.removeItem('p2s_installer_lat'); 190 localStorage.removeItem('p2s_installer_lng'); 191 if (!isStrictPostcode) { 192 return showPostcodeError(locationInput, 'Location not found. Please enter a valid town or postcode.'); 193 } 194 } 195 proceedToStepTwo(locationVal); 196 }, 197 error: function() { 198 showPostcodeError(locationInput, 'Postcode not found. Please check and try again.'); 199 } 200 }); 201 202 function proceedToStepTwo(loc) { 203 $('.p2s-location-name').text(loc); 204 $('.p2s-installer-step-one').hide(); 205 $('.p2s-installer-step-two').show(); 206 $('.p2s-controls-row, .p2s-filter-chips').show(); 207 fetchInstallers(); 208 } 209 }); 210 211 var currentXhr = null; 212 var currentPage = 1; 213 214 $('.p2s-radius-select, .p2s-sort-select').on('change', function() { 215 currentPage = 1; 216 fetchInstallers(); 217 }); 218 219 $('.p2s-chip').on('click', function() { 220 if ($(this).hasClass('p2s-chip-active')) { 221 return; 222 } 223 $('.p2s-chip').removeClass('p2s-chip-active'); 224 $(this).addClass('p2s-chip-active'); 225 currentPage = 1; 226 fetchInstallers(); 227 }); 228 229 function fetchInstallers() { 230 if (currentXhr) { 231 currentXhr.abort(); 232 } 233 234 renderSkeletons(6); 235 236 currentXhr = $.ajax({ 237 url: '/wp-admin/admin-ajax.php', 238 type: 'POST', 239 data: { 240 action: 'p2s_get_installers', 241 location: localStorage.getItem('p2s_location'), 242 search_type: localStorage.getItem('p2s_search_type'), 243 lat: localStorage.getItem('p2s_installer_lat'), 244 lng: localStorage.getItem('p2s_installer_lng'), 245 radius: $('.p2s-radius-select').val(), 246 sort: $('.p2s-sort-select').val(), 247 filter: $('.p2s-chip-active').text().trim(), 248 saved_ids: localStorage.getItem('p2s_saved_installers') || '[]', 249 page: currentPage 250 }, 251 success: function(res) { 252 if (res.success && res.data.html) { 253 $('.p2s-installers-grid').html(res.data.html); 254 $('.p2s-results-count span').text(res.data.count); 255 $('.p2s-location-sub').show().find('.sc-interp').text(res.data.count); 256 $('.p2s-installer-listing-tip').show(); 257 $('.p2s-empty-state').hide(); 258 } else { 259 $('.p2s-installers-grid').empty(); 260 $('.p2s-installer-listing-tip').hide(); 261 $('.p2s-empty-state').show(); 262 $('.p2s-results-count span').text('0'); 263 $('.p2s-location-sub').show().find('.sc-interp').text('0'); 264 } 265 if (res.success) { 266 $('.p2s-installer-pagination').html(res.data.pagination_html || ''); 267 } 268 renderCompareTray(); 269 } 270 }); 271 } 272 273 $(document).on('click', '.p2s-installer-page-btn, .p2s-installer-page-num', function() { 274 var $btn = $(this); 275 if ($btn.is('[disabled]') || $btn.hasClass('p2s-installer-page-active')) { 276 return; 277 } 278 currentPage = parseInt($btn.attr('data-page'), 10);
279 fetchInstallers(); 280 $('html, body').animate({ scrollTop: $('.p2s-results-count').offset().top - 80 }, 300); 281 }); 282 283 $(document).on('click', '.p2s-btn-quote', function(e) { 284 e.preventDefault(); 285 var btn = $(this); 286 if (btn.hasClass('p2s-btn-disabled')) return; 287 var card = btn.closest('.p2s-installer-card'); 288 var id = btn.attr('data-id'); 289 var name = card.find('.p2s-card-title').text().trim(); 290 291 var list = JSON.parse(localStorage.getItem('p2s_compare_list') || '[]'); 292 var existingIndex = list.findIndex(function(item) { return String(item.id) === String(id); }); 293 294 if (existingIndex !== -1) { 295 // Already added â remove from list 296 list.splice(existingIndex, 1); 297 localStorage.setItem('p2s_compare_list', JSON.stringify(list)); 298 renderCompareTray(); 299 } else if (list.length < 3) { 300 // Add to list 301 list.push({ id: id, name: name }); 302 localStorage.setItem('p2s_compare_list', JSON.stringify(list)); 303 renderCompareTray(); 304 } 305 }); 306 307 308 $(document).on('click', '.p2s-tray-remove-btn', function(e) { 309 e.preventDefault(); 310 var name = $(this).attr('data-name'); 311 var list = JSON.parse(localStorage.getItem('p2s_compare_list') || '[]'); 312 313 list = list.filter(function(item) { return item.name !== name; }); 314 localStorage.setItem('p2s_compare_list', JSON.stringify(list)); 315 316 renderCompareTray(); 317 }); 318 319 $(document).on('click', '.p2s-tray-clear-btn', function(e) { 320 e.preventDefault(); 321 localStorage.removeItem('p2s_compare_list'); 322 renderCompareTray(); 323 }); 324 325 $(document).on('click', '.p2s-tray-quote-btn', function(e) { 326 e.preventDefault(); 327 var list = JSON.parse(localStorage.getItem('p2s_compare_list') || '[]'); 328 var ids = list.map(function(item) { return item.id; }).join(','); 329 window.location.href = '/find-an-installer/request-a-quote/' + (ids ? '?installer_ids=' + ids : ''); 330 }); 331 332 $(document).on('change', '.p2s-custom-quote-form label.acf-basic-uploader input[type="file"]', function() { 333 var fileName = this.files && this.files[0] ? this.files[0].name : ''; 334 var $label = $(this).closest('label.acf-basic-uploader'); 335 336 if (fileName) { 337 $label.addClass('has-file'); 338 if ($label.find('.p2s-file-name').length === 0) { 339 $label.append('<span class="p2s-file-name"></span>'); 340 } 341 $label.find('.p2s-file-name').text('ð ' + fileName); 342 } else { 343 $label.removeClass('has-file'); 344 $label.find('.p2s-file-name').remove(); 345 } 346 }); 347 348 var urlParams = new URLSearchParams(window.location.search); 349 350 if ($('.p2s-custom-quote-form').length) { 351 var urlInstallerIds = urlParams.get('installer_ids'); 352 var urlInstallerId = urlParams.get('installer_id'); 353 var storedList = JSON.parse(localStorage.getItem('p2s_compare_list') || '[]'); 354 var compareData; 355 356 if (urlInstallerIds) { 357 var idList = urlInstallerIds.split(',').map(function(id) { 358 return { id: id.trim(), name: '' }; 359 }); 360 compareData = JSON.stringify(idList); 361 localStorage.setItem('p2s_compare_list', compareData); 362 } else if (urlInstallerId) { 363 var singleList = [{ id: urlInstallerId.trim(), name: '' }]; 364 compareData = JSON.stringify(singleList); 365 localStorage.setItem('p2s_compare_list', compareData); 366 } else if (storedList.length > 0) { 367 compareData = JSON.stringify(storedList); 368 } else { 369 compareData = '[]'; 370 } 371 372 $('.p2s-custom-quote-form').append( 373 '<input type="hidden" name="p2s_compare_list" value="' + compareData.replace(/"/g, '"') + '">' 374 ); 375 376 var listToFetch = JSON.parse(compareData); 377 if (listToFetch.length > 0) { 378 $.ajax({ 379 url: '/wp-admin/admin-ajax.php', 380 type: 'POST', 381 data: {
382 action: 'p2s_get_quote_installers_info', 383 installer_ids: JSON.stringify(listToFetch) 384 }, 385 success: function(res) { 386 if (res.success && res.data && res.data.length > 0) { 387 var items = res.data; 388 var count = items.length; 389 390 var updatedList = items.map(function(item) { 391 return { id: String(item.id), name: item.title }; 392 }); 393 localStorage.setItem('p2s_compare_list', JSON.stringify(updatedList)); 394 395 if (count === 1) { 396 var name = items[0].title; 397 $('.p2s-quote-heading .elementor-heading-title, .p2s-quote-heading h1').text('Request a quote'); 398 $('.p2s-quote-subtitle p, .p2s-quote-subtitle').text("Send your details to " + name + ". They'll get back to you shortly."); 399 $('.p2s-custom-quote-form input[type="submit"]').val("Request a quote from " + name); 400 $('.p2s-quote-tip-box .elementor-widget-container, .p2s-quote-tip-box').html( 401 '<b>ð¡ One enquiry, multiple quotes.</b> Go back and click \'Add to quote\' on more installers to request from all of them at once.' 402 ); 403 } else { 404 $('.p2s-quote-heading .elementor-heading-title, .p2s-quote-heading h1').text('Request quotes from ' + count + ' installers'); 405 $('.p2s-quote-subtitle p, .p2s-quote-subtitle').text('One form, ' + count + ' installers. Let the quotes come to you â no chasing required.'); 406 $('.p2s-custom-quote-form input[type="submit"]').val('Request quotes from ' + count + ' installers'); 407 $('.p2s-quote-tip-box .elementor-widget-container, .p2s-quote-tip-box').html( 408 '<b>ð¡ One enquiry, multiple quotes.</b> Getting quotes from multiple installers side by side typically saves the most â youâre all set.' 409 ); 410 } 411 412 var listHtml = count > 1 ? '<div class="p2s-installers-list">' : ''; 413 items.forEach(function(item) { 414 if (count === 1) { 415 var logoHtml = item.logo ? 416 '<div class="p2s-installer-logo-wrap"><img class="p2s-installer-logo" src="' + item.logo + '" alt="' + item.title + '"></div>' : 417 '<div class="p2s-installer-avatar"><span class="sc-interp">' + item.initials + '</span></div>'; 418 419 var rating = item.meta.split(' ')[0] || '4.9'; 420 var reviews = item.meta.substring(item.meta.indexOf(' ')) || ' (214)'; 421 422 listHtml += '<div class="p2s-installer-item p2s-installer-single">' + 423 logoHtml + 424 '<div class="p2s-installer-details">' + 425 '<div class="p2s-installer-title"><span class="sc-interp">' + item.title + '</span></div>' + 426 '</div>' + 427 '</div>'; 428 } else { 429 var logoHtml = item.logo ? 430 '<div class="p2s-installer-logo-wrap"><img class="p2s-installer-logo" src="' + item.logo + '" alt="' + item.title + '"></div>' : 431 '<div class="p2s-installer-avatar"><span class="sc-interp">' + item.initials + '</span></div>'; 432 433 var ratingParts = item.meta.split(' · '); 434 var rating = ratingParts[0] || '4.9'; 435 var distance = ratingParts[1] || ''; 436 var metaHtml = distance ? '<span class="p2s-installer-distance"><span class="sc-interp">' + distance + '</span></span>' : ''; 437 438 listHtml += '<div class="p2s-installer-item p2s-installer-multiple">' + 439 logoHtml + 440 '<div class="p2s-installer-details">' + 441 '<div class="p2s-installer-title"><span class="sc-interp">' + item.title + '</span></div>' + 442 '<div class="p2s-installer-meta">' + metaHtml + '</div>' + 443 '</div>' + 444 '</div>'; 445 } 446 }); 447 if (count > 1) { 448 listHtml += '</div>'; 449 } 450 $('.p2s-contacting-list .elementor-widget-container, .p2s-contacting-list').html(listHtml); 451 } 452 } 453 }); 454 } 455 } 456 457 $(document).on('click', '.p2s-quote-back a', function(e) { 458 e.preventDefault(); 459 if (document.referrer && document.referrer.indexOf(window.location.host) !== -1) { 460 window.history.back(); 461 } else { 462 window.location.href = '/find-an-installer/'; 463 } 464 }); 465
466 var refCode = urlParams.get('ref'); 467 468 if (urlParams.has('updated') || refCode) { 469 localStorage.removeItem('p2s_compare_list'); 470 if (refCode) { 471 $('.p2s-ref-code strong').text(refCode); 472 } 473 $('.before-installer-quote-form').hide(); 474 $('.after-installer-quote-form').show(); 475 476 if (window.history.replaceState) { 477 window.history.replaceState({}, document.title, window.location.pathname); 478 } 479 } else { 480 $('.after-installer-quote-form').hide(); 481 } 482 483 function renderCompareTray() { 484 var list = JSON.parse(localStorage.getItem('p2s_compare_list') || '[]'); 485 var count = list.length; 486 487 if (count > 0) { 488 var html = ''; 489 list.forEach(function(item) { 490 html += '<div class="p2s-tray-item">' + 491 item.name + 492 '<button type="button" class="p2s-tray-remove-btn" data-name="' + item.name + '">Ã</button>' + 493 '</div>'; 494 }); 495 496 $('.p2s-tray-items').html(html); 497 $('.p2s-tray-count').html('<span>' + count + '</span>/3 selected'); 498 if (count === 1) { 499 $('.p2s-tray-quote-btn').html('Request quote from <span class="sc-interp">1</span> installer â'); 500 } else { 501 $('.p2s-tray-quote-btn').html('Request quotes from <span class="sc-interp">' + count + '</span> installers â'); 502 } 503 $('.p2s-compare-tray').show(); 504 } else { 505 $('.p2s-compare-tray').hide(); 506 $('.p2s-tray-items').empty(); 507 } 508 509 var ids = list.map(function(item) { return String(item.id); }); 510 var isMaxReached = list.length >= 3; 511 $('.p2s-installer-card').each(function() { 512 var btn = $(this).find('.p2s-btn-quote'); 513 var btnId = String(btn.attr('data-id')); 514 if (ids.indexOf(btnId) !== -1) { 515 btn.addClass('p2s-btn-added-state').removeClass('p2s-btn-disabled').text('â Added'); 516 btn.removeAttr('title'); 517 } else if (list.length > 0) { 518 btn.removeClass('p2s-btn-added-state').text('Add to quote'); 519 if (isMaxReached) { 520 btn.addClass('p2s-btn-disabled'); 521 btn.attr('title', 'Maximum of 3 quotes allowed'); 522 } else { 523 btn.removeClass('p2s-btn-disabled'); 524 btn.removeAttr('title'); 525 } 526 } else { 527 btn.removeClass('p2s-btn-added-state p2s-btn-disabled').text('Get a quote'); 528 btn.removeAttr('title'); 529 } 530 }); 531 } 532 533 renderCompareTray(); 534 535 $('input[name="installer-postcode"]').on('keypress', function(e) { 536 if (e.which === 13) { 537 e.preventDefault(); 538 $('.installer-step-one-btn').click(); 539 } 540 }); 541 542 function getSavedInstallers() { 543 try { 544 return JSON.parse(localStorage.getItem('p2s_saved_installers')) || []; 545 } catch (e) { 546 return []; 547 } 548 } 549 550 function setSavedInstallers(list) { 551 localStorage.setItem('p2s_saved_installers', JSON.stringify(list)); 552 updateSavedCount(); 553 } 554 555 function updateSavedCount() { 556 var count = getSavedInstallers().length; 557 var $btn = $('.p2s-saved-btn'); 558 if ($btn.length) { 559 $btn.find('.sc-interp').text(count); 560 if ($btn.hasClass('p2s-chip-active') && count === 0) { 561 // Optional: If they are viewing the "Saved" filter and remove the last one, it stays on 0. 562 } 563 } 564 } 565 updateSavedCount(); 566 567 function initProfileSaveButton() { 568 var $btn = $('.p2s-profile-save-btn'); 569 if (!$btn.length) return; 570 571 var id = String($btn.attr('data-id'));
572 var saved = getSavedInstallers(); 573 574 if (saved.indexOf(id) !== -1) { 575 $btn.addClass('active').html('⥠Saved'); 576 } else { 577 $btn.removeClass('active').html('⡠Save'); 578 } 579 } 580 581 initProfileSaveButton(); 582 583 $(document).on('click', '.p2s-profile-save-btn', function() { 584 var $btn = $(this); 585 var id = String($btn.attr('data-id')); 586 if (!id) return; 587 588 var saved = getSavedInstallers(); 589 var index = saved.indexOf(id); 590 591 if (index !== -1) { 592 saved.splice(index, 1); 593 $btn.removeClass('active').html('⡠Save'); 594 } else { 595 saved.push(id); 596 $btn.addClass('active').html('⥠Saved'); 597 } 598 599 setSavedInstallers(saved); 600 }); 601 602 $(document).on('click', '.p2s-card-saved-btn', function() { 603 var $btn = $(this); 604 var id = String($btn.attr('data-id')); 605 if (!id) return; 606 607 var saved = getSavedInstallers(); 608 var index = saved.indexOf(id); 609 610 if (index !== -1) { 611 saved.splice(index, 1); 612 setSavedInstallers(saved); 613 614 if ($('.p2s-saved-btn').hasClass('p2s-chip-active')) { 615 var $grid = $btn.closest('.p2s-installers-grid'); 616 $btn.closest('.p2s-installer-card').remove(); 617 618 var currentCount = parseInt($('.p2s-results-count span').text()) || 0; 619 if (currentCount > 0) { 620 currentCount--; 621 $('.p2s-results-count span').text(currentCount); 622 $('.p2s-location-sub').show().find('.sc-interp').text(currentCount); 623 } 624 625 if ($grid.find('.p2s-installer-card').length === 0) { 626 $('.p2s-empty-state').show(); 627 } 628 } else { 629 $btn.closest('.p2s-card-saved-wrap').remove(); 630 } 631 } 632 }); 633 634 /* Photo Lightbox Functionality */ 635 const $galleryItems = $('.p2s-gallery-item img'); 636 const $lightbox = $('#p2s-lightbox'); 637 638 if ($galleryItems.length && $lightbox.length) { 639 const $lightboxImg = $('#p2s-lightbox-img'); 640 const $counter = $('#p2s-lightbox-counter'); 641 642 const images = []; 643 $galleryItems.each(function() { 644 images.push($(this).attr('src').replace('&w=600&', '&w=1400&')); 645 }); 646 let currentIndex = 0; 647 648 function updateLightbox() { 649 $lightboxImg.attr('src', images[currentIndex]); 650 $counter.text((currentIndex + 1) + ' / ' + images.length); 651 } 652 653 function closeLightbox() { 654 $lightbox.removeClass('active').attr('aria-hidden', 'true'); 655 $('body').css('overflow', ''); 656 } 657 658 function showPrev() { 659 currentIndex = (currentIndex - 1 + images.length) % images.length; 660 updateLightbox(); 661 } 662 663 function showNext() { 664 currentIndex = (currentIndex + 1) % images.length; 665 updateLightbox(); 666 } 667 668 $galleryItems.on('click', function() { 669 currentIndex = $galleryItems.index(this); 670 updateLightbox(); 671 $lightbox.addClass('active').attr('aria-hidden', 'false'); 672 $('body').css('overflow', 'hidden'); 673 }); 674 675 $('#p2s-lightbox-close').on('click', closeLightbox); 676 $('#p2s-lightbox-prev').on('click', function(e) { e.stopPropagation(); showPrev(); }); 677 $('#p2s-lightbox-next').on('click', function(e) { e.stopPropagation(); showNext(); }); 678 679 $lightbox.on('click', function(e) { 680 if ($(e.target).is($lightbox) || $(e.target).hasClass('p2s-lightbox-content')) { 681 closeLightbox(); 682 } 683 }); 684 685 $(document).on('keydown', function(e) { 686 if (!$lightbox.hasClass('active')) return; 687 if (e.key === 'Escape') closeLightbox(); 688 if (e.key === 'ArrowLeft') showPrev(); 689 if (e.key === 'ArrowRight') showNext(); 690 }); 691 } 692 }); 693})(jQuery);
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.