/**
* 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
October, 2000 was an interesting year. Online banking was launched and, 23 years later, people think nothing of purchasing everything from CDs to automobiles over the internet. Heck, I bought (and traded in) my last two cars online and have never had to go back and suffer the auto-dealer dance again.
Who'da thunk?
This short bit of history only as a note to let those of you (rightfully) upset we removed the PayPal option from our website, that it is now back.
Apologies for the interim as we worked to smooth out the payment processes on the new website.
Grab a few albums, SACDs, or downloads here , or upgrade your power amplifier here .
Thanks again for the understanding.
💬
Join the Conversation
Share your thoughts below and get our latest posts in your inbox
We respect your privacy. Unsubscribe anytime.
0 comments