1(function () { 2 'use strict'; 3 4 var config = window.KLOTrackingConfig || {}; 5 if (!config.relayUrl) return; 6 7 var observed = new WeakSet(); 8 var pageSeen = new Set(); 9 var impressionTimers = new WeakMap(); 10 var storageKey = 'klo_tracking_visitor_v1'; 11 var detailKey = 'klo_tracking_detail_source_v1'; 12 13 function visitorToken() { 14 try { 15 var existing = window.localStorage.getItem(storageKey); 16 if (existing) return existing; 17 var token = ''; 18 if (window.crypto && typeof window.crypto.randomUUID === 'function') { 19 token = window.crypto.randomUUID().replace(/-/g, ''); 20 } else if (window.crypto && window.crypto.getRandomValues) { 21 var bytes = new Uint8Array(16); 22 window.crypto.getRandomValues(bytes); 23 token = Array.prototype.map.call(bytes, function (value) { 24 return value.toString(16).padStart(2, '0'); 25 }).join(''); 26 } else { 27 token = String(Date.now()) + Math.random().toString(36).slice(2); 28 } 29 window.localStorage.setItem(storageKey, token); 30 return token; 31 } catch (error) { 32 return ''; 33 } 34 } 35 36 function pageContext() { 37 return window.location.pathname || '/'; 38 } 39 40 function validId(value) { 41 var id = Number(value); 42 return Number.isInteger(id) && id > 0 ? id : 0; 43 } 44 45 function send(eventType, entityId, placement) { 46 entityId = validId(entityId); 47 if (!entityId || !eventType || !placement) return; 48 49 var payload = JSON.stringify({ 50 entity_id: entityId, 51 event_type: eventType, 52 placement: placement, 53 page_context: pageContext(), 54 visitor_token: visitorToken() 55 }); 56 57 if (navigator.sendBeacon) { 58 try { 59 var blob = new Blob([payload], { type: 'application/json' }); 60 if (navigator.sendBeacon(config.relayUrl, blob)) return; 61 } catch (error) {} 62 } 63 64 window.fetch(config.relayUrl, { 65 method: 'POST', 66 headers: { 'Content-Type': 'application/json' }, 67 credentials: 'same-origin', 68 keepalive: true, 69 body: payload 70 }).catch(function () {}); 71 } 72 73 window.KLOTracking = window.KLOTracking || {}; 74 window.KLOTracking.track = function (eventType, entityId, placement) { 75 send(eventType, entityId, placement || 'more_info'); 76 }; 77 78 function elementPlacement(element, fallback) { 79 if (!element) return fallback || ''; 80 if (element.dataset && element.dataset.kloTrackPlacement) return element.dataset.kloTrackPlacement; 81 var parent = element.closest ? element.closest('[data-klo-track-placement]') : null; 82 return parent ? (parent.dataset.kloTrackPlacement || fallback || '') : (fallback || ''); 83 } 84 85 function elementEntityId(element) { 86 if (!element) return 0; 87 if (element.dataset && element.dataset.kloTrackEntityId) return validId(element.dataset.kloTrackEntityId); 88 if (element.dataset && element.dataset.entityId) return validId(element.dataset.entityId); 89 var parent = element.closest ? element.closest('[data-entity-id]') : null; 90 return parent ? validId(parent.dataset.entityId) : 0; 91 } 92 93 function impressionKey(entityId, placement) { 94 return String(entityId) + '|' + String(placement); 95 } 96 97 function observeElement(element) { 98 if (!element || observed.has(element)) return; 99 observed.add(element); 100 observer.observe(element); 101 } 102 103 var observer = new IntersectionObserver(function (entries) { 104 entries.forEach(function (entry) { 105 var element = entry.target; 106 var placement = element.dataset.kloTrackImpression || elementPlacement(element, ''); 107 var entityId = elementEntityId(element); 108 var key = impressionKey(entityId, placement); 109 110 if (!entityId || !placement || pageSeen.has(key)) { 111 observer.unobserve(element); 112 return; 113 } 114 115 if (entry.isIntersecting && entry.intersectionRatio >= Number(config.observerRatio || 0.25)) { 116 if (impressionTimers.has(element)) return; 117 impressionTimers.set(element, window.setTimeout(function () { 118 impressionTimers.delete(element); 119 if (pageSeen.has(key)) return; 120 pageSeen.add(key);
121 send('impression', entityId, placement); 122 observer.unobserve(element); 123 }, Number(config.observerDelay || 500))); 124 } else if (impressionTimers.has(element)) { 125 window.clearTimeout(impressionTimers.get(element)); 126 impressionTimers.delete(element); 127 } 128 }); 129 }, { threshold: [0, Number(config.observerRatio || 0.25), 0.5, 1] }); 130 131 function scan(root) { 132 var scope = root && root.querySelectorAll ? root : document; 133 scope.querySelectorAll('[data-klo-track-impression][data-entity-id]').forEach(observeElement); 134 if (scope.matches && scope.matches('[data-klo-track-impression][data-entity-id]')) observeElement(scope); 135 } 136 137 function rememberDetailSource(link) { 138 var entityId = elementEntityId(link); 139 var placement = elementPlacement(link, ''); 140 if (!entityId || !placement) return; 141 try { 142 window.sessionStorage.setItem(detailKey, JSON.stringify({ 143 entityId: entityId, 144 placement: placement, 145 at: Date.now() 146 })); 147 } catch (error) {} 148 } 149 150 function detailView() { 151 var detail = document.querySelector('.klo-entity-detail[data-entity-id]'); 152 if (!detail) return; 153 var entityId = validId(detail.dataset.entityId); 154 var placement = 'more_info'; 155 156 try { 157 var pending = JSON.parse(window.sessionStorage.getItem(detailKey) || 'null'); 158 if (pending && Number(pending.entityId) === entityId && Date.now() - Number(pending.at || 0) < 120000) { 159 placement = pending.placement || placement; 160 } 161 window.sessionStorage.removeItem(detailKey); 162 } catch (error) {} 163 164 send('detail_view', entityId, placement); 165 } 166 167 function addUtm(link, entityId, placement) { 168 try { 169 var url = new URL(link.href, window.location.href); 170 url.searchParams.set('utm_source', config.utmSource || 'kentuckylake.com'); 171 url.searchParams.set('utm_medium', config.utmMedium || 'referral'); 172 url.searchParams.set('utm_campaign', config.utmCampaign || 'expmg_partner'); 173 url.searchParams.set('utm_content', 'entity_' + entityId + '_' + placement); 174 link.href = url.toString(); 175 } catch (error) {} 176 } 177 178 document.addEventListener('click', function (event) { 179 var detailLink = event.target.closest('[data-klo-track-detail-link]'); 180 if (detailLink) rememberDetailSource(detailLink); 181 182 var tracked = event.target.closest('[data-klo-track-event]'); 183 if (!tracked) return; 184 185 var eventType = tracked.dataset.kloTrackEvent || ''; 186 var entityId = elementEntityId(tracked); 187 var placement = elementPlacement(tracked, 'more_info'); 188 if (!entityId || !eventType || !placement) return; 189 190 if (eventType === 'website_click') addUtm(tracked, entityId, placement); 191 send(eventType, entityId, placement); 192 }, true); 193 194 document.addEventListener('klo:planner:entity-added', function (event) { 195 var detail = event.detail || {}; 196 send('planner_add', detail.entityId, detail.placement || 'more_info'); 197 }); 198 199 document.addEventListener('klo:tracking:map-impression', function (event) { 200 var detail = event.detail || {}; 201 var entityId = validId(detail.entityId); 202 var key = impressionKey(entityId, 'map'); 203 if (!entityId || pageSeen.has(key)) return; 204 pageSeen.add(key); 205 send('impression', entityId, 'map'); 206 }); 207 208 function initTracking() { 209 scan(document); 210 detailView(); 211 212 if (!document.body) return; 213 var mutationObserver = new MutationObserver(function (mutations) { 214 mutations.forEach(function (mutation) { 215 mutation.addedNodes.forEach(function (node) { 216 if (node.nodeType === 1) scan(node); 217 }); 218 }); 219 }); 220 mutationObserver.observe(document.body, { childList: true, subtree: true }); 221 } 222 223 if (document.readyState === 'loading') { 224 document.addEventListener('DOMContentLoaded', initTracking, { once: true }); 225 } else { 226 initTracking(); 227 } 228})();
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.