PageSourceSearch

https://budaq.com/js/marketplace-listing.js?v=1790340418

js budaq.com collected 2026-09-25 12:47:04 UTC 25,760 bytes, 589 lines download raw bytes

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    var btcBtn = document.getElementById('payBtcBtn');
196    if (btcBtn && !btcBtn.disabled) {
197        btcBtn.addEventListener('click', function() { startBtcCheckout(btcBtn); });
198        if (/[?&]autobtc=1/.test(window.location.search)) { startBtcCheckout(btcBtn); }
199    }
200
201    // US bank debit (ACH): no card surcharge; settles in a few business days
202    var bankBtn = document.getElementById('payBankBtn');
203    if (bankBtn && !bankBtn.disabled) {
204        bankBtn.addEventListener('click', function() { startBuyNowCheckout(bankBtn, 'bank'); });
205    }
206
207    // ---- Buy via Escrow.com ----
208    // Delegates to the shared modal in /js/marketplace-escrow-modal.js, which
209    // collects the buyer's Escrow.com email (with pre-fill / localStorage),
210    // then POSTs to escrow-checkout.php and redirects to the agreement URL.
211    function startEscrowCheckout(triggerBtn) {
212        if (!priceCard) return;
213
214        // Non-canonical host redirect — same pattern as Stripe Buy Now.
215        var canonicalUrl = priceCard.dataset.canonicalUrl;
216        try {
217            if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) {
218                window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autoescrow=1';
219                return;
220            }
221        } catch (e) { /* ignore */ }
222
223        var listingId = parseInt(priceCard.dataset.listingId, 10);
224        if (!listingId) return;
225
226        if (typeof window.openEscrowModal === 'function') {
227            window.openEscrowModal({
228                mode: 'listing',
229                listingId: listingId,
230                triggerBtn: triggerBtn
231            });
232        } else {
233            // Defensive fallback if the modal module wasn't loaded for some
234            // reason — should never hit in production.
235            alert('Escrow.com checkout is temporarily unavailable. Please try again or use card payment.');
236        }
237    }
238
239    var escrowBtn = document.getElementById('buyEscrowBtn');
240    if (escrowBtn && !escrowBtn.disabled) {
241        escrowBtn.addEventListener('click', function() { startEscrowCheckout(escrowBtn); });
242    }
243
244    // ---- LTO Start Lease (mirrors Buy Now but hits lto-checkout.php) ----
245    function startLtoCheckout(triggerBtn) {
246        if (!priceCard) return;
247
248        var canonicalUrl = priceCard.dataset.canonicalUrl;
249        try {
250            if (canonicalUrl && new URL(canonicalUrl).host !== window.location.host) {
251                window.location = canonicalUrl + (canonicalUrl.indexOf('?') === -1 ? '?' : '&') + 'autolto=1';
252                return;
253            }
254        } catch (e) { /* ignore */ }
255
256        var listingId = parseInt(priceCard.dataset.listingId, 10);
257        if (!listingId) return;
258
259        var origLabel = triggerBtn ? triggerBtn.innerHTML : '';
260        if (triggerBtn) {
261            triggerBtn.disabled = true;
262            triggerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Redirecting...';
263        }
264
265        fetch('/api/marketplace/lto-checkout', {
266            method: 'POST',
267            headers: { 'Content-Type': 'application/json' },
268            body: JSON.stringify({ listing_id: listingId })
269        }).then(function(r) {
270            return r.json().then(function(data) { return { ok: r.ok, data: data }; });
271        }).then(function(res) {
272            if (res.ok && res.data.success && res.data.checkout_url) {
273                window.location = res.data.checkout_url;
274                return;
275            }
276            alert(res.data && res.data.error ? res.data.error : 'Could not start lease. Please try again.');
277            if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; }
278        }).catch(function() {
279            alert('Network error. Please try again.');
280            if (triggerBtn) { triggerBtn.disabled = false; triggerBtn.innerHTML = origLabel; }
281        });
282    }
283
284    var ltoBtn = document.getElementById('ltoStartBtn');
285    if (ltoBtn && !ltoBtn.disabled) {
286        ltoBtn.addEventListener('click', function() { startLtoCheckout(ltoBtn); });
287    }
288
289    // Auto-fire flows when landed with ?autobuy=1 / ?autolto=1 (from a parked-domain handoff).
290    function stripParam(name) {
291        try {
292            var pattern = new RegExp('([?&])' + name + '=1&?');
293            var cleanUrl = window.location.pathname + window.location.search.replace(pattern, '$1').replace(/[?&]$/, '') + window.location.hash;
294            window.history.replaceState({}, '', cleanUrl);
295        } catch (e) { /* ignore */ }
296    }
297    if (buyBtn && !buyBtn.disabled && /[?&]autobuy=1\b/.test(window.location.search)) {
298        stripParam('autobuy');
299        startBuyNowCheckout(buyBtn);
300    }
301    if (ltoBtn && !ltoBtn.disabled && /[?&]autolto=1\b/.test(window.location.search)) {
302        stripParam('autolto');
303        startLtoCheckout(ltoBtn);
304    }
305
306    // ---- Make Offer / Inquiry modal ----
307    var priceCard = document.querySelector('[data-card="price"]');
308    var offerModal = document.getElementById('offerModal');
309
310    function openOfferModal(mode) {
311        if (!priceCard || !offerModal) return;
312
313        // If served on a non-canonical host (seller's parked domain via catchall),
314        // show a handoff interstitial before bouncing to appraise.net so the
315        // origin change isn't a surprise to the buyer.
316        var canonicalUrl = priceCard.dataset.canonicalUrl;
317        if (canonicalUrl) {
318            try {
319                var canonicalHost = new URL(canonicalUrl).host;
320                if (canonicalHost && canonicalHost !== window.location.host) {
321                    openHandoffModal(canonicalUrl, mode);
322                    return;
323                }
324            } catch (e) { /* ignore parse errors */ }
325        }
326
327        if (priceCard.dataset.isLoggedIn !== '1') {
328            var returnTo = (canonicalUrl || window.location.href);
329            window.location = 'https://appraise.net/login?redirect=' + encodeURIComponent(returnTo);
330            return;
331        }
332
333        var isInquiry = mode === 'inquiry';
334        var domain = priceCard.dataset.domain || 'this domain';
335        var amountField = document.getElementById('offerAmountField');
336        var msgInput = document.getElementById('offerMessageInput');
337        var amountInput = document.getElementById('offerAmountInput');
338
339        document.getElementById('offerModalTitle').textContent = isInquiry ? 'Inquire About Price' : 'Make an Offer';
340        document.getElementById('offerModalSub').textContent = isInquiry
341            ? 'Send a question to the seller of ' + domain + '.'
342            : 'Submit an offer for ' + domain + '. You choose how long it stays open.';
343        document.getElementById('offerSubmitLabel').textContent = isInquiry ? 'Send Inquiry' : 'Send Offer';
344
345        amountField.hidden = isInquiry;
346        var expiryField = document.getElementById('offerExpiryField');
347        if (expiryField) expiryField.hidden = isInquiry;
348        amountInput.value = '';
349        msgInput.value = '';
350        if (isInquiry) {
351            msgInput.placeholder = 'What would you like to ask the seller?';
352        } else {
353            msgInput.placeholder = 'Add a note for the seller...';
354        }
355
356        var err = document.getElementById('offerModalError');
357        err.hidden = true; err.textContent = '';
358
359        offerModal.hidden = false;
360        offerModal.dataset.mode = mode;
361        setTimeout(function() {
362            (isInquiry ? msgInput : amountInput).focus();
363        }, 50);
364    }
365
366    function closeOfferModal() {
367        if (offerModal) offerModal.hidden = true;
368    }
369
370    function submitOffer() {
371        if (!offerModal || !priceCard) return;
372        var mode = offerModal.dataset.mode;
373        var isInquiry = mode === 'inquiry';
374        var listingId = parseInt(priceCard.dataset.listingId, 10);
375        var amountInput = document.getElementById('offerAmountInput');
376        var msgInput = document.getElementById('offerMessageInput');
377        var submitBtn = document.getElementById('offerSubmitBtn');
378        var err = document.getElementById('offerModalError');
379        err.hidden = true; err.textContent = '';
380
381        var amount = isInquiry ? 0 : parseFloat(amountInput.value);
382        var message = msgInput.value.trim();
383
384        if (!isInquiry && (!amount || amount <= 0)) {
385            err.textContent = 'Enter a valid offer amount.';
386            err.hidden = false;
387            return;
388        }
389        var minOffer = parseFloat(priceCard.dataset.minOfferPrice) || 0;
390        if (!isInquiry && minOffer > 0 && amount < minOffer) {
391            err.textContent = 'Minimum offer is $' + minOffer.toLocaleString();
392            err.hidden = false;
393            return;
394        }
395        if (isInquiry && !message) {
396            err.textContent = 'Please type your question for the seller.';
397            err.hidden = false;
398            return;
399        }
400
401        submitBtn.disabled = true;
402        var origLabel = document.getElementById('offerSubmitLabel').textContent;
403        document.getElementById('offerSubmitLabel').textContent = 'Sending...';
404
405        fetch('/api/marketplace/deal', {
406            method: 'POST',
407            headers: { 'Content-Type': 'application/json' },
408            body: JSON.stringify({
409                action: 'create_offer',
410                listing_id: listingId,
411                amount: amount,
412                message: message,
413                inquiry: isInquiry,
414                expiry_days: parseInt((document.getElementById('offerExpiryInput') || {}).value, 10) || 14
415            })
416        }).then(function(r) { return r.json(); }).then(function(data) {
417            if (data.success && data.thread_token) {
418                window.location = '/marketplace/deal/' + data.thread_token;
419            } else {
420                err.textContent = data.error || 'Could not submit. Please try again.';
421                err.hidden = false;
422                submitBtn.disabled = false;
423                document.getElementById('offerSubmitLabel').textContent = origLabel;
424            }
425        }).catch(function() {
426            err.textContent = 'Network error. Please try again.';
427            err.hidden = false;
428            submitBtn.disabled = false;
429            document.getElementById('offerSubmitLabel').textContent = origLabel;
430        });
431    }
432
433    // Delegated handlers — survive artist-tool rebuilds of priceCardContent
434    document.addEventListener('click', function(e) {
435        if (!e.target.closest) return;
436        if (e.target.closest('#makeOfferBtn')) { openOfferModal('offer'); return; }
437        if (e.target.closest('#inquireBtn')) { openOfferModal('inquiry'); return; }
438        if (e.target.closest('.mobile-cta .btn-cta') && priceCard) {
439            var lt = priceCard.dataset.listingType;
440            // buy_now_or_offer's mobile CTA shows "Buy Now" as the primary action,
441            // so route it through the BIN flow just like a buy_now listing.
442            if (lt === 'buy_now' || lt === 'buy_now_or_offer') {
443                var bn = document.getElementById('buyNowBtn');
444                if (bn && !bn.disabled) { startBuyNowCheckout(bn); }
445                else if (lt === 'buy_now_or_offer') { openOfferModal('offer'); }
446                return;
447            }
448            if (lt === 'make_offer') { openOfferModal('offer'); return; }
449            if (lt === 'inquiry') { openOfferModal('inquiry'); return; }
450            if (lt === 'lease_to_own') { var lto = document.getElementById('ltoStartBtn'); if (lto) lto.click(); }
451        }
452    });
453
454    if (offerModal) {
455        offerModal.querySelectorAll('[data-offer-close]').forEach(function(el) {
456            el.addEventListener('click', closeOfferModal);
457        });
458        document.getElementById('offerSubmitBtn').addEventListener('click', submitOffer);
459        document.addEventListener('keydown', function(e) {
460            if (e.key === 'Escape' && !offerModal.hidden) closeOfferModal();
461        });
462    }
463
464    // Handoff modal (non-canonical hosts)
465    var handoffModal = document.getElementById('handoffModal');
466
467    function openHandoffModal(canonicalUrl, mode) {
468        if (!handoffModal) { window.location = canonicalUrl; return; }
469        var isInquiry = mode === 'inquiry';
470        document.getElementById('handoffTitle').textContent = isInquiry ? 'Continue on Appraise.net' : 'Continue on Appraise.net';
471        document.getElementById('handoffSub').innerHTML = isInquiry
472            ? "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."
473            : "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.";
474        document.getElementById('handoffContinueBtn').setAttribute('href', canonicalUrl);
475        handoffModal.hidden = false;
476    }
477
478    function closeHandoffModal() {
479        if (handoffModal) handoffModal.hidden = true;
480    }
481
482    if (handoffModal) {
483        handoffModal.querySelectorAll('[data-handoff-close]').forEach(function(el) {
484            el.addEventListener('click', closeHandoffModal);
485        });
486        document.addEventListener('keydown', function(e) {
487            if (e.key === 'Escape' && !handoffModal.hidden) closeHandoffModal();
488        });
489    }
490
491    // Seller: Toggle publish
492    var publishBtn = document.getElementById('togglePublishBtn');
493    if (publishBtn) {
494        publishBtn.addEventListener('click', function() {
495            // TODO: POST to toggle status
496            alert('Publish toggle coming soon.');
497        });
498    }
499
500    // Seller: Delete
501    var deleteBtn = document.getElementById('deleteListingBtn');
502    if (deleteBtn) {
503        deleteBtn.addEventListener('click', function() {
504            if (confirm('Are you sure you want to delete this listing? This cannot be undone.')) {
505                // TODO: POST to delete
506                alert('Delete coming soon.');
507            }
508        });
509    }
510
511    // Left sidebar visibility on 1280px+
512    var leftSidebar = document.querySelector('.sticky-sidebar');
513    if (leftSidebar && window.innerWidth >= 1280) {
514        leftSidebar.style.display = 'block';
515    }
516    window.addEventListener('resize', function() {
517        if (leftSidebar && window.innerWidth >= 1280) {
518            leftSidebar.style.display = 'block';
519        }
520    });
521    // Certificate modal
522    var certModal = document.getElementById('certModal');
523    if (certModal) {
524        var certIframe = document.getElementById('certIframe');
525        var certBackdrop = certModal.querySelector('.cert-modal-backdrop');
526        var certClose = certModal.querySelector('.cert-modal-close');
527
528        var certLoading = certModal.querySelector('.cert-modal-loading');
529
530        document.querySelectorAll('.cert-link').forEach(function(link) {
531            link.addEventListener('click', function(e) {
532                e.preventDefault();
533                var certId = this.dataset.certId;
534                if (certId) {
535                    if (certLoading) certLoading.style.display = 'flex';
536                    certIframe.style.opacity = '0';
537                    certIframe.src = 'https://appraise.net/certificate?id=' + encodeURIComponent(certId);
538                    certModal.classList.add('open');
539                }
540            });
541        });
542
543        certIframe.addEventListener('load', function() {
544            if (certLoading) certLoading.style.display = 'none';
545            certIframe.style.opacity = '1';
546        });
547
548        function closeCertModal() {
549            certModal.classList.remove('open');
550            certIframe.src = '';
551            certIframe.style.opacity = '0';
552        }
553
554        certBackdrop.addEventListener('click', closeCertModal);
555        certClose.addEventListener('click', closeCertModal);
556        document.addEventListener('keydown', function(e) {
557            if (e.key === 'Escape' && certModal.classList.contains('open')) closeCertModal();
558        });
559    }
560})();
561
562/* ---- Nav brand-logo lightbox: the badge shows a cropped strip of the logo; click it to
563        see the full logo in an overlay (X, backdrop, the logo itself, or Esc all close). ---- */
564(function() {
565    var badge = document.querySelector('.nav-brand-logo.logo-zoomable');
566    var box = document.getElementById('logo-lightbox');
567    if (!badge || !box) return;
568    var img = box.querySelector('.logo-lightbox-img');
569
570    function openLogo() {
571        img.src = badge.getAttribute('data-logo-src') || '';
572        img.alt = badge.getAttribute('data-logo-alt') || '';
573        box.hidden = false;
574        document.addEventListener('keydown', onKey);
575    }
576    function closeLogo() {
577        box.hidden = true;
578        img.removeAttribute('src');
579        document.removeEventListener('keydown', onKey);
580    }
581    function onKey(e) { if (e.key === 'Escape') closeLogo(); }
582
583    badge.addEventListener('click', function(e) { e.preventDefault(); openLogo(); });
584    badge.addEventListener('keydown', function(e) {
585        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openLogo(); }
586    });
587    // Any click on the overlay (backdrop, close button, or the enlarged logo) dismisses it.
588    box.addEventListener('click', closeLogo);
589})();

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.