1/* AstroReport first-touch attribution (2026-09-07). Same contract as the 2 webapp's src/lib/attribution.ts: records utm_* / src / ref / click ids / 3 referrer / landing path ONCE (first touch wins, 90 days) in localStorage and 4 in the same-domain cookie `ar_attr`, so a click from an email or an ad that 5 lands here still reaches the webapp at astroreport.ai/users/... intact. */ 6(function () { 7 try { 8 var KEY = 'ar_attr_v1', COOKIE = 'ar_attr', TTL = 90 * 24 * 3600 * 1000; 9 var KEYS = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','src','ref','source','gclid','fbclid','ttclid','msclkid']; 10 function parse(raw) { 11 if (!raw) return null; 12 try { var v = JSON.parse(raw); if (!v || typeof v !== 'object') return null; 13 var at = Date.parse(v.first_touch_at || ''); if (isFinite(at) && Date.now() - at > TTL) return null; return v; } catch (e) { return null; } 14 } 15 function cookie(name) { var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); return m ? decodeURIComponent(m[1]) : null; } 16 var existing = null; 17 try { existing = parse(localStorage.getItem(KEY)); } catch (e) {} 18 if (!existing) existing = parse(cookie(COOKIE)); 19 var params = new URLSearchParams(location.search), fresh = {}, has = false; 20 for (var i = 0; i < KEYS.length; i++) { var v = params.get(KEYS[i]); if (v && v.trim()) { fresh[KEYS[i]] = v.trim().slice(0, 200); has = true; } } 21 var out = null; 22 if (existing) { 23 if (has && fresh.utm_source && !existing.utm_source) { out = Object.assign({}, fresh, existing, { last_touch_at: new Date().toISOString(), last_utm_source: fresh.utm_source, last_utm_campaign: fresh.utm_campaign }); } 24 else return; 25 } else { 26 var referrer = ''; 27 try { if (document.referrer) { var r = new URL(document.referrer); if (!/astroreport\.ai$|localhost/i.test(r.hostname)) referrer = document.referrer.slice(0, 200); } } catch (e) {} 28 if (!has && !referrer) return; 29 out = Object.assign({}, fresh, { landing_path: (location.pathname + (location.search ? location.search.slice(0, 120) : '')).slice(0, 200), first_touch_at: new Date().toISOString() }); 30 if (referrer) out.landing_referrer = referrer; 31 } 32 var json = JSON.stringify(out); 33 try { localStorage.setItem(KEY, json); } catch (e) {} 34 try { document.cookie = COOKIE + '=' + encodeURIComponent(json) + '; Path=/; Max-Age=' + Math.floor(TTL / 1000) + '; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : ''); } catch (e) {} 35 } catch (e) {} 36})();
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.