1(function() { 2 'use strict'; 3 4 // Theme toggle 5 var themeToggle = document.getElementById('themeToggle'); 6 var iconLight = document.getElementById('themeIconLight'); 7 var iconDark = document.getElementById('themeIconDark'); 8 var html = document.documentElement; 9 10 function getEffectiveTheme() { 11 var theme = html.getAttribute('data-theme'); 12 if (theme === 'dark') return 'dark'; 13 if (theme === 'light') return 'light'; 14 // auto â check system preference 15 return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 16 } 17 18 function updateThemeIcons() { 19 var effective = getEffectiveTheme(); 20 // Show sun icon when dark (click to go light), moon when light (click to go dark) 21 iconLight.style.display = effective === 'dark' ? 'inline' : 'none'; 22 iconDark.style.display = effective === 'light' ? 'inline' : 'none'; 23 } 24 25 // Restore saved preference 26 var saved = localStorage.getItem('mkt_theme'); 27 if (saved) { 28 html.setAttribute('data-theme', saved); 29 } 30 updateThemeIcons(); 31 32 if (themeToggle) { 33 themeToggle.addEventListener('click', function() { 34 var effective = getEffectiveTheme(); 35 var next = effective === 'dark' ? 'light' : 'dark'; 36 html.setAttribute('data-theme', next); 37 localStorage.setItem('mkt_theme', next); 38 updateThemeIcons(); 39 }); 40 } 41 42 // Listen for system theme changes when in auto mode 43 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() { 44 if (html.getAttribute('data-theme') === 'auto') { 45 updateThemeIcons(); 46 } 47 }); 48 49 // Favorite / Save toggle â persists to the server (no login required; anon 50 // saves are attributed to a first-party cookie the endpoint sets). 51 var favBtn = document.getElementById('favoriteBtn'); 52 if (favBtn) { 53 var favListingId = parseInt(favBtn.getAttribute('data-listing-id'), 10) || 0; 54 55 var paintFav = function(saved) { 56 favBtn.innerHTML = '<i class="fa' + (saved ? 's' : 'r') + ' fa-heart"></i> ' + (saved ? 'Saved' : 'Save'); 57 }; 58 59 favBtn.addEventListener('click', function() { 60 if (!favListingId || favBtn.dataset.busy === '1') return; 61 favBtn.dataset.busy = '1'; 62 var wasSaved = favBtn.querySelector('i').classList.contains('fas'); 63 paintFav(!wasSaved); // optimistic 64 65 fetch('/api/marketplace/favorite', { 66 method: 'POST', 67 headers: { 'Content-Type': 'application/json' }, 68 credentials: 'same-origin', 69 body: JSON.stringify({ listing_id: favListingId }) 70 }) 71 .then(function(r) { return r.json(); }) 72 .then(function(d) { 73 if (!d || !d.success) { paintFav(wasSaved); return; } // revert on failure 74 paintFav(d.saved); 75 }) 76 .catch(function() { paintFav(wasSaved); }) 77 .finally(function() { favBtn.dataset.busy = '0'; }); 78 }); 79 } 80 81 // Alert subscription 82 var alertBtn = document.getElementById('alertBtn'); 83 if (alertBtn) { 84 alertBtn.addEventListener('click', function() { 85 // TODO: POST to /api/marketplace/price_alert.php 86 alert('Price alert subscription coming soon.'); 87 }); 88 } 89 90 // Buy Now â starts Stripe Checkout. Guest-friendly: no login required. 91 // If the buyer is on a parked seller domain (non-canonical host), that 92 // host doesn't serve the /api endpoints, so we redirect to the canonical 93 // listing URL with autobuy=1 and auto-fire the flow once it loads. 94 function startBuyNowCheckout(triggerBtn, channel) { 95 if (!priceCard) return; 96 97 if (priceCard.dataset.buyNowEnabled !== '1') { 98 alert('Buy Now is not available â the seller has not completed payout setup.'); 99 return; 100 } 101 102 var canonicalUrl = priceCard.dataset.canonicalUrl; 103 try { 104 if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) { 105 window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autobuy=1'; 106 return; 107 } 108 } catch (e) { /* ignore parse errors */ } 109 110 var listingId = parseInt(priceCard.dataset.listingId, 10); 111 if (!listingId) return; 112 113 var origLabel = triggerBtn ? triggerBtn.innerHTML : ''; 114 if (triggerBtn) { 115 triggerBtn.disabled = true; 116 triggerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Redirecting...'; 117 } 118 119 fetch('/api/marketplace/checkout', { 120 method: 'POST', 121 headers: { 'Content-Type': 'application/json' }, 122 body: JSON.stringify({ listing_id: listingId, channel: channel || 'card' }) 123 }).then(function(r) { 124 return r.json().then(function(data) { return { ok: r.ok, data: data }; }); 125 }).then(function(res) { 126 if (res.ok && res.data.success && res.data.checkout_url) { 127 window.location = res.data.checkout_url; 128 return; 129 } 130 alert(res.data && res.data.error ? res.data.error : 'Could not start checkout. Please try again.'); 131 if (triggerBtn) { 132 triggerBtn.disabled = false;
133 triggerBtn.innerHTML = origLabel; 134 } 135 }).catch(function() { 136 alert('Network error. Please try again.'); 137 if (triggerBtn) { 138 triggerBtn.disabled = false; 139 triggerBtn.innerHTML = origLabel; 140 } 141 }); 142 } 143 144 var buyBtn = document.getElementById('buyNowBtn'); 145 if (buyBtn && !buyBtn.disabled) { 146 buyBtn.addEventListener('click', function() { startBuyNowCheckout(buyBtn, 'card'); }); 147 } 148 149 // Alipay (zh pages): familiar rail for Chinese buyers 150 var alipayBtn = document.getElementById('payAlipayBtn'); 151 if (alipayBtn && !alipayBtn.disabled) { 152 alipayBtn.addEventListener('click', function() { startBuyNowCheckout(alipayBtn, 'alipay'); }); 153 } 154 155 // Crypto/stablecoins: instant, no surcharge 156 var cryptoBtn = document.getElementById('payCryptoBtn'); 157 if (cryptoBtn && !cryptoBtn.disabled) { 158 cryptoBtn.addEventListener('click', function() { startBuyNowCheckout(cryptoBtn, 'crypto'); }); 159 } 160 161 // Bitcoin (BTCPay on our side): our own invoice page. Login required - a verified email is the only 162 // identity we ask of a Bitcoin buyer - so a signed-out click goes to login and back to this listing. 163 function startBtcCheckout(triggerBtn) { 164 if (!priceCard) return; 165 var canonicalUrl = priceCard.dataset.canonicalUrl; 166 try { 167 if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) { 168 window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autobtc=1'; 169 return; 170 } 171 } catch (e) { /* ignore */ } 172 var listingId = parseInt(priceCard.dataset.listingId, 10); 173 if (!listingId) return; 174 var origLabel = triggerBtn ? triggerBtn.innerHTML : ''; 175 if (triggerBtn) { triggerBtn.disabled = true; triggerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating invoice...'; } 176 fetch('/api/marketplace/btc-checkout', { 177 method: 'POST', 178 headers: { 'Content-Type': 'application/json' }, 179 body: JSON.stringify({ listing_id: listingId }) 180 }).then(function(r) { 181 return r.json().then(function(data) { return { ok: r.ok, data: data }; }); 182 }).then(function(res) { 183 if (res.ok && res.data.success && res.data.pay_url) { window.location = res.data.pay_url; return; } 184 if (res.data && res.data.login_required) { 185 window.location = 'https://appraise.net/login?redirect=' + encodeURIComponent(window.location.pathname + '?autobtc=1'); 186 return; 187 } 188 alert(res.data && res.data.error ? res.data.error : 'Could not start Bitcoin checkout. Please try again.'); 189 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 190 }).catch(function() { 191 alert('Network error. Please try again.'); 192 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 193 }); 194 } 195 // PayPal (Orders v2): our endpoint creates the order and we send the buyer to PayPal's approval page. 196 function startPaypalCheckout(triggerBtn) { 197 if (!priceCard) return; 198 var canonicalUrl = priceCard.dataset.canonicalUrl; 199 try { 200 if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) { 201 window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autopaypal=1'; 202 return; 203 } 204 } catch (e) { /* ignore */ } 205 var listingId = parseInt(priceCard.dataset.listingId, 10); 206 if (!listingId) return; 207 var origLabel = triggerBtn ? triggerBtn.innerHTML : ''; 208 if (triggerBtn) { triggerBtn.disabled = true; triggerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Redirecting to PayPal...'; } 209 fetch('/api/marketplace/paypal-checkout', { 210 method: 'POST', 211 headers: { 'Content-Type': 'application/json' }, 212 body: JSON.stringify({ listing_id: listingId }) 213 }).then(function(r) { 214 return r.json().then(function(data) { return { ok: r.ok, data: data }; }); 215 }).then(function(res) { 216 if (res.ok && res.data.success && res.data.approve_url) { window.location = res.data.approve_url; return; } 217 if (res.data && res.data.login_required) { 218 window.location = 'https://appraise.net/login?redirect=' + encodeURIComponent(window.location.pathname + '?autopaypal=1'); 219 return; 220 } 221 alert(res.data && res.data.error ? res.data.error : 'Could not start PayPal checkout. Please try again.'); 222 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 223 }).catch(function() { 224 alert('Network error. Please try again.'); 225 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 226 }); 227 } 228 var paypalBtn = document.getElementById('payPaypalBtn'); 229 if (paypalBtn && !paypalBtn.disabled) { 230 paypalBtn.addEventListener('click', function() { startPaypalCheckout(paypalBtn); }); 231 if (/[?&]autopaypal=1/.test(window.location.search)) { startPaypalCheckout(paypalBtn); } 232 } 233 234 var btcBtn = document.getElementById('payBtcBtn'); 235 if (btcBtn && !btcBtn.disabled) { 236 btcBtn.addEventListener('click', function() { startBtcCheckout(btcBtn); }); 237 if (/[?&]autobtc=1/.test(window.location.search)) { startBtcCheckout(btcBtn); } 238 } 239 240 // US bank debit (ACH): no card surcharge; settles in a few business days 241 var bankBtn = document.getElementById('payBankBtn'); 242 if (bankBtn && !bankBtn.disabled) { 243 bankBtn.addEventListener('click', function() { startBuyNowCheckout(bankBtn, 'bank'); }); 244 } 245 246 // ---- Buy via Escrow.com ---- 247 // Delegates to the shared modal in /js/marketplace-escrow-modal.js, which 248 // collects the buyer's Escrow.com email (with pre-fill / localStorage), 249 // then POSTs to escrow-checkout.php and redirects to the agreement URL. 250 function startEscrowCheckout(triggerBtn) { 251 if (!priceCard) return; 252 253 // Non-canonical host redirect â same pattern as Stripe Buy Now. 254 var canonicalUrl = priceCard.dataset.canonicalUrl; 255 try { 256 if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) { 257 window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autoescrow=1'; 258 return; 259 } 260 } catch (e) { /* ignore */ } 261 262 var listingId = parseInt(priceCard.dataset.listingId, 10); 263 if (!listingId) return; 264 265 if (typeof window.openEscrowModal === 'function') { 266 window.openEscrowModal({ 267 mode: 'listing', 268 listingId: listingId, 269 triggerBtn: triggerBtn 270 }); 271 } else { 272 // Defensive fallback if the modal module wasn't loaded for some 273 // reason â should never hit in production. 274 alert('Escrow.com checkout is temporarily unavailable. Please try again or use card payment.'); 275 } 276 } 277 278 var escrowBtn = document.getElementById('buyEscrowBtn'); 279 if (escrowBtn && !escrowBtn.disabled) { 280 escrowBtn.addEventListener('click', function() { startEscrowCheckout(escrowBtn); }); 281 } 282 283 // ---- LTO Start Lease (mirrors Buy Now but hits lto-checkout.php) ---- 284 // The lease is a contract: the buyer confirms they have read the lease-to-own terms in a modal first, and 285 // lto-checkout.php refuses to create the transaction without terms_accepted. 286 var ltoTermsModal = document.getElementById('ltoTermsModal'); 287 var ltoTermsAck = document.getElementById('ltoTermsAck'); 288 var ltoTermsContinue = document.getElementById('ltoTermsContinue'); 289 var ltoPendingBtn = null; 290 function openLtoTerms(triggerBtn) { 291 if (!ltoTermsModal) { startLtoCheckout(triggerBtn, true); return; } 292 ltoPendingBtn = triggerBtn; 293 if (ltoTermsAck) { ltoTermsAck.checked = false; } 294 if (ltoTermsContinue) { ltoTermsContinue.disabled = true; } 295 ltoTermsModal.hidden = false;
296 } 297 if (ltoTermsModal) { 298 ltoTermsModal.addEventListener('click', function(e) { if (e.target.closest('[data-lto-close]')) ltoTermsModal.hidden = true; }); 299 if (ltoTermsAck && ltoTermsContinue) { ltoTermsAck.addEventListener('change', function() { ltoTermsContinue.disabled = !ltoTermsAck.checked; }); } 300 if (ltoTermsContinue) { ltoTermsContinue.addEventListener('click', function() { 301 if (!ltoTermsAck || !ltoTermsAck.checked) return; 302 ltoTermsModal.hidden = true; 303 startLtoCheckout(ltoPendingBtn, true); 304 }); } 305 } 306 function startLtoCheckout(triggerBtn, termsAccepted) { 307 if (!priceCard) return; 308 309 var canonicalUrl = priceCard.dataset.canonicalUrl; 310 try { 311 if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) { 312 window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autolto=1'; 313 return; 314 } 315 } catch (e) { /* ignore */ } 316 317 var listingId = parseInt(priceCard.dataset.listingId, 10); 318 if (!listingId) return; 319 320 var origLabel = triggerBtn ? triggerBtn.innerHTML : ''; 321 if (triggerBtn) { 322 triggerBtn.disabled = true; 323 triggerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Redirecting...'; 324 } 325 326 fetch('/api/marketplace/lto-checkout', { 327 method: 'POST', 328 headers: { 'Content-Type': 'application/json' }, 329 body: JSON.stringify({ listing_id: listingId, terms_accepted: termsAccepted ? 1 : 0 }) 330 }).then(function(r) { 331 return r.json().then(function(data) { return { ok: r.ok, data: data }; }); 332 }).then(function(res) { 333 if (res.ok && res.data.success && res.data.checkout_url) { 334 window.location = res.data.checkout_url; 335 return; 336 } 337 alert(res.data && res.data.error ? res.data.error : 'Could not start lease. Please try again.'); 338 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 339 }).catch(function() { 340 alert('Network error. Please try again.'); 341 if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; } 342 }); 343 } 344 345 var ltoBtn = document.getElementById('ltoStartBtn'); 346 if (ltoBtn && !ltoBtn.disabled) { 347 ltoBtn.addEventListener('click', function() { openLtoTerms(ltoBtn); }); 348 } 349 350 // Auto-fire flows when landed with ?autobuy=1 / ?autolto=1 (from a parked-domain handoff). 351 function stripParam(name) { 352 try { 353 var pattern = new RegExp('([?&])' + name + '=1&?'); 354 var cleanUrl = window.location.pathname + window.location.search.replace(pattern, '$1').replace(/[?&]$/, '') + window.location.hash; 355 window.history.replaceState({}, '', cleanUrl); 356 } catch (e) { /* ignore */ } 357 } 358 if (buyBtn && !buyBtn.disabled && /[?&]autobuy=1\b/.test(window.location.search)) { 359 stripParam('autobuy'); 360 startBuyNowCheckout(buyBtn); 361 } 362 if (ltoBtn && !ltoBtn.disabled && /[?&]autolto=1\b/.test(window.location.search)) { 363 stripParam('autolto'); 364 startLtoCheckout(ltoBtn); 365 } 366 367 // ---- Make Offer / Inquiry modal ---- 368 var priceCard = document.querySelector('[data-card="price"]'); 369 var offerModal = document.getElementById('offerModal'); 370 371 function openOfferModal(mode) { 372 if (!priceCard || !offerModal) return; 373 374 // If served on a non-canonical host (seller's parked domain via catchall), 375 // show a handoff interstitial before bouncing to appraise.net so the 376 // origin change isn't a surprise to the buyer. 377 var canonicalUrl = priceCard.dataset.canonicalUrl; 378 if (canonicalUrl) { 379 try { 380 var canonicalHost = new URL(canonicalUrl).host; 381 if (canonicalHost && canonicalHost !== window.location.host) { 382 openHandoffModal(canonicalUrl, mode); 383 return; 384 } 385 } catch (e) { /* ignore parse errors */ } 386 } 387 388 if (priceCard.dataset.isLoggedIn !== '1') { 389 var returnTo = (canonicalUrl || window.location.href); 390 window.location = 'https://appraise.net/login?redirect=' + encodeURIComponent(returnTo); 391 return; 392 } 393 394 var isInquiry = mode === 'inquiry'; 395 var domain = priceCard.dataset.domain || 'this domain'; 396 var amountField = document.getElementById('offerAmountField'); 397 var msgInput = document.getElementById('offerMessageInput'); 398 var amountInput = document.getElementById('offerAmountInput'); 399 400 document.getElementById('offerModalTitle').textContent = isInquiry ? 'Inquire About Price' : 'Make an Offer'; 401 document.getElementById('offerModalSub').textContent = isInquiry 402 ? 'Send a question to the seller of ' + domain + '.' 403 : 'Submit an offer for ' + domain + '. You choose how long it stays open.'; 404 document.getElementById('offerSubmitLabel').textContent = isInquiry ? 'Send Inquiry' : 'Send Offer'; 405 406 amountField.hidden = isInquiry; 407 var expiryField = document.getElementById('offerExpiryField'); 408 if (expiryField) expiryField.hidden = isInquiry; 409 amountInput.value = ''; 410 msgInput.value = ''; 411 if (isInquiry) { 412 msgInput.placeholder = 'What would you like to ask the seller?'; 413 } else { 414 msgInput.placeholder = 'Add a note for the seller...'; 415 } 416 417 var err = document.getElementById('offerModalError'); 418 err.hidden = true; err.textContent = ''; 419 420 offerModal.hidden = false;
421 offerModal.dataset.mode = mode; 422 setTimeout(function() { 423 (isInquiry ? msgInput : amountInput).focus(); 424 }, 50); 425 } 426 427 function closeOfferModal() { 428 if (offerModal) offerModal.hidden = true; 429 } 430 431 function submitOffer() { 432 if (!offerModal || !priceCard) return; 433 var mode = offerModal.dataset.mode; 434 var isInquiry = mode === 'inquiry'; 435 var listingId = parseInt(priceCard.dataset.listingId, 10); 436 var amountInput = document.getElementById('offerAmountInput'); 437 var msgInput = document.getElementById('offerMessageInput'); 438 var submitBtn = document.getElementById('offerSubmitBtn'); 439 var err = document.getElementById('offerModalError'); 440 err.hidden = true; err.textContent = ''; 441 442 var amount = isInquiry ? 0 : parseFloat(amountInput.value); 443 var message = msgInput.value.trim(); 444 445 if (!isInquiry && (!amount || amount <= 0)) { 446 err.textContent = 'Enter a valid offer amount.'; 447 err.hidden = false; 448 return; 449 } 450 var minOffer = parseFloat(priceCard.dataset.minOfferPrice) || 0; 451 if (!isInquiry && minOffer > 0 && amount < minOffer) { 452 err.textContent = 'Minimum offer is $' + minOffer.toLocaleString(); 453 err.hidden = false; 454 return; 455 } 456 if (isInquiry && !message) { 457 err.textContent = 'Please type your question for the seller.'; 458 err.hidden = false; 459 return; 460 } 461 462 submitBtn.disabled = true; 463 var origLabel = document.getElementById('offerSubmitLabel').textContent; 464 document.getElementById('offerSubmitLabel').textContent = 'Sending...'; 465 466 fetch('/api/marketplace/deal', { 467 method: 'POST', 468 headers: { 'Content-Type': 'application/json' }, 469 body: JSON.stringify({ 470 action: 'create_offer', 471 listing_id: listingId, 472 amount: amount, 473 message: message, 474 inquiry: isInquiry, 475 expiry_days: parseInt((document.getElementById('offerExpiryInput') || {}).value, 10) || 14 476 }) 477 }).then(function(r) { return r.json(); }).then(function(data) { 478 if (data.success && data.thread_token) { 479 window.location = '/marketplace/deal/' + data.thread_token; 480 } else { 481 err.textContent = data.error || 'Could not submit. Please try again.'; 482 err.hidden = false; 483 submitBtn.disabled = false; 484 document.getElementById('offerSubmitLabel').textContent = origLabel; 485 } 486 }).catch(function() { 487 err.textContent = 'Network error. Please try again.'; 488 err.hidden = false; 489 submitBtn.disabled = false; 490 document.getElementById('offerSubmitLabel').textContent = origLabel; 491 }); 492 } 493 494 // Delegated handlers â survive artist-tool rebuilds of priceCardContent 495 document.addEventListener('click', function(e) { 496 if (!e.target.closest) return; 497 if (e.target.closest('#makeOfferBtn')) { openOfferModal('offer'); return; } 498 if (e.target.closest('#inquireBtn')) { openOfferModal('inquiry'); return; } 499 if (e.target.closest('.mobile-cta .btn-cta') && priceCard) { 500 var lt = priceCard.dataset.listingType; 501 // buy_now_or_offer's mobile CTA shows "Buy Now" as the primary action, 502 // so route it through the BIN flow just like a buy_now listing. 503 if (lt === 'buy_now' || lt === 'buy_now_or_offer') { 504 var bn = document.getElementById('buyNowBtn'); 505 if (bn && !bn.disabled) { startBuyNowCheckout(bn); } 506 else if (lt === 'buy_now_or_offer') { openOfferModal('offer'); } 507 return; 508 } 509 if (lt === 'make_offer') { openOfferModal('offer'); return; } 510 if (lt === 'inquiry') { openOfferModal('inquiry'); return; } 511 if (lt === 'lease_to_own') { var lto = document.getElementById('ltoStartBtn');
511 if (lto) lto.click(); } 512 } 513 }); 514 515 if (offerModal) { 516 offerModal.querySelectorAll('[data-offer-close]').forEach(function(el) { 517 el.addEventListener('click', closeOfferModal); 518 }); 519 document.getElementById('offerSubmitBtn').addEventListener('click', submitOffer); 520 document.addEventListener('keydown', function(e) { 521 if (e.key === 'Escape' && !offerModal.hidden) closeOfferModal(); 522 }); 523 } 524 525 // Handoff modal (non-canonical hosts) 526 var handoffModal = document.getElementById('handoffModal'); 527 528 function openHandoffModal(canonicalUrl, mode) { 529 if (!handoffModal) { window.location = canonicalUrl; return; } 530 var isInquiry = mode === 'inquiry'; 531 document.getElementById('handoffTitle').textContent = isInquiry ? 'Continue on Appraise.net' : 'Continue on Appraise.net'; 532 document.getElementById('handoffSub').innerHTML = isInquiry 533 ? "Inquiries are handled on <strong>Appraise.net Marketplace</strong>, where you can message the seller securely. You'll be taken there now and can return any time." 534 : "Offers are negotiated and paid securely on <strong>Appraise.net Marketplace</strong>. You'll be taken there to complete your offer â and you can return here any time."; 535 document.getElementById('handoffContinueBtn').setAttribute('href', canonicalUrl); 536 handoffModal.hidden = false; 537 } 538 539 function closeHandoffModal() { 540 if (handoffModal) handoffModal.hidden = true; 541 } 542 543 if (handoffModal) { 544 handoffModal.querySelectorAll('[data-handoff-close]').forEach(function(el) { 545 el.addEventListener('click', closeHandoffModal); 546 }); 547 document.addEventListener('keydown', function(e) { 548 if (e.key === 'Escape' && !handoffModal.hidden) closeHandoffModal(); 549 }); 550 } 551 552 // Seller: Toggle publish 553 var publishBtn = document.getElementById('togglePublishBtn'); 554 if (publishBtn) { 555 publishBtn.addEventListener('click', function() { 556 // TODO: POST to toggle status 557 alert('Publish toggle coming soon.'); 558 }); 559 } 560 561 // Seller: Delete 562 var deleteBtn = document.getElementById('deleteListingBtn'); 563 if (deleteBtn) { 564 deleteBtn.addEventListener('click', function() { 565 if (confirm('Are you sure you want to delete this listing? This cannot be undone.')) { 566 // TODO: POST to delete 567 alert('Delete coming soon.'); 568 } 569 }); 570 } 571 572 // Left sidebar visibility on 1280px+ 573 var leftSidebar = document.querySelector('.sticky-sidebar'); 574 if (leftSidebar && window.innerWidth >= 1280) { 575 leftSidebar.style.display = 'block'; 576 } 577 window.addEventListener('resize', function() { 578 if (leftSidebar && window.innerWidth >= 1280) { 579 leftSidebar.style.display = 'block'; 580 } 581 }); 582 // Certificate modal 583 var certModal = document.getElementById('certModal'); 584 if (certModal) { 585 var certIframe = document.getElementById('certIframe'); 586 var certBackdrop = certModal.querySelector('.cert-modal-backdrop'); 587 var certClose = certModal.querySelector('.cert-modal-close'); 588
589 var certLoading = certModal.querySelector('.cert-modal-loading'); 590 591 document.querySelectorAll('.cert-link').forEach(function(link) { 592 link.addEventListener('click', function(e) { 593 e.preventDefault(); 594 var certId = this.dataset.certId; 595 if (certId) { 596 if (certLoading) certLoading.style.display = 'flex'; 597 certIframe.style.opacity = '0'; 598 certIframe.src = 'https://appraise.net/certificate?id=' + encodeURIComponent(certId); 599 certModal.classList.add('open'); 600 } 601 }); 602 }); 603 604 certIframe.addEventListener('load', function() { 605 if (certLoading) certLoading.style.display = 'none'; 606 certIframe.style.opacity = '1'; 607 }); 608 609 function closeCertModal() { 610 certModal.classList.remove('open'); 611 certIframe.src = ''; 612 certIframe.style.opacity = '0'; 613 } 614 615 certBackdrop.addEventListener('click', closeCertModal); 616 certClose.addEventListener('click', closeCertModal); 617 document.addEventListener('keydown', function(e) { 618 if (e.key === 'Escape' && certModal.classList.contains('open')) closeCertModal(); 619 }); 620 } 621})(); 622 623/* ---- Nav brand-logo lightbox: the badge shows a cropped strip of the logo; click it to 624 see the full logo in an overlay (X, backdrop, the logo itself, or Esc all close). ---- */ 625(function() { 626 var badge = document.querySelector('.nav-brand-logo.logo-zoomable'); 627 var box = document.getElementById('logo-lightbox'); 628 if (!badge || !box) return; 629 var img = box.querySelector('.logo-lightbox-img'); 630 631 function openLogo() { 632 img.src = badge.getAttribute('data-logo-src') || ''; 633 img.alt = badge.getAttribute('data-logo-alt') || ''; 634 box.hidden = false; 635 document.addEventListener('keydown', onKey); 636 } 637 function closeLogo() { 638 box.hidden = true; 639 img.removeAttribute('src'); 640 document.removeEventListener('keydown', onKey); 641 } 642 function onKey(e) { if (e.key === 'Escape') closeLogo(); } 643 644 badge.addEventListener('click', function(e) { e.preventDefault(); openLogo(); }); 645 badge.addEventListener('keydown', function(e) { 646 if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openLogo(); } 647 }); 648 // Any click on the overlay (backdrop, close button, or the enlarged logo) dismisses it. 649 box.addEventListener('click', closeLogo); 650})();
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.