/**
* Geolocation-based product restrictions
* Compatible with all Shopify plans (no checkout scripts required)
* Version 3.4 - Clean rebuild with essential fixes only
*/
// Prevent duplicate initialization
if (!window.geoRestrictionsInitialized) {
window.geoRestrictionsInitialized = true;
class GeolocationRestrictions {
constructor() {
this.config = {
enabled: window.geoRestrictionSettings?.enabled || false,
allowedCountries: this.parseCountries(window.geoRestrictionSettings?.allowedCountries || 'US'),
restrictedMessage: window.geoRestrictionSettings?.restrictedMessage || 'This product is not available in your region.',
showContactLink: window.geoRestrictionSettings?.showContactLink || true,
productTemplate: window.geoRestrictionSettings?.productTemplate || 'default',
isCollectionPage: window.geoRestrictionSettings?.isCollectionPage || false,
cloudflareCountry: window.geoRestrictionSettings?.cloudflareCountry || null
};
this.userCountry = null;
this.storageKey = 'shopify_user_country';
this.storageExpiry = 'shopify_country_expiry';
}
parseCountries(countriesString) {
return countriesString
.toUpperCase()
.split(',')
.map(c => c.trim())
.filter(c => c.length === 2);
}
async init() {
console.log('=== GEO RESTRICTIONS INIT v3.4 ===');
console.log('Enabled:', this.config.enabled);
console.log('Product Template:', this.config.productTemplate);
console.log('Is Collection Page:', this.config.isCollectionPage);
if (!this.config.enabled) {
console.log('Geolocation restrictions disabled');
this.removeLoadingClass();
return;
}
if (this.config.productTemplate === 'records') {
console.log('Records template - no restrictions, showing all content');
this.removeLoadingClass();
this.showAllowedElements();
return;
}
if (window.CLOUDFLARE_COUNTRY) {
console.log('Using Cloudflare Worker country:', window.CLOUDFLARE_COUNTRY);
this.userCountry = window.CLOUDFLARE_COUNTRY;
this.setCachedCountry(this.userCountry);
this.applyRestrictions();
return;
}
// Note: Removed Shopify.country check - it reflects market selection, not actual visitor location
const cachedCountry = this.getCachedCountry();
if (cachedCountry) {
this.userCountry = cachedCountry;
this.applyRestrictions();
return;
}
await this.detectCountry();
this.applyRestrictions();
}
removeLoadingClass() {
document.documentElement.classList.remove('geo-checking');
document.documentElement.classList.add('geo-checked');
}
getCachedCountry() {
try {
const expiry = sessionStorage.getItem(this.storageExpiry);
const country = sessionStorage.getItem(this.storageKey);
if (expiry && country && Date.now() < parseInt(expiry)) {
console.log(`Using cached country: ${country}`);
return country;
}
} catch (e) {
console.warn('sessionStorage not available:', e);
}
return null;
}
setCachedCountry(country) {
try {
// Cache for 1 hour
const expiry = Date.now() + (60 * 60 * 1000);
sessionStorage.setItem(this.storageKey, country);
sessionStorage.setItem(this.storageExpiry, expiry.toString());
} catch (e) {
console.warn('Could not cache country:', e);
}
}
async detectCountry() {
try {
const response = await fetch('https://ipapi.co/json/', {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) throw new Error('Geolocation API failed');
const data = await response.json();
this.userCountry = data.country_code || 'US';
console.log(`Detected country: ${this.userCountry}`);
this.setCachedCountry(this.userCountry);
} catch (error) {
console.warn('Country detection failed, defaulting to US:', error);
this.userCountry = 'US';
}
}
isCountryAllowed() {
return this.config.allowedCountries.includes(this.userCountry);
}
collapseParentIfEmpty(el) {
el.style.margin = '0';
el.style.padding = '0';
el.style.height = '0';
el.style.minHeight = '0';
const parent = el.parentElement;
if (!parent) return;
const tagName = parent.tagName.toLowerCase();
const className = (parent.className || '').toLowerCase();
if (tagName !== 'div' && tagName !== 'span') return;
if (className.includes('product')) return;
if (className.includes('info')) return;
if (className.includes('content')) return;
if (className.includes('wrapper')) return;
if (className.includes('container')) return;
if (className.includes('form')) return;
if (className.includes('main')) return;
if (className.includes('section')) return;
if (className.includes('block') && !className.includes('text-block') && !className.includes('group-block')) return;
const children = Array.from(parent.children);
if (children.length === 1 && children[0] === el) {
parent.style.display = 'none';
parent.style.margin = '0';
parent.style.padding = '0';
parent.setAttribute('data-geo-restricted', 'true');
}
}
// Remove "- Unavailable" and "- Sold out" from variant selectors
cleanVariantSelectors() {
console.log('=== CLEANING VARIANT SELECTORS ===');
const cleanText = (text) => {
return text
.replace(/ - Unavailable/gi, '')
.replace(/ - Sold out/gi, '')
.replace(/ — Unavailable/gi, '')
.replace(/ — Sold out/gi, '')
.replace(/- Unavailable/gi, '')
.replace(/- Sold out/gi, '');
};
// Clean all select options - with whitespace normalization
document.querySelectorAll('select option').forEach(option => {
const originalText = option.textContent;
const normalizedText = originalText.replace(/\s+/g, ' ').trim();
if (normalizedText.toLowerCase().includes('unavailable') || normalizedText.toLowerCase().includes('sold out')) {
const cleaned = normalizedText
.replace(/\s*-\s*Unavailable/gi, '')
.replace(/\s*—\s*Unavailable/gi, '')
.replace(/\s*-\s*Sold out/gi, '')
.replace(/\s*—\s*Sold out/gi, '')
.trim();
if (cleaned !== normalizedText) {
option.setAttribute('data-original-text', originalText);
option.textContent = cleaned;
console.log('Cleaned option:', normalizedText, '->', cleaned);
}
}
});
// Clean variant picker displayed values
document.querySelectorAll('variant-picker span, variant-picker div, .variant-option span, [class*="variant"] span').forEach(el => {
if (el.children.length === 0) {
const text = el.textContent;
if ((text.includes('Unavailable') || text.includes('Sold out')) && text.length < 100) {
const cleaned = cleanText(text);
if (cleaned !== text) {
el.setAttribute('data-original-text', text);
el.textContent = cleaned;
console.log('Cleaned span:', text, '->', cleaned);
}
}
}
});
// Run again after delays
setTimeout(() => this.cleanVariantSelectorsDelayed(), 100);
setTimeout(() => this.cleanVariantSelectorsDelayed(), 500);
setTimeout(() => this.cleanVariantSelectorsDelayed(), 1500);
// Set up MutationObserver for dynamic changes
document.querySelectorAll('variant-picker, .variant-picker').forEach(picker => {
if (!picker.hasAttribute('data-geo-observed')) {
picker.setAttribute('data-geo-observed', 'true');
const observer = new MutationObserver(() => {
this.cleanVariantSelectorsDelayed();
});
observer.observe(picker, { childList: true, subtree: true, characterData: true });
}
});
}
cleanVariantSelectorsDelayed() {
document.querySelectorAll('select option').forEach(option => {
const originalText = option.textContent;
const normalizedText = originalText.replace(/\s+/g, ' ').trim();
if (normalizedText.toLowerCase().includes('unavailable') || normalizedText.toLowerCase().includes('sold out')) {
option.textContent = normalizedText
.replace(/\s*-\s*Unavailable/gi, '')
.replace(/\s*—\s*Unavailable/gi, '')
.replace(/\s*-\s*Sold out/gi, '')
.replace(/\s*—\s*Sold out/gi, '')
.trim();
}
});
document.querySelectorAll('variant-picker span, variant-picker div, .variant-option span').forEach(el => {
if (el.children.length === 0 && el.textContent) {
const text = el.textContent;
if ((text.includes('Unavailable') || text.includes('Sold out')) && text.length < 100) {
el.textContent = text
.replace(/ - Unavailable/gi, '')
.replace(/ - Sold out/gi, '')
.replace(/ — Unavailable/gi, '')
.replace(/ — Sold out/gi, '')
.replace(/- Unavailable/gi, '')
.replace(/- Sold out/gi, '');
}
}
});
}
applyRestrictions() {
this.removeLoadingClass();
if (this.config.productTemplate === 'records') {
console.log('Records template - skipping restrictions');
document.documentElement.classList.add('geo-allowed');
this.showAllowedElements();
return;
}
const allowed = this.isCountryAllowed();
console.log(`Country: ${this.userCountry}, Allowed: ${allowed}`);
if (!allowed) {
document.documentElement.classList.add('geo-restricted');
this.hideRestrictedElements();
// Note: showRestrictionMessage removed - geo-dealer-display.liquid handles this now
} else {
document.documentElement.classList.add('geo-allowed');
this.showAllowedElements();
}
}
hideRestrictedElements() {
console.log('=== HIDING RESTRICTED ELEMENTS ===');
console.log('Is Collection Page:', this.config.isCollectionPage);
// Hide "Sold Out" badges on collection pages
if (this.config.isCollectionPage) {
document.querySelectorAll('.product-badges__badge, .product-badges, [class*="badge"], .badge').forEach(el => {
const text = el.textContent.trim().toLowerCase();
if (text === 'sold out' || text === 'soldout' || text.includes('sold out')) {
el.style.display = 'none';
el.setAttribute('data-geo-hidden-badge', 'true');
}
});
}
// Hide add to cart text metafield blocks (short pricing notes)
document.querySelectorAll('.text-block, .group-block, rte-formatter, [class*="text-block"], [class*="rich-text"], .rte').forEach(el => {
const text = el.textContent.trim().toLowerCase();
if (text.length < 100) {
if (text.includes('price is per') ||
text.includes('price per') ||
text.includes('priced per') ||
text.includes('sold as pair') ||
text.includes('sold as a pair') ||
text === 'per pair' ||
text.includes('each unit') ||
text.includes('price includes')) {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
this.collapseParentIfEmpty(el);
}
}
});
// Hide prices
const priceSelectors = [
'.product__price', '.price', '.product-price', '[data-price]', '.money',
'.price--on-sale', '.price-item--sale', '.price-item--regular',
'.price__sale', '.price__regular', '.compare-at-price', '.was-price'
];
priceSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
});
// Collection page: hide prices in cards
if (this.config.isCollectionPage) {
document.querySelectorAll('[class*="card"] .price, [class*="grid"] .price, .price s, .price del, s, del').forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
}
// Hide add to cart buttons
const cartSelectors = [
'button[name="add"]', '.product-form__submit', '.add-to-cart',
'form[action*="/cart/add"]', '.shopify-payment-button', '.product-form__buttons',
'.trade-in-buttons-wrapper', '.trade-in-button-group', '.trade-in-btn',
'.buy-buttons-block', '.product-form-buttons', '.add-to-cart-button',
'button[data-open-trade-modal]', 'button[data-add-to-cart-normal]', 'button[data-add-with-trade]'
];
cartSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
});
// Hide quantity selectors
document.querySelectorAll('.product-form__quantity, .quantity-selector').forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
// Hide trade-in promo text
document.querySelectorAll('strong, p, div, span, section, aside').forEach(el => {
const text = el.textContent.trim();
if (text.length < 300) {
if ((text.startsWith('Receive up to') && text.includes('trade in')) ||
(text.includes('Now available') && text.includes('trade in')) ||
text.includes('Receive up to $') ||
text.includes('trade in your')) {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
this.collapseParentIfEmpty(el);
}
}
if (text === '-%}' || text.trim() === '-%}') {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
}
});
// Hide trade-in class elements
document.querySelectorAll('[class*="trade-in"], [class*="tradein"], [class*="trade-up"], [class*="tradeup"]').forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
// Hide accordion sections
document.querySelectorAll('accordion-custom, details').forEach(el => {
const text = el.textContent;
if (text.includes('Talk to a Hi-Fi Specialist') ||
text.includes('Home Audition Made Easy') ||
(text.includes('Shipping') && !text.includes('Shipping policy'))) {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
}
});
// Hide menu prices
document.querySelectorAll('.menu .price, .dropdown .price, .mega-menu .price, nav .price, header .price').forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
// Hide waiting list / waitlist buttons (for international visitors)
document.querySelectorAll('[class*="waiting"], [class*="waitlist"], [class*="Waiting"], [class*="Waitlist"]').forEach(el => {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
});
document.querySelectorAll('button, a.button, a[class*="btn"]').forEach(el => {
const text = el.textContent.toLowerCase();
if (text.includes('waiting list') || text.includes('waitlist') || text.includes('notify me')) {
el.style.display = 'none';
el.setAttribute('data-geo-restricted', 'true');
}
});
// Clean variant selectors (remove "- Unavailable")
this.cleanVariantSelectors();
}
showAllowedElements() {
const restrictionMsg = document.querySelector('.geo-restriction-message');
if (restrictionMsg) restrictionMsg.remove();
document.querySelectorAll('[data-geo-restricted]').forEach(el => {
el.style.display = '';
el.style.margin = '';
el.style.padding = '';
el.style.height = '';
el.style.minHeight = '';
el.style.overflow = '';
el.removeAttribute('data-geo-restricted');
});
document.querySelectorAll('[data-geo-hidden-badge]').forEach(el => {
el.style.display = '';
el.removeAttribute('data-geo-hidden-badge');
});
document.querySelectorAll('[data-original-text]').forEach(el => {
el.textContent = el.getAttribute('data-original-text');
el.removeAttribute('data-original-text');
});
}
setTestCountry(countryCode) {
this.userCountry = countryCode.toUpperCase();
this.setCachedCountry(this.userCountry);
document.documentElement.classList.remove('geo-restricted', 'geo-allowed');
document.querySelectorAll('[data-geo-restricted]').forEach(el => {
el.style.display = '';
el.style.margin = '';
el.style.padding = '';
el.style.height = '';
el.style.minHeight = '';
el.style.overflow = '';
el.removeAttribute('data-geo-restricted');
});
document.querySelectorAll('[data-geo-hidden-badge]').forEach(el => {
el.style.display = '';
el.removeAttribute('data-geo-hidden-badge');
});
document.querySelectorAll('[data-original-text]').forEach(el => {
el.textContent = el.getAttribute('data-original-text');
el.removeAttribute('data-original-text');
});
const msg = document.querySelector('.geo-restriction-message');
if (msg) msg.remove();
this.applyRestrictions();
this.removeLoadingClass();
}
clearCache() {
try {
sessionStorage.removeItem(this.storageKey);
sessionStorage.removeItem(this.storageExpiry);
// Also clear old localStorage entries
localStorage.removeItem(this.storageKey);
localStorage.removeItem(this.storageExpiry);
console.log('Country cache cleared');
} catch (e) {
console.warn('Could not clear cache:', e);
}
}
}
function initGeoRestrictions() {
if (!window.geoRestrictionSettings) {
console.warn('Waiting for geoRestrictionSettings...');
setTimeout(initGeoRestrictions, 50);
return;
}
console.log('Initializing geo restrictions v3.4');
window.geoRestrictions = new GeolocationRestrictions();
window.geoRestrictions.init();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initGeoRestrictions);
} else {
initGeoRestrictions();
}
window.testGeoRestriction = function(countryCode) {
if (window.geoRestrictions) {
window.geoRestrictions.setTestCountry(countryCode);
}
};
window.clearGeoCache = function() {
if (window.geoRestrictions) {
window.geoRestrictions.clearCache();
location.reload();
}
};
}
Skip to content
When we're working on new product directions, one of the first questions we ask is where this potential new product fits in: an addition to an existing genre (like a DAC, cable, or phono stage) or something entirely new (like our LANRover USB transport)?
Adding to an existing category of products, like a new DAC, has very different design challenges than blazing a new path forward. What are we offering that is unavailable elsewhere? Better performance, price, or a new feature you can't get anywhere else? In other words, what makes this new product unique enough to stand out in a crowded field?
When dreaming up new products my internal, personal, conversation with my homunculus goes something like this: if I had a magic wand and could design any feature or performance characteristic in a DAC, what would that look like? I never let practical matters enter into that conversation.
I just dream.
I suppose every designer has their own way, their own internal conversations, but for what it's worth, that's how mine goes.
If you had a magic wand, what would your new product look like?
💬
Join the Conversation
Share your thoughts below and get our latest posts in your inbox
We respect your privacy. Unsubscribe anytime.
0 comments