1/* 2 * ARIE Finance â GA4 / GTM event layer (dataLayer only). 3 * 4 * Loads only on the marketing single-page site (index.html); utility 5 * routes (/salestracker, /managementreport, /internal/*, /logout) do 6 * not include this file, so no analytics is pushed from those pages. 7 * 8 * Consent Mode: Advanced. Before explicit acceptance analytics_storage 9 * is denied and Google may send cookieless pings; the site's cookie 10 * banner handles the explicit accept / essential-only flip. 11 * Advertising storage/consent is never granted. 12 * 13 * PII policy: we never push name, email, phone, company, message body, 14 * uploaded files, or free text from any form into the dataLayer. Only 15 * non-personal intent slugs, CTA text, and URLs of tel:/mailto: links 16 * (which are the site's public contact details) are captured. 17 */ 18(function () { 19 'use strict'; 20 21 var dl = (window.dataLayer = window.dataLayer || []); 22 function push(evt, params) { 23 try { 24 var obj = { event: evt }; 25 if (params) { 26 for (var k in params) { 27 if (Object.prototype.hasOwnProperty.call(params, k)) obj[k] = params[k]; 28 } 29 } 30 dl.push(obj); 31 } catch (e) { /* never break the site for analytics */ } 32 } 33 34 // ---------- section_view (single-page hash routes) ---------- 35 var SECTIONS = [ 36 'home', 'about', 'services', 'direct-clients', 'introducers', 37 'team', 'careers', 'contact', 'compliance', 'home-services', 38 'safeguarding', 'eligibility', 'policies', 39 'compliance-facts', 'compliance-is-is-not' 40 ]; 41 var seen = Object.create(null); 42 43 function currentSection() { 44 var raw = (location.hash || '#home').replace(/^#/, ''); 45 raw = raw.split('?')[0].split('&')[0]; 46 return raw || 'home'; 47 } 48 function fireSection() { 49 var s = currentSection(); 50 if (!s || seen[s]) return; 51 if (SECTIONS.indexOf(s) === -1) return; 52 seen[s] = true; 53 push('section_view', { 54 section_name: s, 55 page_title: document.title, 56 page_location: location.href 57 }); 58 } 59 window.addEventListener('hashchange', fireSection); 60 if (document.readyState === 'loading') { 61 document.addEventListener('DOMContentLoaded', fireSection); 62 } else { 63 fireSection(); 64 } 65 66 // ---------- helpers ---------- 67 function textOf(el) { 68 return (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 120); 69 } 70 function ctaLocation(el) { 71 var s = el.closest ? el.closest('section') : null; 72 if (s && s.id) return s.id; 73 if (s && s.className) return String(s.className).split(/\s+/)[0] || 'section'; 74 return 'unknown'; 75 } 76 function currentContactTopic() { 77 var checked = document.querySelector('input[name="topic"]:checked'); 78 if (!checked) return ''; 79 var t = checked.getAttribute('data-topic'); 80 return t || ''; 81 } 82 83 var START_APP_RE = /start\s+your\s+application/i; 84 var SPEAK_RE = /^(speak to our team|speak to the team|contact arie finance)$/i; 85 86 // ---------- click delegation ---------- 87 document.addEventListener('click', function (ev) { 88 var link = ev.target && ev.target.closest ? ev.target.closest('a[href], button') : null; 89 if (!link) return; 90 91 var href = link.getAttribute ? link.getAttribute('href') : null; 92 if (href) { 93 if (href.indexOf('mailto:') === 0) { 94 push('email_click', { link_url: href, link_text: textOf(link) }); 95 return; 96 } 97 if (href.indexOf('tel:') === 0) { 98 push('phone_click', { link_url: href, link_text: textOf(link) }); 99 return; 100 } 101 } 102 103 var t = textOf(link); 104 if (!t) return; 105 106 // start_application: customer/client application CTA intent only. 107 // Strict identity: the DOM element must be the site's contact-route 108 // anchor (data-route="contact"). This rejects any unrelated future 109 // button labelled "Apply" â for example a hypothetical careers 110 // Apply control â from being classified as a commercial CTA. 111 // Matched text: "Start Your Application" or the exact mobile short 112 // label "Apply" that lives inside the same data-route="contact" 113 // anchor as "Start Your Application" (verified in the repo). 114 if (link.matches && link.matches('a[data-route="contact"]')) { 115 if (START_APP_RE.test(t) || t === 'Apply') { 116 push('start_application', { cta_text: t, cta_location: ctaLocation(link) }); 117 return; 118 } 119 } 120
121 // speak_to_team: general commercial contact CTAs. 122 if (SPEAK_RE.test(t)) { 123 push('speak_to_team', { cta_text: t, cta_location: ctaLocation(link) }); 124 return; 125 } 126 }, true); 127 128 // ---------- contact_topic_selected (engagement) ---------- 129 // Fires whenever the contact-form intent radio changes. Parameter 130 // contact_topic is the non-personal slug from the radio's data-topic 131 // attribute (e.g. "account", "payments", "introducer", "referral", 132 // "support", "other"). Selection is INTEREST, not a submitted lead; 133 // the actual submitted lead is contact_form_submit with the same 134 // contact_topic value. 135 document.addEventListener('change', function (ev) { 136 var t = ev.target; 137 if (!t || t.name !== 'topic') return; 138 if (!t.checked) return; 139 var slug = t.getAttribute && t.getAttribute('data-topic'); 140 if (!slug) return; 141 push('contact_topic_selected', { contact_topic: slug }); 142 }, true); 143 144 // ---------- form_start / form_submit ---------- 145 // form_start on first user interaction with each form, once. 146 // form_submit when the site's existing success element gets 147 // .is-visible (set by the confirmed-success branch in the site's 148 // contact/careers submit handler, after /api/website-form returns 149 // {ok:true}). For the contact form, contact_topic is attached from 150 // whichever intent radio is checked at that moment. 151 document.querySelectorAll('form[data-form]').forEach(function (form) { 152 var name = form.dataset.form; // "contact" | "careers" 153 var started = false; 154 function start() { 155 if (started) return; 156 started = true; 157 push(name + '_form_start', { form_name: name }); 158 } 159 form.addEventListener('input', start, true); 160 form.addEventListener('change', start, true); 161 }); 162 163 document.querySelectorAll('[data-success]').forEach(function (success) { 164 var name = success.getAttribute('data-success'); // "contact" | "careers" 165 if (!name) return; 166 try { 167 var mo = new MutationObserver(function () { 168 if (success.classList.contains('is-visible')) { 169 var params = { form_name: name }; 170 if (name === 'contact') { 171 var topic = currentContactTopic(); 172 if (topic) params.contact_topic = topic; 173 } 174 push(name + '_form_submit', params); 175 mo.disconnect(); 176 } 177 }); 178 mo.observe(success, { attributes: true, attributeFilter: ['class'] }); 179 } catch (e) { /* MutationObserver unavailable â form_submit will not fire */ } 180 }); 181})();
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.