*/
(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
How to Fit a Suit for Square Shoulders—A Custom Tailoring Guide
Getting a suit that fits perfectly is more than just picking the right size—it’s about understanding your unique body shape and making precise adjustments . For Philip Wadle , a hospital strategy executive, the biggest challenge was his square shoulders , which made off-the-rack jackets nearly impossible to fit.
“Any off-the-rack jacket is gonna have to be tailored, if not once, twice, maybe three times to get the fit right. And even then, it never quite looks like it’s supposed to.”
Enter custom tailoring.
The Shoulder Fix: Eliminating the Collar Roll ✔ The Issue: Philip’s square shoulders lifted the back of the jacket , causing an unflattering collar roll —a common problem for guys with naturally upright posture.
✔ The Solution: We adjusted the collar height and squared the shoulders, allowing the fabric to sit smoothly across the neck instead of pulling away.
✔ The Result: A clean, structured fit that hugs the body properly—no bunching, no gaps, just seamless drape .
The Collar Gap Adjustment Another issue with square shoulders? Collar gaps . When the jacket’s back rises too much , it creates a noticeable space between the collar and the neck.
✔ The Fix: We made the collar slightly smaller , ensuring it hugs the neck without stiffness.
✔ Why It Matters: The collar is the foundation of any great jacket fit—if it’s too loose, the whole look feels sloppy. If it’s too tight, it feels restrictive. Getting it just right makes all the difference.
Now, Philip’s forest green corduroy suit isn’t just stylish—it’s built for him . No unnecessary alterations, no second-guessing. Just a tailored fit that moves effortlessly.
Customize Your Perfect Suit.
MORE FROM ARTICLES OF STYLE: