(site-conversion D9): // navigator.webdriver true and no interaction within 2s of page load. if (window.__gbBotSuspected) { return; } if (typeof window.gtag === 'function') { window.gtag('event', name, params || {}); } } function attribution() { var p = {}; try { var q = new URLSearchParams(window.location.search); ['ref', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach(function (k) { var v = q.get(k); if (v) { p[k] = v; } }); } catch (e) {} return p; } // A checkbox/radio is "filled in" when checked; anything else when its trimmed value // is non-empty. Autofill and programmatic fills satisfy this exactly like typing does. function hasValue(el) { if (!el) { return false; } if (el.type === 'checkbox' || el.type === 'radio') { return !!el.checked; } return typeof el.value === 'string' && el.value.trim() !== ''; } var attr = attribution(); var form = document.querySelector('form[data-signup-form]'); var started = false; var submitted = false; var lastField = ''; var seen = {}; // The form's fields in DOM order, de-duplicated (a radio group shares one name), so each // field carries a stable position and the GA4 funnel steps read top-to-bottom. var order = []; if (form) { form.querySelectorAll('[name]').forEach(function (el) { var n = el.getAttribute('name'); if (n && order.indexOf(n) === -1) { order.push(n); } }); } emit('signup_view', attr); if (form) { // signup_start: the first real interaction, once per page view. 'input' only // fires from typing, pasting, autofill or a programmatic value change, never // from focus alone, which is what let this fire on page load before. form.addEventListener('input', function (e) { var name = e.target && e.target.name ? e.target.name : ''; if (name) { lastField = name; } if (!started) { started = true; emit('signup_start', attr); } }, { passive: true }); // signup_field: on blur, once per field, only when it was actually filled in. // A field tabbed through and left blank (the page-load-focus false positive // this replaces) never counts as a funnel step. form.addEventListener('focusout', function (e) { var el = e.target; var name = el && el.name ? el.name : ''; if (!name || seen[name] || !hasValue(el)) { return; } seen[name] = true; lastField = name; var idx = order.indexOf(name); emit('signup_field', Object.assign({ field: name, field_index: idx === -1 ? order.length : idx, }, attr)); }, { passive: true }); form.addEventListener('submit', function () { submitted = true; emit('signup_submit', attr); }, { passive: true }); } // Abandon: visibilitychange is more reliable than beforeunload on mobile. Only counts // once the visitor has started and has not submitted. document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden' && started && !submitted) { emit('signup_abandon', Object.assign({ step: lastField || 'unknown' }, attr)); } }); })();