*/
(function() {
'use strict';
const CONFIG = {
storageKey: 'aos_utm_data',
storageDuration: 30, // days
utmParams: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'],
// Shopify attribution mapping
shopifyFields: {
utm_source: 'source',
utm_medium: 'medium',
utm_campaign: 'campaign',
utm_content: 'content',
utm_term: 'term',
landing_page: 'landing_site',
referrer: 'referring_site'
}
};
// ============================================
// STEP 1: CAPTURE & STORE UTM PARAMETERS
// ============================================
function captureUTMFromURL() {
const params = new URLSearchParams(window.location.search);
const utmData = {};
let hasUTM = false;
CONFIG.utmParams.forEach(param => {
if (params.has(param)) {
utmData[param] = params.get(param);
hasUTM = true;
}
});
return hasUTM ? utmData : null;
}
function getStoredUTMData() {
try {
const stored = localStorage.getItem(CONFIG.storageKey);
if (stored) {
const data = JSON.parse(stored);
// Check if data is still valid (within duration)
const timestamp = data.timestamp || 0;
const now = Date.now();
const maxAge = CONFIG.storageDuration * 24 * 60 * 60 * 1000;
if (now - timestamp < maxAge) {
return data;
}
}
} catch (e) {
console.error('Error reading UTM data:', e);
}
return null;
}
function saveUTMData(utmData) {
try {
const dataToStore = {
...utmData,
timestamp: Date.now(),
landing_page: utmData.landing_page || window.location.href,
referrer: utmData.referrer || document.referrer || 'direct'
};
localStorage.setItem(CONFIG.storageKey, JSON.stringify(dataToStore));
// Also set as cookie for server-side access
setCookie(CONFIG.storageKey, JSON.stringify(dataToStore), CONFIG.storageDuration);
console.log('UTM data saved:', dataToStore);
return dataToStore;
} catch (e) {
console.error('Error saving UTM data:', e);
return null;
}
}
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/;SameSite=Lax";
}
// ============================================
// STEP 2: MANAGE UTM DATA (NEW OR STORED)
// ============================================
function initializeUTMTracking() {
// Check for new UTM parameters in current URL
const newUTM = captureUTMFromURL();
if (newUTM) {
// New UTM found - overwrite stored data
console.log('New UTM parameters detected, updating storage');
return saveUTMData({
...newUTM,
landing_page: window.location.href,
referrer: document.referrer || 'direct'
});
} else {
// No new UTM - get stored data
const storedUTM = getStoredUTMData();
if (!storedUTM) {
// First visit, no UTM - save as direct traffic
console.log('First visit without UTM, marking as direct');
return saveUTMData({
utm_source: 'direct',
utm_medium: 'none',
landing_page: window.location.href,
referrer: document.referrer || 'direct'
});
}
console.log('Using stored UTM data:', storedUTM);
return storedUTM;
}
}
// ============================================
// STEP 3: AUTO-APPEND UTM TO INTERNAL LINKS
// ============================================
function appendUTMToURL(url, utmData) {
try {
const urlObj = new URL(url, window.location.origin);
// Only add UTM if not already present
CONFIG.utmParams.forEach(param => {
if (utmData[param] && !urlObj.searchParams.has(param)) {
urlObj.searchParams.set(param, utmData[param]);
}
});
return urlObj.toString();
} catch (e) {
return url; // Return original if parsing fails
}
}
function isInternalLink(url) {
try {
const urlObj = new URL(url, window.location.origin);
return urlObj.hostname === window.location.hostname;
} catch (e) {
// Relative URLs are internal
return !url.startsWith('http://') && !url.startsWith('https://');
}
}
function hasUTMParameters(url) {
try {
const urlObj = new URL(url, window.location.origin);
return CONFIG.utmParams.some(param => urlObj.searchParams.has(param));
} catch (e) {
return false;
}
}
function addUTMToInternalLinks(utmData) {
if (!utmData) return;
// Function to update a single link
function updateLink(link) {
const href = link.getAttribute('href');
if (!href || href === '#' || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return;
}
// Only update internal links without UTM
if (isInternalLink(href) && !hasUTMParameters(href)) {
const newHref = appendUTMToURL(href, utmData);
link.setAttribute('href', newHref);
}
}
// Update all existing links
document.querySelectorAll('a[href]').forEach(updateLink);
// Watch for dynamically added links
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) { // Element node
if (node.tagName === 'A' && node.hasAttribute('href')) {
updateLink(node);
}
// Also check children
node.querySelectorAll && node.querySelectorAll('a[href]').forEach(updateLink);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// ============================================
// STEP 4: SEND TO SHOPIFY ATTRIBUTION
// ============================================
function sendToShopifyAttribution(utmData) {
if (!utmData) return;
// Method 1: Use Shopify's analytics tracking
if (window.ShopifyAnalytics && window.ShopifyAnalytics.lib) {
try {
const attributionData = {};
Object.keys(CONFIG.shopifyFields).forEach(key => {
const shopifyField = CONFIG.shopifyFields[key];
if (utmData[key]) {
attributionData[shopifyField] = utmData[key];
}
});
// Set landing site
if (utmData.landing_page) {
attributionData.landing_site = utmData.landing_page;
}
// Set referring site
if (utmData.referrer && utmData.referrer !== 'direct') {
attributionData.referring_site = utmData.referrer;
}
console.log('Sending to Shopify Analytics:', attributionData);
// This sets the attribution data for the session
if (window.ShopifyAnalytics.lib.config) {
window.ShopifyAnalytics.lib.config.Attribution = attributionData;
}
} catch (e) {
console.error('Error setting Shopify attribution:', e);
}
}
// Method 2: Add to checkout attributes (for order-level tracking)
addToCheckoutAttributes(utmData);
// Method 3: Add to cart note attributes
addToCartAttributes(utmData);
}
function addToCheckoutAttributes(utmData) {
// This will be picked up during checkout
window._shopifyCheckoutAttributes = window._shopifyCheckoutAttributes || {};
CONFIG.utmParams.forEach(param => {
if (utmData[param]) {
window._shopifyCheckoutAttributes[param] = utmData[param];
}
});
if (utmData.landing_page) {
window._shopifyCheckoutAttributes['landing_page'] = utmData.landing_page;
}
if (utmData.referrer) {
window._shopifyCheckoutAttributes['referrer'] = utmData.referrer;
}
}
function addToCartAttributes(utmData) {
// Hook into cart form submissions
document.addEventListener('submit', function(e) {
const form = e.target;
// Check if it's a cart form
if (form.action && (form.action.includes('/cart/add') || form.action.includes('/cart'))) {
// Add hidden fields for UTM data
CONFIG.utmParams.forEach(param => {
if (utmData[param]) {
addHiddenField(form, `attributes[${param}]`, utmData[param]);
}
});
if (utmData.landing_page) {
addHiddenField(form, 'attributes[landing_page]', utmData.landing_page);
}
if (utmData.referrer) {
addHiddenField(form, 'attributes[referrer]', utmData.referrer);
}
}
});
}
function addHiddenField(form, name, value) {
// Check if field already exists
let field = form.querySelector(`input[name="${name}"]`);
if (!field) {
field = document.createElement('input');
field.type = 'hidden';
field.name = name;
form.appendChild(field);
}
field.value = value;
}
// ============================================
// STEP 5: AJAX CART HANDLING
// ============================================
function interceptAjaxCalls(utmData) {
// Intercept fetch calls
const originalFetch = window.fetch;
window.fetch = function(...args) {
let [url, options] = args;
if (typeof url === 'string' && url.includes('/cart/add')) {
options = options || {};
// Add UTM data to body if it's a POST request
if (options.method === 'POST' || !options.method) {
try {
const body = options.body ? JSON.parse(options.body) : {};
body.attributes = body.attributes || {};
CONFIG.utmParams.forEach(param => {
if (utmData[param]) {
body.attributes[param] = utmData[param];
}
});
if (utmData.landing_page) body.attributes.landing_page = utmData.landing_page;
if (utmData.referrer) body.attributes.referrer = utmData.referrer;
options.body = JSON.stringify(body);
} catch (e) {
console.error('Error adding UTM to fetch:', e);
}
}
}
return originalFetch.apply(this, [url, options]);
};
// Intercept jQuery AJAX if jQuery is loaded
if (window.jQuery) {
const originalAjax = jQuery.ajax;
jQuery.ajax = function(url, options) {
if (typeof url === 'object') {
options = url;
url = options.url;
}
if (url && url.includes('/cart/add')) {
options.data = options.data || {};
if (typeof options.data === 'object') {
options.data.attributes = options.data.attributes || {};
CONFIG.utmParams.forEach(param => {
if (utmData[param]) {
options.data.attributes[param] = utmData[param];
}
});
if (utmData.landing_page) options.data.attributes.landing_page = utmData.landing_page;
if (utmData.referrer) options.data.attributes.referrer = utmData.referrer;
}
}
return originalAjax.call(this, url, options);
};
}
}
// ============================================
// STEP 6: INITIALIZE EVERYTHING
// ============================================
function init() {
console.log('Initializing UTM Tracking System...');
// Initialize UTM tracking (capture new or get stored)
const utmData = initializeUTMTracking();
if (utmData) {
// Add UTM to all internal links
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => addUTMToInternalLinks(utmData));
} else {
addUTMToInternalLinks(utmData);
}
// Send to Shopify attribution
sendToShopifyAttribution(utmData);
// Intercept AJAX cart calls
interceptAjaxCalls(utmData);
console.log('UTM Tracking System initialized successfully');
}
}
// Start the tracking system
init();
// Expose utility function to get current UTM data
window.getUTMData = function() {
return getStoredUTMData();
};
// Expose function to manually clear UTM data
window.clearUTMData = function() {
localStorage.removeItem(CONFIG.storageKey);
console.log('UTM data cleared');
};
})();
Skip to Main Content
Jordan Shallow - aka the Muscle Doc - is a bobybuilder, fitness coach, and an absolute MONSTER in the gym.
He's 5'11" 260 lbs of pure muscle.
He's also one of my favorite clients because he always provides a nice challenge for our tailors and our digital fitting process.
Below is his try-on garment for his first order from our new factory.
Keep in mind, his custom try-on garment was built based on his body measurements (with some clients we request body measurements, when they are "off the charts").
Therefore, the try-on is what a typical "made to measure" suit would look like on him:
This is a great example of why a try-on garment is so necessary.
There are things you simply can't "measure".
Look at those lats for example!! Dude has full-bodied Christmas hams under each arm. It's virtually impossible to measure something like that.
The other important thing about the try-on (for all clients) is understanding their unique personal preference and personal style.
For body builders, for example, there's often two different preferences. Some guys want a very slim fit to show off the body they've worked so hard for. Other guys are so sick and tired of feeling tight and restricted in their clothing that they want more room and range of motion.
The try-on bridges the digital/physical gap, and allows us to
1) review the look on the body and
2) review the feel on the body.
Both are equally important when making a garments they will love and wear for a long time.
Here is a simplified overview of some of the adjustments we made to his fit, for his final garment(s):
Thanks, as always, for reading - and special thanks to Jordan for allowing us to share this.
Yours in style,
Dan Trepaner
NEED HELP WITH YOUR WARDROBE?
MORE FROM ARTICLES OF STYLE