Last active
March 13, 2026 00:34
-
-
Save akapug/6e70fa0042eae4122a1d5e4b092a2228 to your computer and use it in GitHub Desktop.
Referral tracking script for elide.dev website — add to js/utm-capture.js and include in build.mjs head()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From f19980e97eaafe3414a1de9bfeb8183378f4f95d Mon Sep 17 00:00:00 2001 | |
| From: David <215816+akapug@users.noreply.github.com> | |
| Date: Thu, 12 Mar 2026 17:31:42 -0700 | |
| Subject: [PATCH] feat: add referral tracking & UTM capture script | |
| - Add js/utm-capture.js for UTM parameter and referral attribution tracking | |
| - Modify build.mjs head() to include script on all pages (defer loaded) | |
| - Regenerate all HTML output files | |
| The script captures: | |
| - UTM params (utm_source, utm_medium, utm_campaign) into elide_touchpoints cookie | |
| - Referral codes from ?ref=CODE URL param into elide_ref cookie | |
| - Multi-touch attribution (up to 10 touchpoints, 90-day expiry) | |
| Part of the referral program: glue.elide.work/api/ref/[code] redirects here, | |
| and this script ensures attribution persists for downstream conversion tracking. | |
| --- | |
| about.html | 1 + | |
| blog/index.html | 1 + | |
| blog/whiplash/index.html | 1 + | |
| build.mjs | 1 + | |
| index.html | 1 + | |
| js/utm-capture.js | 107 +++++++++++++++++++++++++++++++++++++++ | |
| pricing.html | 1 + | |
| 7 files changed, 113 insertions(+) | |
| create mode 100644 js/utm-capture.js | |
| diff --git a/about.html b/about.html | |
| index d15d2e9..e675f33 100644 | |
| --- a/about.html | |
| +++ b/about.html | |
| @@ -22,6 +22,7 @@ | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head> | |
| <body> | |
| diff --git a/blog/index.html b/blog/index.html | |
| index dd57eb4..f488730 100644 | |
| --- a/blog/index.html | |
| +++ b/blog/index.html | |
| @@ -22,6 +22,7 @@ | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head> | |
| <body> | |
| diff --git a/blog/whiplash/index.html b/blog/whiplash/index.html | |
| index 2d7487e..a575b16 100644 | |
| --- a/blog/whiplash/index.html | |
| +++ b/blog/whiplash/index.html | |
| @@ -22,6 +22,7 @@ | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head> | |
| <body> | |
| diff --git a/build.mjs b/build.mjs | |
| index 51975ea..45e1d5c 100644 | |
| --- a/build.mjs | |
| +++ b/build.mjs | |
| @@ -152,6 +152,7 @@ function head(title, description, url, ogType = "article", ogImage = "https://el | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head>`; | |
| } | |
| diff --git a/index.html b/index.html | |
| index bfdf258..0496fd9 100644 | |
| --- a/index.html | |
| +++ b/index.html | |
| @@ -22,6 +22,7 @@ | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head> | |
| <body> | |
| diff --git a/js/utm-capture.js b/js/utm-capture.js | |
| new file mode 100644 | |
| index 0000000..18a8976 | |
| --- /dev/null | |
| +++ b/js/utm-capture.js | |
| @@ -0,0 +1,107 @@ | |
| +/** | |
| + * Elide UTM Capture Snippet | |
| + * | |
| + * Drop this script on any landing page (elide.dev, blog, docs) to capture | |
| + * UTM parameters and accumulate multi-touch attribution data. | |
| + * | |
| + * Usage: | |
| + * <script src="https://glue.elide.work/js/utm-capture.js" defer></script> | |
| + * | |
| + * What it does: | |
| + * 1. On page load, reads UTM params from the URL (utm_source, utm_medium, utm_campaign) | |
| + * 2. Reads the referral code cookie (set by /api/ref/[code]) | |
| + * 3. Appends a touchpoint to the elide_touchpoints cookie (JSON array) | |
| + * 4. When the user clicks a buy/checkout link, the /api/buy endpoint reads | |
| + * the accumulated touchpoints and sends them to Stripe metadata. | |
| + * | |
| + * Cookie format (elide_touchpoints): | |
| + * [{"source":"youtube","medium":"influencer","campaign":"theo","timestamp":"..."}] | |
| + * | |
| + * Respects: No PII is stored. Only UTM params + timestamps. | |
| + */ | |
| +(function () { | |
| + var COOKIE_NAME = "elide_touchpoints"; | |
| + var MAX_TOUCHPOINTS = 10; | |
| + var COOKIE_DAYS = 90; | |
| + | |
| + function getCookie(name) { | |
| + var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); | |
| + return match ? decodeURIComponent(match[2]) : null; | |
| + } | |
| + | |
| + function setCookie(name, value, days) { | |
| + var expires = new Date(Date.now() + days * 864e5).toUTCString(); | |
| + document.cookie = | |
| + name + | |
| + "=" + | |
| + encodeURIComponent(value) + | |
| + "; expires=" + | |
| + expires + | |
| + "; path=/; SameSite=Lax"; | |
| + } | |
| + | |
| + function getUtmParams() { | |
| + var params = new URLSearchParams(window.location.search); | |
| + var source = params.get("utm_source"); | |
| + if (!source) return null; | |
| + return { | |
| + source: source, | |
| + medium: params.get("utm_medium") || "none", | |
| + campaign: params.get("utm_campaign") || undefined, | |
| + timestamp: new Date().toISOString(), | |
| + }; | |
| + } | |
| + | |
| + function getReferralCode() { | |
| + // Check URL param first (?ref=CODE), then cookie (set by /api/ref/[code]) | |
| + var params = new URLSearchParams(window.location.search); | |
| + var urlRef = params.get("ref"); | |
| + if (urlRef) { | |
| + setCookie("elide_ref", urlRef, COOKIE_DAYS); | |
| + return urlRef; | |
| + } | |
| + return getCookie("elide_ref") || null; | |
| + } | |
| + | |
| + // Read existing touchpoints | |
| + var existing = []; | |
| + try { | |
| + var raw = getCookie(COOKIE_NAME); | |
| + if (raw) existing = JSON.parse(raw); | |
| + if (!Array.isArray(existing)) existing = []; | |
| + } catch (e) { | |
| + existing = []; | |
| + } | |
| + | |
| + // Add UTM touchpoint if present | |
| + var utm = getUtmParams(); | |
| + if (utm) { | |
| + existing.push(utm); | |
| + } | |
| + | |
| + // Add referral touchpoint if cookie is set and not already tracked | |
| + var refCode = getReferralCode(); | |
| + if (refCode) { | |
| + var hasRef = existing.some(function (t) { | |
| + return t.source === "referral"; | |
| + }); | |
| + if (!hasRef) { | |
| + existing.push({ | |
| + source: "referral", | |
| + medium: "user", | |
| + campaign: refCode, | |
| + timestamp: new Date().toISOString(), | |
| + }); | |
| + } | |
| + } | |
| + | |
| + // Keep only the last N touchpoints | |
| + if (existing.length > MAX_TOUCHPOINTS) { | |
| + existing = existing.slice(-MAX_TOUCHPOINTS); | |
| + } | |
| + | |
| + // Persist | |
| + if (existing.length > 0) { | |
| + setCookie(COOKIE_NAME, JSON.stringify(existing), COOKIE_DAYS); | |
| + } | |
| +})(); | |
| diff --git a/pricing.html b/pricing.html | |
| index 8b15488..063ac0a 100644 | |
| --- a/pricing.html | |
| +++ b/pricing.html | |
| @@ -22,6 +22,7 @@ | |
| <link rel="stylesheet" href="/css/reset.css"> | |
| <link rel="stylesheet" href="/css/layout.css"> | |
| <link rel="stylesheet" href="/css/components.css"> | |
| + <script src="/js/utm-capture.js" defer></script> | |
| </head> | |
| <body> | |
| -- | |
| 2.51.0 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Elide UTM Capture Snippet | |
| * | |
| * Drop this script on any landing page (elide.dev, blog, docs) to capture | |
| * UTM parameters and accumulate multi-touch attribution data. | |
| * | |
| * Usage: | |
| * <script src="https://glue.elide.work/js/utm-capture.js" defer></script> | |
| * | |
| * What it does: | |
| * 1. On page load, reads UTM params from the URL (utm_source, utm_medium, utm_campaign) | |
| * 2. Reads the referral code cookie (set by /api/ref/[code]) | |
| * 3. Appends a touchpoint to the elide_touchpoints cookie (JSON array) | |
| * 4. When the user clicks a buy/checkout link, the /api/buy endpoint reads | |
| * the accumulated touchpoints and sends them to Stripe metadata. | |
| * | |
| * Cookie format (elide_touchpoints): | |
| * [{"source":"youtube","medium":"influencer","campaign":"theo","timestamp":"..."}] | |
| * | |
| * Respects: No PII is stored. Only UTM params + timestamps. | |
| */ | |
| (function () { | |
| var COOKIE_NAME = "elide_touchpoints"; | |
| var MAX_TOUCHPOINTS = 10; | |
| var COOKIE_DAYS = 90; | |
| function getCookie(name) { | |
| var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); | |
| return match ? decodeURIComponent(match[2]) : null; | |
| } | |
| function setCookie(name, value, days) { | |
| var expires = new Date(Date.now() + days * 864e5).toUTCString(); | |
| document.cookie = | |
| name + | |
| "=" + | |
| encodeURIComponent(value) + | |
| "; expires=" + | |
| expires + | |
| "; path=/; SameSite=Lax"; | |
| } | |
| function getUtmParams() { | |
| var params = new URLSearchParams(window.location.search); | |
| var source = params.get("utm_source"); | |
| if (!source) return null; | |
| return { | |
| source: source, | |
| medium: params.get("utm_medium") || "none", | |
| campaign: params.get("utm_campaign") || undefined, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| } | |
| function getReferralCode() { | |
| // Check URL param first (?ref=CODE), then cookie (set by /api/ref/[code]) | |
| var params = new URLSearchParams(window.location.search); | |
| var urlRef = params.get("ref"); | |
| if (urlRef) { | |
| setCookie("elide_ref", urlRef, COOKIE_DAYS); | |
| return urlRef; | |
| } | |
| return getCookie("elide_ref") || null; | |
| } | |
| // Read existing touchpoints | |
| var existing = []; | |
| try { | |
| var raw = getCookie(COOKIE_NAME); | |
| if (raw) existing = JSON.parse(raw); | |
| if (!Array.isArray(existing)) existing = []; | |
| } catch (e) { | |
| existing = []; | |
| } | |
| // Add UTM touchpoint if present | |
| var utm = getUtmParams(); | |
| if (utm) { | |
| existing.push(utm); | |
| } | |
| // Add referral touchpoint if cookie is set and not already tracked | |
| var refCode = getReferralCode(); | |
| if (refCode) { | |
| var hasRef = existing.some(function (t) { | |
| return t.source === "referral"; | |
| }); | |
| if (!hasRef) { | |
| existing.push({ | |
| source: "referral", | |
| medium: "user", | |
| campaign: refCode, | |
| timestamp: new Date().toISOString(), | |
| }); | |
| } | |
| } | |
| // Keep only the last N touchpoints | |
| if (existing.length > MAX_TOUCHPOINTS) { | |
| existing = existing.slice(-MAX_TOUCHPOINTS); | |
| } | |
| // Persist | |
| if (existing.length > 0) { | |
| setCookie(COOKIE_NAME, JSON.stringify(existing), COOKIE_DAYS); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment