1(function () { 2 var d = document, w = window, s = d.currentScript; 3 if (!s || w.__lamp) return; 4 w.__lamp = 1; 5 6 var site = s.getAttribute("data-site"); 7 var ep = s.getAttribute("data-endpoint") || "/api/e"; 8 if (!site) return; 9 if (navigator.webdriver) return; 10 if (location.hostname === "localhost" || location.hostname === "127.0.0.1") return; 11 12 var maxScroll = 0, flushed = false; 13 14 function send(body, beacon) { 15 body.s = site; 16 body.p = location.pathname + location.search; 17 var json = JSON.stringify(body); 18 // text/plain, never application/json: a site with no /api proxy (Shopify) 19 // posts to the Worker cross-origin, and a JSON content type makes the 20 // browser preflight it and drop the pageview. collect() parses either. 21 try { 22 if (beacon && navigator.sendBeacon) { 23 navigator.sendBeacon(ep, new Blob([json], { type: "text/plain" })); 24 } else { 25 fetch(ep, { 26 method: "POST", 27 body: json, 28 keepalive: true, 29 headers: { "Content-Type": "text/plain" } 30 })["catch"](function () {}); 31 } 32 } catch (e) {} 33 } 34 35 function pageview() { 36 maxScroll = 0; 37 flushed = false; 38 send({ t: "pageview", r: d.referrer || "" }); 39 } 40 41 function track(name, meta) { 42 send({ t: "event", n: name, m: meta || null }); 43 } 44 45 w.lamp = track; 46 47 addEventListener("scroll", function () { 48 var h = d.documentElement.scrollHeight - innerHeight; 49 var pct = h > 0 ? Math.min(100, Math.round((scrollY / h) * 100)) : 100; 50 if (pct > maxScroll) maxScroll = pct; 51 }, { passive: true }); 52 53 function flush() { 54 if (flushed || maxScroll <= 0) return; 55 flushed = true; 56 send({ t: "scroll", v: maxScroll }, true); 57 } 58 59 addEventListener("pagehide", flush); 60 d.addEventListener("visibilitychange", function () { 61 if (d.visibilityState === "hidden") flush(); 62 }); 63 64 d.addEventListener("click", function (e) { 65 var t = e.target; 66 if (!t || !t.closest) return; 67 var el = t.closest("a, button, [data-track]"); 68 if (!el) return; 69 70 var named = el.getAttribute("data-track"); 71 if (named) { track(named); return; } 72 73 if (el.tagName !== "A") return; 74 var href = el.getAttribute("href") || ""; 75 76 if (/^tel:/i.test(href)) track("Phone number clicked", href.slice(4)); 77 else if (/^mailto:/i.test(href)) track("Email address clicked"); 78 else if (/^https?:/i.test(href) && el.hostname && el.hostname !== location.hostname) { 79 track("Outbound link", el.hostname); 80 } 81 }, true); 82 83 d.addEventListener("submit", function (e) { 84 var f = e.target; 85 if (!f || f.tagName !== "FORM") return; 86 track(f.getAttribute("data-track") || "Form submitted", 87 f.getAttribute("name") || f.getAttribute("id") || null); 88 }, true); 89 90 // Third-party booking and form widgets sit in cross-origin iframes, so a 91 // completed booking is invisible from out here â except that several providers 92 // announce one by postMessage. Listen for those, keyed on the sending origin. 93 // 94 // Deliberately always on rather than a per-site flag: a site with no Calendly 95 // never receives a Calendly message, so this costs nothing on the other 66 and 96 // needs no snippet change to switch on. The origin is the whole security model, 97 // so it is matched exactly and never by substring â "calendly.com.evil.net" 98 // must not pass. Only known event names are honoured, and only the provider 99 // name and its own record id ever ride along in meta: no form contents. 100 var BRIDGES = { 101 "https://calendly.com": function (m) { 102 // widget.js emits calendly.* through the booking flow; only the last one 103 // means an appointment actually exists. 104 if (m && m.event === "calendly.event_scheduled") return ["Booking scheduled", "Calendly"]; 105 return null; 106 }, 107 "https://go.movegistics.com": function (m) { 108 if (m && m.event === "movegistics_generate_lead") { 109 var id = m.lead_data && m.lead_data.id; 110 return ["Quote request sent", id ? "Movegistics · " + id : "Movegistics"]; 111 } 112 return null; 113 }, 114 // Off The Couch (clueclock's booking) emits a GA4-shaped ecommerce funnel on 115 // e.data.eventType: page_view, add_to_cart, begin_checkout, purchase, success. 116 // 117 // Only "purchase" is taken. It is the standard name for a completed 118 // transaction, and "success" appears to be its twin â recording both would 119 // count one booking as two leads, which is the one error worth avoiding in a 120 // number a client is shown. If real bookings turn out to announce themselves
121 // only as "success", swap the name here rather than accepting both. 122 "https://offthecouch.io": function (m) { 123 if (m && m.eventType === "purchase") { 124 var v = m.eventValue; 125 return ["Booking paid", "Off The Couch" + 126 (v ? " · " + (m.currency || "") + " " + v : "")]; 127 } 128 return null; 129 }, 130 // GoHighLevel / LeadConnector form embeds. Unlike the others this arrives as 131 // an ARRAY, not an object: 132 // 133 // ["set-sticky-contacts", "_ud", "<the whole contact as JSON>", 134 // "<location id>", "<form id>"] 135 // 136 // A companion message follows immediately with "embedded_iframe_<id>" in 137 // slot 1 instead of "_ud". Matching on slot 0 alone would record one 138 // submission as two, so the "_ud" check is load-bearing rather than cosmetic. 139 // 140 // **Slot 2 is the entire submitted record** â name, email, phone, address and 141 // every answer on the form. None of it is read. The rule against putting form 142 // contents in meta exists for exactly this shape, and this is the strongest 143 // case for it on the fleet: it is the same material we refuse to copy out of 144 // Netlify, offered for free by a widget. 145 // 146 // The name is alarming and was checked rather than assumed: 147 // "set-sticky-contacts" is GoHighLevel's form pre-fill feature, so if it also 148 // fired on page *load* for a contact the platform recognises, every returning 149 // visitor opening the page would record an enquiry they never sent. 150 // 151 // **Measured on allthingscakeco 2026-08-02**, against the live widget: two 152 // submissions each emitted it, and a reload of the same page in the same 153 // browser â by then a known contact, which is the case that would trigger a 154 // pre-fill â emitted nothing at all. It fires on submit only. 155 // 156 // Worth re-checking if GoHighLevel ever changes the embed, because the 157 // failure would be silent and upward: a lead figure that climbs on page views 158 // looks like success. 159 "https://api.leadconnectorhq.com": function (m) { 160 if (m && m.length === 5 && m[0] === "set-sticky-contacts" && m[1] === "_ud") { 161 return ["Quote request sent", "GoHighLevel"]; 162 } 163 return null; 164 } 165 }; 166 167 addEventListener("message", function (e) { 168 var read = BRIDGES[e.origin]; 169 if (!read) return; 170 var hit; 171 try { hit = read(e.data); } catch (err) { return; } 172 if (hit) track(hit[0], hit[1]); 173 }); 174 175 var push = history.pushState; 176 if (push) { 177 history.pushState = function () { 178 push.apply(this, arguments); 179 flush(); 180 pageview(); 181 }; 182 addEventListener("popstate", function () { flush(); pageview(); }); 183 } 184 185 pageview(); 186})();
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.