Photo credit: Alice Prenat for Portrait Madame studio
/**
* NLLG Utility Script — UTM capture, injection, and forwarding
* Per NLLG Measurement Plan v1.4, "The utility script" section.
* v2, 19 September 2026: fixes a mapping bug (see below) and adds the
* self-reported source question, since Kit's embed widget can't carry it.
*
* Install: Squarespace > Settings > Advanced > Code Injection > Footer.
* Runs site-wide on lauriejwetzel.com. Four jobs, in order:
* 1. Read UTM parameters from the URL on page load.
* 2. Inject them into the correct hidden Kit CUSTOM FIELDS (not fields
* literally named after the UTM parameters — see the map below).
* 3. Inject a visible "How did you find me?" dropdown into any embedded
* Kit form, wired to the acq_detail custom field.
* 4. Append UTM params to outbound links pointing at Demio, ThriveCart,
* and ScoreApp.
*
* v1 bug, fixed here: v1 injected hidden fields named fields[utm_source]
* and fields[utm_campaign]. Kit has no custom field by those names — the
* actual fields are acq_source and acq_campaign, per the Measurement Plan's
* own table ("acq_source: the utility script, from utm_source"). v1 would
* have submitted correctly-shaped forms that Kit simply had no column for,
* so nothing would have landed on the subscriber record despite no error.
*
* Consequence per the Measurement Plan: this only works against embedded Kit
* forms (type "embed" in the Kit API, e.g. the "Stuck opt-in" and "The Read"
* forms). It cannot run against a Kit-hosted page.
*
* Owner: Ruel, per the Execution Plan phase-one build (owner currently
* unresolved as of the 19 September ruling — see Execution Plan v1.4,
* Standing Risks). Whoever installs this, install once, site-wide.
*/
(function () {
"use strict";
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
var STORAGE_KEY = "nllg_utm_params";
var FORWARD_HOSTS = ["thrivecart.com", "demio.com", "scoreapp.com", "webinarjam.com"];
// Map: UTM parameter -> actual Kit custom field key. Only utm_source and
// utm_campaign have a defined Kit field per the Measurement Plan. utm_medium,
// utm_content, utm_term are read for the outbound-link forwarding job (#4)
// but have no Kit custom field to write into.
var UTM_TO_KIT_FIELD = {
utm_source: "acq_source",
utm_campaign: "acq_campaign"
};
// The self-reported source question. Wording and options must match every
// other capture form exactly, per the Measurement Plan, so answers aggregate.
var SELF_REPORT_QUESTION = "How did you find me?";
var SELF_REPORT_OPTIONS = [
"A podcast",
"Someone shared it with me",
"LinkedIn",
"Instagram",
"Substack",
"A newsletter I read",
"Search",
"Other"
];
// ---- 1. Read UTM parameters from the URL on page load ----
function readUtmsFromUrl() {
var params = new URLSearchParams(window.location.search);
var found = {};
var any = false;
UTM_KEYS.forEach(function (key) {
var val = params.get(key);
if (val) {
found[key] = val;
any = true;
}
});
return any ? found : null;
}
// Persist the first UTM set seen this session so it survives navigation
// between pages (e.g. quiz result page -> lead magnet -> checkout) without
// a UTM string on every internal link. First-touch wins for the session;
// per the Measurement Plan, "a UTM value is never changed once used."
function getPersistedUtms() {
var fresh = readUtmsFromUrl();
if (fresh) {
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(fresh));
} catch (e) {
/* storage unavailable; fall through to session-only use */
}
return fresh;
}
try {
var stored = sessionStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : null;
} catch (e) {
return null;
}
}
var utms = getPersistedUtms();
if (!utms) return; // nothing to inject or forward on this page load
// ---- 2. Inject into the correct hidden Kit custom fields ----
// Kit embed forms render as