*/ (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 ROLL A FELT HAT

HOW TO ROLL A FELT HAT

I love wearing classic brimmed hats, but I always run into problems with storage.

As proper etiquette goes, a gentleman is supposed to remove his hat upon entering an establishment…but where are you supposed to store it? Very few places offer hat check (like restaurants, bars, theaters, etc. did back when men wouldn’t leave the house without one). So you’re stuck holding it. And if you’re carrying a typical Fedora or Trilby, it’s relatively high maintenance. It can’t be folded, rolled, or crushed in any way, because then it will require professional re-shaping and steaming.

This is why I wear my “crushable” Borsalino more than any other hat in my collection. I paid a little more for it, but it was totally worth it. Like a good casual blazer, it’s soft and unstructured. It’s made from one thin layer of high-quality fur felt, with no lining or stiffener, just a flexible leather sweatband on the inside. This might not seem like an important detail, but it allows me to roll my hat and stick it in my pocket, which makes all the difference when stepping inside (or traveling).

Here’s how to properly roll a crushable hat:

1. Remove Hat

Man in an elegant green suit holding a black felt hat, showcasing classic menswear style

2. Smooth-out the pinch in the crown, making it nice and round.

Close-up of a man in a green suit holding the brim of a black felt hat, demonstrating refined men's fashion

3. Fold the crown in half, across the top of the hat from front to back.

Man in a green suit rolling a black felt hat, demonstrating a step in the hat-folding process

4. Start from one side and begin rolling.

Man in a green suit folding a black felt hat as part of a step-by-step guide

5. Boom. Hat is now serviette size.

Man holding a neatly rolled black felt hat in a green suit, emphasizing functional men's accessories

6. Store hat in pocket (or bag, or suitcase, etc).

Man in a green suit placing a rolled black felt hat into his pocket, showcasing practical menswear

7. Continue living without having to worry about where to place your delicate hat.

Man in a green pinstripe suit with a black rolled felt hat in his pocket, highlighting elegant details.

When it’s time to put the hat back on, simply unroll it and reshape the pinch in the crown. Then straighten out the brim to desired angle.

Man tipping a black felt hat while wearing a green suit, combining classic and stylish menswear

The other beauty of a soft crushable hat, is that you can easily mold the brim to any desired shape. For example, I’ve worn this same hat with both a straight brim and a “floppy” brim (which creates an entirely different look).

Man walking down the street in a felt hat, textured sweater, and jeans, presenting casual men's fashion.

Thanks, as always, for reading.

Yours in style,

Dan Trepanier

 

NEED HELP WITH YOUR WARDROBE?


MORE FROM ARTICLES OF STYLE