* Place inside , BEFORE any analytics scripts on shop. * * Debug: * ?ssl_debug=1 in the URL prints what the script is doing to the console. * --------------------------------------------------------------------------- */ (function () { 'use strict'; // ------------------------------------------------------------------------- // Configuration // ------------------------------------------------------------------------- var BRIDGE_VERSION = '2026.09.26-1'; // keep in step with the header line var APEX_DOMAIN = '.superstratumlabs.com'; var SHOP_HOST = 'shop.superstratumlabs.com'; // The marketing apex ships its own React link decorator (shopLinkAttribution), // which also re-decorates in-place href rewrites this script's observer cannot // see. On the apex this script only CAPTURES origin; it decorates on other // hosts (e.g. calculator.*) that have no bundle. One decorator per host. var APEX_HOST_RE = /^(www\.)?superstratumlabs\.com$/i; var COOKIE_NAME = 'utm_data'; // SESSION-SCOPED, ROLLING. This cookie is not a history store — GA4 and the // BigQuery export own long-term navigation. Its only job is to carry the // CURRENT session's origin across the apex/calculator/shop hops so the last // click survives into Shopify, which cannot resolve it itself. // // 30 minutes of inactivity matches how both GA4 and Shopify define a session, // and the window slides on every page view (see touchCookie), so an active // journey of any length is covered. // // The previous 90-day TTL is what produced the stale-credit bug: with nothing // expiring, buildShopParams fell back to a months-old first touch. Order // #19572 forwarded a 34-day-old Klaviyo campaign onto a visit whose real // referrer was bing.com. A session-length cookie removes that class of error // at the root rather than patching it. var SESSION_MINUTES = 30; var COOKIE_TTL = 60 * SESSION_MINUTES; var DEBUG = /[?&]ssl_debug=1/.test(location.search); var MAX_PATH_LEN = 256; // Hosts whose links we decorate. SHOP ONLY, deliberately. // // Every hop between apex / calculator / shop shares the `.superstratumlabs.com` // cookie, so origin already travels with the visitor on internal hops without // any decoration. Decoration exists for exactly one reason: Shopify reads // attribution from the HTTP request, server-side, before any script runs. So // the ONLY hop that must carry params is the last one into shop. // // This holds for every path shape — apex→calculator→apex→shop, // shop→apex→shop, calculator→shop→apex→shop — because whichever host owns // that final hop (React on apex, this script on calculator) builds the params // from the same shared cookie. // // Decorating internal apex/calculator links would be redundant, would leak // campaign params into shareable URLs, and would feed our own synthesised // params back into capture on the next host. var LINK_TARGETS = ['shop.superstratumlabs.com']; // Tracking params we will capture / forward / restore var UTM_PARAMS = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id','utm_source_platform']; var CLICK_IDS = ['gclid','fbclid','ttclid','msclkid','wbraid','gbraid','yclid','irclickid','epik','li_fat_id','rdt_cid','twclid','gad_source','ScCid']; var EXTRA_PARAMS = ['ref','source','aff_id','aff_sub']; var ALL_PARAMS = UTM_PARAMS.concat(CLICK_IDS).concat(EXTRA_PARAMS); // Referrer → source/medium, matched in order. Anything unmatched and external // falls through to { source: , medium: 'referral' }. var REFERRER_RULES = [ { re: /(^|\.)mail\.google\.com$/, source: 'gmail', medium: 'email' }, { re: /(^|\.)google\.[a-z.]+$/, source: 'google', medium: 'organic' }, { re: /(^|\.)bing\.com$/, source: 'bing', medium: 'organic' }, { re: /(^|\.)duckduckgo\.com$/, source: 'duckduckgo', medium: 'organic' }, { re: /(^|\.)search\.yahoo\.[a-z.]+$/,source: 'yahoo', medium: 'organic' }, { re: /(^|\.)ecosia\.org$/, source: 'ecosia', medium: 'organic' }, { re: /(^|\.)search\.brave\.com$/, source: 'brave', medium: 'organic' }, { re: /(^|\.)yandex\.[a-z.]+$/, source: 'yandex', medium: 'organic' }, { re: /(^|\.)baidu\.com$/, source: 'baidu', medium: 'organic' }, { re: /(^|\.)startpage\.com$/, source: 'startpage', medium: 'organic' }, { re: /(^|\.)facebook\.com$/, source: 'facebook', medium: 'social' }, { re: /(^|\.)fb\.(com|me)$/, source: 'facebook', medium: 'social' }, { re: /(^|\.)instagram\.com$/, source: 'instagram', medium: 'social' }, { re: /(^|\.)t\.co$/, source: 'twitter', medium: 'social' }, { re: /(^|\.)(twitter|x)\.com$/, source: 'twitter', medium: 'social' }, { re: /(^|\.)linkedin\.com$/, source: 'linkedin', medium: 'social' }, { re: /(^|\.)lnkd\.in$/, source: 'linkedin', medium: 'social' }, { re: /(^|\.)pinterest\.[a-z.]+$/, source: 'pinterest', medium: 'social' }, { re: /(^|\.)reddit\.com$/, source: 'reddit', medium: 'social' }, { re: /(^|\.)tiktok\.com$/, source: 'tiktok', medium: 'social' }, { re: /(^|\.)youtube\.com$/, source: 'youtube', medium: 'social' }, { re: /(^|\.)threads\.(net|com)$/, source: 'threads', medium: 'social' }, { re: /(^|\.)snapchat\.com$/, source: 'snapchat', medium: 'social' }, { re: /(^|\.)whatsapp\.com$/, source: 'whatsapp', medium: 'social' }, { re: /(^|\.)linktr\.ee$/, source: 'linktree', medium: 'referral'}, { re: /(^|\.)shop\.app$/, source: 'shop_app', medium: 'referral'} ]; // ------------------------------------------------------------------------- // Utilities // ------------------------------------------------------------------------- function log() { if (!DEBUG) return; try { console.log.apply(console, ['[ssl-origin-bridge]'].concat([].slice.call(arguments))); } catch (e) {} } function getCookie(name) { var m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()[\]\\\/+^])/g, '\\$1') + '=([^;]*)')); return m ? decodeURIComponent(m[1]) : null; } function setCookie(name, value) { document.cookie = name + '=' + encodeURIComponent(value) + '; domain=' + APEX_DOMAIN + '; path=/' + '; max-age=' + COOKIE_TTL + '; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : ''); } function parseCookieJSON(name) { var raw = getCookie(name); if (!raw) return {}; try { return JSON.parse(raw); } catch (e) { return {}; } } function parseQuery(search) { var out = {}; var qs = (search || '').replace(/^\?/, ''); if (!qs) return out; qs.split('&').forEach(function (p) { var i = p.indexOf('='); try { if (i < 0) { out[decodeURIComponent(p)] = ''; return; } out[decodeURIComponent(p.slice(0, i))] = decodeURIComponent(p.slice(i + 1)); } catch (e) { /* malformed escape — skip this pair */ } }); return out; } function getQuery() { return parseQuery(location.search); } // FIX 7: the query string of an internal referrer, or {} if none/unparseable. function referrerQuery(ref) { var q = ref.indexOf('?'); if (q < 0) return {}; var end = ref.indexOf('#', q); return parseQuery(ref.slice(q, end < 0 ? undefined : end)); } function isInternalReferrer(ref) { return /^https?:\/\/([a-z0-9-]+\.)?superstratumlabs\.com/i.test(ref || ''); } function onReady(fn) { if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn); else fn(); } function trimPath(p) { if (!p) return p; return p.length > MAX_PATH_LEN ? p.slice(0, MAX_PATH_LEN) : p; } /** * Classify a referrer into { source, medium }, GA4-style. * Returns direct/none for an empty or unparseable referrer, and null for an * internal referrer (nothing to learn from our own domains). */ function classifyReferrer(ref) { if (!ref) return { source: 'direct', medium: 'none' }; if (isInternalReferrer(ref)) return null; var host; try { host = new URL(ref).hostname.toLowerCase(); } catch (e) { return { source: 'direct', medium: 'none' }; } if (!host) return { source: 'direct', medium: 'none' }; for (var i = 0; i < REFERRER_RULES.length; i++) { if (REFERRER_RULES[i].re.test(host)) { return { source: REFERRER_RULES[i].source, medium: REFERRER_RULES[i].medium }; } } return { source: host.replace(/^www\./, ''), medium: 'referral' }; } function decorateTargets() { var here = (location.hostname || '').toLowerCase(); return LINK_TARGETS.filter(function (h) { return h !== here; }); } // ------------------------------------------------------------------------- // 1. Origin capture (runs on every host) // ------------------------------------------------------------------------- // ------------------------------------------------------------------------- // FIX 6 — a touch is a complete set // ------------------------------------------------------------------------- // A touch is one arrival's tracking params (ALL_PARAMS). First touch lives // under the plain keys, last touch under `last_*`. Each is written, compared // and forwarded whole; a key the chosen touch lacks stays absent. function touchFromQuery(qp) { var t = {}; ALL_PARAMS.forEach(function (k) { if (qp[k]) t[k] = qp[k]; }); return t; } function hasLastTouch(data) { if (data.last_touch_at) return true; return ALL_PARAMS.some(function (k) { return data['last_' + k] != null; }); } // The touch forwarding uses: last touch if one exists, else first touch. // Never a mix of the two. function currentTouch(data) { var prefix = hasLastTouch(data) ? 'last_' : ''; var t = {}; ALL_PARAMS.forEach(function (k) { var v = data[prefix + k]; if (v != null && v !== '') t[k] = String(v); }); return t; } // Every URL param already matches the current touch: this is our own // decoration arriving from the previous host, not a new click. A subset // counts — the apex decorator forwards fewer keys than the bridge stores. function isEchoOf(incoming, touch) { var keys = Object.keys(incoming); return keys.length > 0 && keys.every(function (k) { return touch[k] === incoming[k]; }); } function replaceTouch(data, prefix, touch) { ALL_PARAMS.forEach(function (k) { delete data[prefix + k]; }); Object.keys(touch).forEach(function (k) { data[prefix + k] = touch[k]; }); } // FIX 1: derive source/medium from the referrer when the touch carries no // utm_source of its own. Never overwrites an authored utm_source. function deriveSourceIfMissing(data, ref, externalRef) { if (data.utm_source) return; var derived = classifyReferrer(externalRef ? ref : ''); if (derived) { data.utm_source = derived.source; if (!data.utm_medium) data.utm_medium = derived.medium; data.source_derived = '1'; } } function captureOrigin() { var existing = parseCookieJSON(COOKIE_NAME); var qp = getQuery(); var changed = false; var incoming = touchFromQuery(qp); var hasAnyTrackingParam = Object.keys(incoming).length > 0; var ref = document.referrer || ''; var externalRef = !!ref && !isInternalReferrer(ref); if (!existing.captured_at) { // FIRST TOUCH. // Unconditional (FIX 2): previously this required a tracking param or an // external referrer, so true-direct arrivals wrote nothing and their // outbound links went bare — which Shopify then booked as a self-referral. // FIX 7: no cookie + internal referrer = a resumed session (the cookie // lapsed mid-visit), not a direct arrival. If the URL carries nothing, // the previous page's URL may: take its tracking params as the touch. var internalRef = !!ref && !externalRef; var fromRef = false; if (!hasAnyTrackingParam && internalRef) { var recovered = touchFromQuery(referrerQuery(ref)); if (Object.keys(recovered).length) { incoming = recovered; fromRef = true; } } replaceTouch(existing, '', incoming); if (fromRef) { // Whole touch as found; a campaign without utm_source stays without it // rather than being padded with a synthesised direct/none. existing.touch_from_referrer = '1'; } else { deriveSourceIfMissing(existing, ref, externalRef); } if (internalRef) existing.session_resumed = '1'; existing.captured_at = new Date().toISOString(); existing.landing_host = location.hostname; existing.landing_path = trimPath(location.pathname + (location.search || '')); existing.landing_referrer = ref || null; changed = true; log('first-touch captured', existing); } else if (hasAnyTrackingParam && isEchoOf(incoming, currentTouch(existing))) { // ECHO (FIX 6). These params are our own decoration arriving from the // previous host. Treating them as a new click would re-run the upgrade // or last-touch branch and move captured_at / landing_host to the shop. log('forwarded params match the current touch; no change'); } else if (existing.source_derived === '1' && hasAnyTrackingParam) { // PROVISIONAL UPGRADE. // A derived first-touch (direct / organic / referral, synthesised because // the URL carried no campaign) is provisional: it exists so the visitor // is never invisible, not to claim acquisition. The first REAL campaign // to arrive takes the first-touch slot, which is what the old gate // achieved by refusing to write at all. // old: untagged visit writes nothing -> later ad becomes first-touch // new: untagged visit writes `direct` -> later ad REPLACES it // Same attribution outcome, minus the 26% of orders that were arriving // with no origin at all. // FIX 6: the campaign replaces the provisional touch WHOLE. Previously a // bare ?gclid= kept the derived google/organic and dropped the flag, so // a synthesised source then looked authored. A touch without its own // utm_source is re-derived from the referrer, exactly like a first touch. delete existing.source_derived; delete existing.session_resumed; // FIX 7: the landing moves to this click delete existing.touch_from_referrer; replaceTouch(existing, '', incoming); deriveSourceIfMissing(existing, ref, externalRef); if (!existing.first_seen_at) existing.first_seen_at = existing.captured_at; existing.captured_at = new Date().toISOString(); existing.landing_host = location.hostname; existing.landing_path = trimPath(location.pathname + (location.search || '')); existing.landing_referrer = ref || existing.landing_referrer || null; changed = true; log('provisional first-touch upgraded to campaign', existing.utm_source); } else if (hasAnyTrackingParam) { // LAST TOUCH via explicit campaign params. First-touch keys stay frozen, // so order ssl_utm_* attributes remain comparable across history while // ssl_last_utm_* carries the current campaign. // FIX 6: the new campaign replaces the last-touch slot WHOLE. Merging // per key kept the previous touch's utm_term / utm_id / click ids when // the new one omitted them. (Not an echo — that was ruled out above.) replaceTouch(existing, 'last_', incoming); // An explicit campaign supersedes any previously derived last-touch. delete existing.last_source_derived; existing.last_touch_at = new Date().toISOString(); existing.last_touch_host = location.hostname; changed = true; log('last-touch refreshed (campaign)', existing); } else if (externalRef) { // FIX 3: LAST TOUCH via referrer change. Under the old 90-day TTL this // was what let a month-old email campaign keep winning against a fresh // organic visit. The 30-min scope now retires most of those on its own; // this branch covers the rest — a real origin change inside one session. var d = classifyReferrer(ref); if (d) { var currentSource = existing.last_utm_source != null ? existing.last_utm_source : existing.utm_source; if (d.source !== currentSource) { // FIX 6: whole slot — the previous last touch's campaign and click // ids do not survive onto a referrer-derived touch. replaceTouch(existing, 'last_', { utm_source: d.source, utm_medium: d.medium }); existing.last_source_derived = '1'; existing.last_touch_at = new Date().toISOString(); existing.last_touch_host = location.hostname; changed = true; log('last-touch refreshed (referrer)', d.source, d.medium); } } } if (changed) setCookie(COOKIE_NAME, JSON.stringify(existing)); else if (existing.captured_at) touchCookie(existing); return existing; } /** * Slide the session window. Re-writes the cookie unchanged with a fresh * max-age on every page view, so an active journey never expires mid-flight * while an abandoned one lapses after SESSION_MINUTES of inactivity — * the same rule GA4 and Shopify use to bound a session. */ function touchCookie(data) { try { setCookie(COOKIE_NAME, JSON.stringify(data)); } catch (e) {} } // ------------------------------------------------------------------------- // 2. Link / form decorator (calculator + previews; NOT apex) // ------------------------------------------------------------------------- /** * FIX 5. Is the source we would forward one this script SYNTHESISED as * direct, rather than one the visitor actually arrived with? * * classifyReferrer() returns direct/none for an arrival with no referrer and * no campaign. STORING that is right — it keeps the visitor visible and stops * Shopify inventing a self-referral (FIX 2). FORWARDING it is wrong: GA4's * Direct channel matches source `(direct)` with medium `(none)`/`(not set)`, * so the bare strings `direct`/`none` match no channel rule at all and the * session drops into Unassigned. * * Suppressing is safe because GA4 already resolves these correctly on its * own: over the same 30 days there were ZERO sessions attributed to * superstratumlabs.com, so cross-domain continuity is intact and nothing * falls back to a self-referral on the GA4 side. The ssl_origin_* params * below still travel, and the shop still stamps ssl_* cart attributes, so the * ORDER keeps the origin even though the URL stays clean. What we give up is * Shopify's native report for this slice, which is the lesser report — GA4 * and the BigQuery export are the system of record. * * Only ever suppresses a value this script invented. A real campaign on the * current URL, or a stored campaign from a real click, always wins. */ function isDerivedDirect(data, qp) { if (qp && (qp.utm_source || qp.utm_medium)) return false; // Judge the same touch forwarding will use (FIX 6). if (hasLastTouch(data)) { return data.last_source_derived === '1' && data.last_utm_source === 'direct'; } return data.source_derived === '1' && data.utm_source === 'direct'; } function buildShopParams() { var data = parseCookieJSON(COOKIE_NAME); var qp = getQuery(); var out = {}; var dropDirect = isDerivedDirect(data, qp); // FIX 6: forward ONE touch whole — last touch if there is one, else first // touch. captureOrigin has already folded this page's own params into the // cookie, so the stored touch is the current one. Resolving per key here // is what put a Meta utm_term on a linktree link. var touch = currentTouch(data); UTM_PARAMS.concat(CLICK_IDS).forEach(function (k) { var v = touch[k]; if (!v) return; // Click IDs still travel: a gclid/fbclid is real even when the referrer // was empty, and GA4 resolves those to google/cpc itself. if (dropDirect && (k === 'utm_source' || k === 'utm_medium')) return; out[k] = v; }); if (data.captured_at) out.ssl_origin_at = data.captured_at; if (data.landing_host) out.ssl_origin_host = data.landing_host; if (data.landing_path) out.ssl_origin_path = data.landing_path; return out; } function decorateUrl(urlStr) { var u; try { u = new URL(urlStr, location.href); } catch (e) { return urlStr; } if (decorateTargets().indexOf((u.hostname || '').toLowerCase()) === -1) return urlStr; var params = buildShopParams(); var search = new URLSearchParams(u.search); // FIX 6: a link that authors ANY tracking param carries its own touch. // Adding ours beside it would blend two campaigns, so it gets only the // ssl_origin_* breadcrumbs. var authored = UTM_PARAMS.concat(CLICK_IDS).some(function (k) { return search.has(k); }); Object.keys(params).forEach(function (k) { if (authored && k.indexOf('ssl_') !== 0) return; if (!search.has(k)) search.set(k, params[k]); }); u.search = search.toString(); return u.toString(); } function decorateAnchor(a) { try { var href = a.getAttribute('href'); if (!href || /^(mailto:|tel:|javascript:|#)/i.test(href)) return; if (a.getAttribute('data-ssl-decorated') === '1') return; var newHref = decorateUrl(href); if (newHref !== href) { a.setAttribute('href', newHref); a.setAttribute('data-ssl-decorated', '1'); } } catch (e) {} } function decorateForm(form) { try { var action = form.getAttribute('action'); if (!action) return; if (form.getAttribute('data-ssl-decorated') === '1') return; var u; try { u = new URL(action, location.href); } catch (e) { return; } if (decorateTargets().indexOf((u.hostname || '').toLowerCase()) === -1) return; var params = buildShopParams(); // FIX 6: same rule as decorateUrl — an authored touch is left whole. var authored = UTM_PARAMS.concat(CLICK_IDS).some(function (k) { return !!form.querySelector('input[name="' + k + '"]'); }); Object.keys(params).forEach(function (k) { if (authored && k.indexOf('ssl_') !== 0) return; if (form.querySelector('input[name="' + k + '"]')) return; var input = document.createElement('input'); input.type = 'hidden'; input.name = k; input.value = params[k]; form.appendChild(input); }); form.setAttribute('data-ssl-decorated', '1'); } catch (e) {} } function decorateAll() { var anchors = document.querySelectorAll('a[href]'); for (var i = 0; i < anchors.length; i++) decorateAnchor(anchors[i]); var forms = document.querySelectorAll('form[action]'); for (var j = 0; j < forms.length; j++) decorateForm(forms[j]); log('decorated initial links', anchors.length, 'forms', forms.length); } function watchMutations() { if (!window.MutationObserver) return; var obs = new MutationObserver(function (muts) { muts.forEach(function (m) { if (!m.addedNodes) return; m.addedNodes.forEach(function (n) { if (n.nodeType !== 1) return; if (n.tagName === 'A' && n.hasAttribute('href')) decorateAnchor(n); else if (n.tagName === 'FORM' && n.hasAttribute('action')) decorateForm(n); else if (n.querySelectorAll) { n.querySelectorAll('a[href]').forEach(decorateAnchor); n.querySelectorAll('form[action]').forEach(decorateForm); } }); }); }); obs.observe(document.documentElement, { childList: true, subtree: true }); } function backstopClick() { // Catches programmatic .click() and last-second href swaps. document.addEventListener('click', function (e) { var a = e.target && e.target.closest ? e.target.closest('a[href]') : null; if (a) decorateAnchor(a); }, true); document.addEventListener('submit', function (e) { var f = e.target; if (f && f.tagName === 'FORM') decorateForm(f); }, true); } // ------------------------------------------------------------------------- // 3. Shop-side reconciler // NOTE: this cannot repair Shopify's own attribution — Shopify records // landing_site from the HTTP request, before any script runs. Kept as // best-effort for GTM/dataLayer consumers only. The attribution fix is // decoration on the originating host, above. // ------------------------------------------------------------------------- function reconcileShopUrl() { var data = parseCookieJSON(COOKIE_NAME); if (!data || !Object.keys(data).length) { log('shop reconcile: no cookie data'); return false; } var qp = getQuery(); // FIX 6: a campaign OR a bare click id makes the URL its own touch. Adding // stored params to it blended them: a live ?fbclid= arrival was rewritten // as the stored Klaviyo email campaign. var alreadyTagged = UTM_PARAMS.concat(CLICK_IDS).some(function (k) { return qp[k]; }); if (alreadyTagged) { log('shop reconcile: URL already carries a touch'); return false; } var params = new URLSearchParams(location.search); var added = 0; var dropDirect = isDerivedDirect(data, qp); // FIX 5 — see isDerivedDirect var touch = currentTouch(data); // FIX 6 — one touch, whole UTM_PARAMS.concat(CLICK_IDS).forEach(function (k) { if (dropDirect && (k === 'utm_source' || k === 'utm_medium')) return; var v = touch[k]; if (v && !params.has(k)) { params.set(k, v); added++; } }); if (!added) return false; var newQs = params.toString(); var newUrl = location.pathname + (newQs ? '?' + newQs : '') + location.hash; try { history.replaceState(history.state, '', newUrl); log('shop reconcile: URL rewritten with', added, 'params'); return true; } catch (e) { log('replaceState failed', e); return false; } } function pushDataLayer() { var data = parseCookieJSON(COOKIE_NAME); if (!data || !Object.keys(data).length) return; window.dataLayer = window.dataLayer || []; var payload = { event: 'ssl_origin_ready' }; Object.keys(data).forEach(function (k) { payload['ssl_' + k] = data[k]; }); window.dataLayer.push(payload); log('dataLayer pushed', payload); } function stampCartAttributes() { if (!window.fetch) return; var data = parseCookieJSON(COOKIE_NAME); if (!data || !Object.keys(data).length) return; var pairs = []; Object.keys(data).forEach(function (k) { pairs.push( encodeURIComponent('attributes[ssl_' + k + ']') + '=' + encodeURIComponent(String(data[k] == null ? '' : data[k])) ); }); if (!pairs.length) return; fetch('/cart/update.js', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }, body: pairs.join('&'), credentials: 'same-origin', keepalive: true }).then(function (r) { log('cart.attributes stamped', r.status); }) .catch(function (e) { log('cart.attributes error', e); }); } // ------------------------------------------------------------------------- // Boot // ------------------------------------------------------------------------- var mode = location.hostname === SHOP_HOST ? 'shop' : (APEX_HOST_RE.test(location.hostname) ? 'apex-capture-only' : 'decorate'); // FIX 4: make the deployed build identifiable from outside. try { window.__sslOriginBridge = { version: BRIDGE_VERSION, host: location.hostname, mode: mode, targets: decorateTargets() }; } catch (e) {} // Always capture first — works on all three hosts. captureOrigin(); if (mode === 'shop') { // Reconcile URL SYNC, before Shopify analytics beacons fire. reconcileShopUrl(); // Push to dataLayer SYNC too (so GTM tags reading dataLayer see it). pushDataLayer(); // Cart attribute stamp can wait for DOMContentLoaded. onReady(stampCartAttributes); } else if (mode === 'apex-capture-only') { // Apex: capture only. shopLinkAttribution (React bundle) owns decoration, // because it alone re-decorates React's in-place href rewrites. log('apex host: capture only, React bundle decorates links', BRIDGE_VERSION); } else { // Other hosts (calculator.*, previews) have no bundle — decorate here. onReady(function () { decorateAll(); watchMutations(); backstopClick(); }); } })();
Microscopic view of fungal hyphae and spore structures
Research Foundation

The Science of Mycotoxins

INTRODUCTION

Mycotoxins are secondary metabolites produced by filamentous fungi — primarily species of Aspergillus, Penicillium, Fusarium, and Stachybotrys. Unlike the fungi themselves, mycotoxins are nanoparticle-sized compounds that persist in environments long after the producing organism has been eliminated.

Key statistic

60–80% of world grain production is contaminated with mycotoxins

Key statistic

Over 400 mycotoxins identified; ~30 considered clinically significant

Over 400 mycotoxins have been identified to date. Approximately 30 are considered clinically significant for human health. The scope of exposure is staggering: an estimated 60–80% of world grain production is contaminated with detectable levels of mycotoxins, making some degree of dietary exposure nearly universal.

In the built environment, mycotoxins present a distinct challenge. They are too small to be captured by standard air filtration, easily become airborne on dust particles, and persist on surfaces indefinitely. Understanding their mechanisms of harm is essential for both clinical diagnosis and effective remediation.

NEUROTOXICITY

The neurotoxic potential of mycotoxins is among the most significant and underrecognized health concerns associated with indoor mold exposure. Multiple mycotoxin classes demonstrate the ability to cross the blood-brain barrier, with aflatoxins being particularly well-documented — all 21 members of the aflatoxin family can penetrate this critical protective boundary.

Key statistic

Trichothecenes can be released at 300× spore concentrations in contaminated buildings

Key statistic

All 21 aflatoxin family members can disrupt the blood-brain barrier

Key statistic

61% of symptoms reported from building-related illness are mental health issues

Trichothecenes, produced by Stachybotrys chartarum and Fusarium species, demonstrate direct neurotoxicity through inhibition of protein synthesis at the ribosomal level. In water-damaged buildings, satratoxins can be released at concentrations 300× greater than spore counts would suggest, creating significant inhalation risk even in environments that appear minimally contaminated.

Ochratoxin A has been increasingly linked to neurodegenerative processes, with studies demonstrating its ability to induce oxidative stress in neural tissue and inhibit mitochondrial respiration in brain cells. Its unusually long half-life (35 days in humans) means chronic low-level exposure can result in significant tissue accumulation.

Inhalation through the sinus cavity creates a direct pathway to the central nervous system, bypassing many of the body's standard detoxification mechanisms. This route of exposure may explain why neurological and cognitive symptoms — including brain fog, anxiety, depression, and memory impairment — are disproportionately represented in building-related illness cases. Research indicates that 61% of symptoms reported from building-related illness are mental health issues.

AUTOIMMUNE CONNECTIONS

Mycotoxin exposure has been associated with dysregulation of the immune system at multiple levels. Trichothecenes are potent immunosuppressants that reduce white blood cell counts and impair the body's ability to mount appropriate immune responses.

Paradoxically, while suppressing certain immune functions, mycotoxins can simultaneously trigger autoimmune-like responses. Gliotoxin, produced by Aspergillus fumigatus, modulates NF-κB signaling and can alter T-cell function in ways that promote autoimmune dysregulation.

Chronic mycotoxin exposure has been linked in clinical literature to the onset or exacerbation of conditions including Hashimoto's thyroiditis, multiple chemical sensitivity, mast cell activation syndrome, and chronic inflammatory response syndrome (CIRS). While causation is difficult to establish definitively, the immunomodulatory mechanisms are well-characterized at the cellular level.

The combination of immune suppression and immune dysregulation creates a particularly challenging clinical picture, as patients may present with both increased susceptibility to infection and inappropriate inflammatory responses simultaneously.

CANCER LINKS

Aflatoxin B1 is classified as a Group 1 carcinogen by the International Agency for Research on Cancer (IARC) — the highest classification, indicating sufficient evidence of carcinogenicity in humans. It is the most potent naturally occurring carcinogen known, primarily associated with hepatocellular carcinoma.

Sterigmatocystin, structurally related to Aflatoxin B1, is classified as a Group 2B possible carcinogen. It is produced by Aspergillus versicolor — one of the most common indoor mold species in water-damaged buildings. Any detection of sterigmatocystin in an indoor environment warrants investigation of Aspergillus growth sources.

Ochratoxin A has been classified as a Group 2B possible carcinogen, with evidence linking it to renal cell carcinoma and urinary tract tumors. Fumonisin B1, common in corn products, has been associated with esophageal cancer in populations with high dietary exposure.

The carcinogenic mechanisms vary by compound but include DNA adduct formation (aflatoxins), oxidative DNA damage (ochratoxin A), and disruption of sphingolipid metabolism (fumonisins). Chronic low-level exposure to multiple mycotoxins may present cumulative carcinogenic risk that is not captured by single-compound risk assessments.

CELLULAR MECHANISMS

Mycotoxins exert their effects through diverse molecular mechanisms, often affecting multiple cellular pathways simultaneously.

Protein synthesis inhibition is the primary mechanism of trichothecene toxicity. These compounds bind to the peptidyl transferase center of the 60S ribosomal subunit, halting translation. This affects rapidly dividing cells disproportionately — explaining why the immune system, gastrointestinal epithelium, and bone marrow are primary targets.

Oxidative stress is induced by multiple mycotoxin classes. Ochratoxin A generates reactive oxygen species through disruption of mitochondrial electron transport. Aflatoxins are bioactivated by cytochrome P450 enzymes into reactive epoxide intermediates that form covalent DNA adducts.

Membrane disruption is the mechanism of beauvericin and other ionophoric mycotoxins. These compounds form cation-selective channels in cell membranes, disrupting the ion gradients essential for cellular function. Even trace detection of ionophoric mycotoxins warrants monitoring due to their mechanism of action.

Endocrine disruption by zearalenone occurs through direct binding to estrogen receptors. Its binding affinity, while lower than estradiol, is sufficient to produce biological effects at concentrations commonly found in contaminated grain products.

CLINICAL IMPLICATIONS

The clinical presentation of mycotoxin-related illness is characteristically multisystem, making diagnosis challenging within traditional single-organ specialty frameworks.

Common presenting complaints include persistent fatigue, cognitive impairment ("brain fog"), respiratory symptoms, recurrent sinusitis, skin rashes, gastrointestinal disturbance, and neuropsychiatric symptoms including anxiety, depression, and sleep disruption. The overlap with conditions such as chronic fatigue syndrome, fibromyalgia, and multiple chemical sensitivity has historically led to underdiagnosis.

Biomarker testing has advanced significantly. Urinary mycotoxin panels can detect several compounds and their metabolites. Environmental testing via LC-MS/MS (liquid chromatography–tandem mass spectrometry) can identify and quantify 40+ analytes from surface samples, providing objective evidence of contamination.

Treatment approaches in clinical practice typically involve: (1) source identification and removal/remediation, (2) binding agents to reduce gastrointestinal reabsorption, (3) support for hepatic detoxification pathways, (4) treatment of secondary conditions (e.g., fungal colonization, inflammatory cascading), and (5) environmental controls to prevent re-exposure.

The critical first step in any treatment protocol remains environmental — identifying and eliminating the source of exposure. Without this step, clinical interventions are unlikely to produce sustained improvement.

RESEARCH GAPS

Despite significant advances in mycotoxicology, several critical gaps remain in our understanding of indoor mycotoxin exposure and its health effects.

Combined exposure effects are poorly characterized. Most toxicological studies examine single compounds, but real-world exposure typically involves multiple mycotoxins simultaneously. Synergistic or additive effects between compounds are likely but inadequately quantified. Multiple detections in combination, even at sub-quantification levels, may indicate more significant risk than any single result suggests.

Chronic low-dose exposure thresholds are not established for indoor environments. Occupational exposure limits exist for some agricultural settings, but residential guidelines are largely absent. This makes interpretation of environmental testing results dependent on comparative analysis rather than absolute standards.

Genetic susceptibility varies significantly. Polymorphisms in HLA-DR genes have been associated with increased susceptibility to mold-related illness, but the interaction between genetic factors and specific mycotoxin exposures is not well-mapped.

Longitudinal health outcomes data for building occupants with documented mycotoxin exposure is limited. Most studies are cross-sectional, making it difficult to establish causal relationships or predict long-term health trajectories.

Standardized diagnostic criteria for mycotoxin-related illness do not exist in mainstream medical practice, leading to significant variability in clinical recognition and treatment approaches across providers.

References
  1. [1]

    Bennett, J. W., & Klich, M. (2003). Mycotoxins. Clinical Microbiology Reviews, 16(3), 497–516.

  2. [2]

    IARC Working Group. (2012). Chemical Agents and Related Occupations. IARC Monographs on the Evaluation of Carcinogenic Risks to Humans, 100F.

  3. [3]

    Doi, K., & Uetsuka, K. (2011). Mechanisms of mycotoxin-induced neurotoxicity through oxidative stress-associated pathways. International Journal of Molecular Sciences, 12(8), 5213–5237.

  4. [4]

    Escrivá, L., Font, G., & Manyes, L. (2015). In vivo toxicity studies of fusarium mycotoxins in the last decade: A review. Food and Chemical Toxicology, 78, 185–206.

  5. [5]

    Kraft, S., et al. (2021). Mycotoxin exposure and neuropsychiatric symptoms: A systematic review. Environmental Research, 198, 111205.

  6. [6]

    Peraica, M., et al. (1999). Toxic effects of mycotoxins in humans. Bulletin of the World Health Organization, 77(9), 754–766.

  7. [7]

    Straus, D. C. (2009). Molds, mycotoxins, and sick building syndrome. Toxicology and Industrial Health, 25(9-10), 617–635.

  8. [8]

    WHO. (2018). Mycotoxins. Fact Sheet. World Health Organization.

  9. [9]

    Jarvis, B. B., & Miller, J. D. (2005). Mycotoxins as harmful indoor air contaminants. Applied Microbiology and Biotechnology, 66(4), 367–372.

  10. [10]

    Shoemaker, R. C., & House, D. E. (2006). Sick building syndrome (SBS) and exposure to water-damaged buildings. Neurotoxicology and Teratology, 28(5), 573–588.

Results pertain only to the documented test. This content is for informational purposes and does not constitute medical advice. Superstratum Labs, Newton NC.