(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));
}
});
})();