1(function(){ 2 const LS_KEY = 'bol_geo_v2'; // {lat,lng,source,ts} 3 const MAX_AGE_MS = 7*24*60*60*1000; 4 5 function getCached() { 6 try { 7 const v = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); 8 if (!v) return null; 9 if (Date.now() - (v.ts||0) > MAX_AGE_MS) return null; 10 return v; 11 } catch(e){ return null; } 12 } 13 14 function cacheLoc(lat, lng, source) { 15 localStorage.setItem(LS_KEY, JSON.stringify({lat:+lat, lng:+lng, source, ts: Date.now()})); 16 document.dispatchEvent(new CustomEvent('bol:geo:ready', {detail:{lat:+lat, lng:+lng, source}})); 17 } 18 19 function tryBrowser() { 20 return new Promise((resolve, reject)=>{ 21 if (!navigator.geolocation) return reject('no-geo'); 22 // short timeout, no high accuracy (less friction, faster) 23 const opts = { enableHighAccuracy: false, timeout: 1500, maximumAge: 24*60*60*1000 }; 24 navigator.geolocation.getCurrentPosition( 25 pos => resolve({lat: pos.coords.latitude, lng: pos.coords.longitude, source: 'browser'}), 26 _err => reject('denied-or-timeout'), 27 opts 28 ); 29 }); 30 } 31 32 function tryServerApprox() { 33 return fetch('/wp-json/bol/v1/approx', {credentials:'same-origin'}) 34 .then(r=>r.json()) 35 .then(j=>({lat:j.lat, lng:j.lng, source:j.source||'approx'})); 36 } 37 38 async function init() { 39 const cached = getCached(); 40 if (cached) { 41 document.dispatchEvent(new CustomEvent('bol:geo:ready', {detail:cached})); 42 // refresh in background if stale-ish (> 1 day) 43 if (Date.now() - cached.ts > 24*60*60*1000) { 44 Promise.any([tryBrowser(), tryServerApprox()]).then(v=>cacheLoc(v.lat,v.lng,v.source)).catch(()=>{}); 45 } 46 return; 47 } 48 // race both; whichever is first wins 49 try { 50 const winner = await Promise.any([tryBrowser(), tryServerApprox()]); 51 cacheLoc(winner.lat, winner.lng, winner.source); 52 } catch(e){ 53 // still nothing? set US centroid to keep UI predictable 54 cacheLoc(39.8283, -98.5795, 'fallback'); 55 } 56 } 57 58 init(); 59 60 // Expose getter for other scripts 61 window.BOL_GEO = { 62 get: ()=> getCached(), 63 refresh: ()=> localStorage.removeItem(LS_KEY) 64 }; 65})(); 66 67(function(){ 68 function injectPills(root) { 69 var scope = root || document; 70 var wraps = scope.querySelectorAll('.bol-card-wrap[data-bol-distance]:not([data-bol-pill])'); 71 wraps.forEach(function (wrap) { 72 var dist = wrap.getAttribute('data-bol-distance'); 73 if (!dist) return; 74 75 // Find a likely image wrapper on these cards 76 var img = wrap.querySelector('.bol-card-image, .bol-card__image, .bol-img, .card-image'); 77 if (!img) return; 78 79 wrap.setAttribute('data-bol-pill','1'); 80 81 // Ensure positioned container for absolute pill 82 var pos = window.getComputedStyle(img).position; 83 if (!pos || pos === 'static') img.style.position = 'relative'; 84 85 var pill = document.createElement('span'); 86 pill.className = 'bol-pill bol-pill--distance'; 87 pill.setAttribute('aria-label', 'Distance'); 88 pill.textContent = dist; 89 img.appendChild(pill); 90 }); 91 } 92 93 // Run now (even if DOMContentLoaded already fired) 94 if (document.readyState === 'loading') { 95 document.addEventListener('DOMContentLoaded', function(){ injectPills(document); }); 96 } else { 97 injectPills(document); 98 } 99 100 // Also listen for a custom event weâll fire after nearby sections render 101 document.addEventListener('bol:nearby:render', function(e){ 102 injectPills((e.detail && e.detail.root) || document); 103 }); 104})(); 105 106(function(){ 107 // Haversine in km 108 function hav(lat1, lon1, lat2, lon2){ 109 var R=6371, dLat=(lat2-lat1)*Math.PI/180, dLon=(lon2-lon1)*Math.PI/180; 110 var a=Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLon/2)**2; 111 return 2*R*Math.asin(Math.min(1, Math.sqrt(a))); 112 } 113 function toMiles(km){ return Math.round(km*0.621371*10)/10; } 114 115 function updateGrid(root, user){ 116 if (!root || !user || typeof user.lat!=='number' || typeof user.lng!=='number') return; 117 var cards = Array.from(root.querySelectorAll('.bol-card-wrap[data-lat][data-lng]')); 118 if (!cards.length) return; 119 120 cards.forEach(function(card){ 121 var lat = parseFloat(card.getAttribute('data-lat')); 122 var lng = parseFloat(card.getAttribute('data-lng')); 123 if (!isFinite(lat) || !isFinite(lng)) return; 124 125 var dkm = hav(user.lat, user.lng, lat, lng); 126 card.dataset.dkmbol = dkm.toFixed(1); 127 128 // Update the pill on the image wrapper 129 var img = card.querySelector('.bol-card-image, .bol-card__image, .card-image, .bol-img'); 130 if (img){ 131 if (getComputedStyle(img).position === 'static') img.style.position = 'relative'; 132 img.setAttribute('data-bol-distance', toMiles(dkm)+'mi'); 133 } 134 }); 135 136 // sort by distance and re-append 137 cards.sort(function(a,b){ 138 var da = parseFloat(a.dataset.dkmbol||'1e9'); 139 var db = parseFloat(b.dataset.dkmbol||'1e9'); 140 return da - db; 141 }
141).forEach(function(c){ root.appendChild(c); }); 142 } 143 144 function fetchApprox(){ 145 return fetch('/wp-json/bol/v1/approx', {credentials:'same-origin'}) 146 .then(function(r){ return r.json(); }) 147 .then(function(j){ 148 if (j && typeof j.lat==='number' && typeof j.lng==='number') return j; 149 return null; 150 }) 151 .catch(function(){ return null; }); 152 } 153 154 function init(){ 155 var grids = Array.from(document.querySelectorAll('.bol-grid[data-geo-sort="distance"]')); 156 if (!grids.length) return; 157 fetchApprox().then(function(user){ 158 if (!user) return; 159 grids.forEach(function(g){ updateGrid(g, user); }); 160 }); 161 } 162 163 if (document.readyState === 'loading') { 164 document.addEventListener('DOMContentLoaded', init); 165 } else { init(); } 166})();
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.