/* ============================================================
KVS — Site Scripts v2.7
External JS for Kootenay Vape Shops
Loaded via:
Contains:
1. Age Verification Gate (19+ requirement)
2. Stock Urgency Badges (product pages) — UPDATED: SPA-aware
3. Trust Badges (product pages) — UPDATED: SPA-aware, stale cleanup
4. Google Tag Manager (noscript fallback injected)
5. Mailchimp Popup (mc.js loader)
6. "We Ship To" footer bar (province links)
7. Shipping Eligibility Guard (cart/checkout banner) — UPDATED: repositioned above cart
8. (removed in v2.7 — no longer needed)
9. Sticky Add-to-Cart Bar (mobile product pages)
10. Shipping Estimate Note (cart page)
11. Empty Search Recovery (search results)
12. Accessibility Fixes (focus outlines, contrast)
13. Ecwid SPA Page Hooks — NEW: re-fires features on SPA navigation
v2.7 CHANGELOG:
- REMOVED: initDefaultVariantFix (Section 8) — no longer needed.
Root cause fixed at the source: all product options set to
"Value selected automatically: None" via Ecwid API, so dropdowns
load on "Please choose" by default. No sold-out variant is ever
auto-selected, eliminating the need for JS intervention.
Sections v2.4–v2.6 of this feature are now obsolete.
v2.6 CHANGELOG:
- FIX: isSoldOutState() was checking three selectors that don't exist
on our Ecwid theme (.details-product-purchase__out-of-stock, etc).
Replaced with single proven selector: .product-details__product-soldout
Ecwid adds/removes this node on variant change — existence check only.
- FIX: findFirstValidOption() now skips Ecwid's "Please choose"
placeholder (value="Please choose", not empty) to avoid selecting
a non-purchasable option.
v2.5 CHANGELOG:
- REWRITE: initDefaultVariantFix (Section 8) — complete rewrite
merging Claude + GPT approaches. Now detects sold-out state via
actual DOM badges/disabled buttons instead of just option text.
Selects in-stock variant FIRST (while hidden), then unhides
container only after confirming state is valid. Handles Flavour
+ Strength sequentially with delay for Ecwid recalculation.
Single lightweight retry loop with overlap guard. Only unhides
if in-stock path exists — truly sold-out products stay as-is.
v2.4 CHANGELOG:
- NEW: initDefaultVariantFix (Section 8) — companion to Ecwid app.
Three-step fix: select in-stock option, dispatch change, unhide
container. Replaced by v2.5 rewrite.
v2.2 CHANGELOG:
- REMOVED: initVariantAutoSelect (Section 8) — replaced by Ecwid app
that hides out-of-stock variants entirely. Better UX than auto-selecting.
v2.1 CHANGELOG:
- FIX: Trust badges, stock badges, variant auto-select, and sticky ATC
now re-fire on Ecwid SPA navigation (category → product clicks).
Root cause: Ecwid uses SPA routing, so DOMContentLoaded only fires once.
- FIX: Variant auto-select uses Ecwid's "- Sold out" option labels
instead of waiting for stock text to update (faster, more reliable).
- FIX: Trust badges remove stale badges before re-injecting on new product.
- FIX: Variant auto-select also checks second dropdown (Strength) after
switching the first dropdown (Flavour).
============================================================ */
(function() {
'use strict';
/* ──────────────────────────────────────
1. AGE VERIFICATION GATE
────────────────────────────────────── */
function getCookie(name) {
var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
function setCookie(name, value, days) {
var expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + '=' + value + '; expires=' + expires + '; path=/; SameSite=Lax';
}
function initAgeGate() {
if (getCookie('kvs_age_ok') === '1') return;
var style = document.createElement('style');
style.textContent = [
'#kvs-age-gate{position:fixed;inset:0;z-index:999999;display:flex;align-items:center;justify-content:center;background:rgba(8,8,16,0.97);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);font-family:"Barlow","Arial",sans-serif;opacity:0;transition:opacity 0.35s ease;}',
'#kvs-age-gate.kvs-visible{opacity:1;}',
'#kvs-age-gate-box{position:relative;background:linear-gradient(160deg,#0e0e1a,#141428);border:1px solid rgba(155,45,255,0.35);border-radius:20px;padding:3rem 2.5rem 2.5rem;max-width:440px;width:90%;text-align:center;box-shadow:0 0 80px rgba(155,45,255,0.2),0 30px 80px rgba(0,0,0,0.6);}',
'#kvs-age-gate-box::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(135deg,#00d4ff,#9b2dff,#ff2d9b);border-radius:20px 20px 0 0;}',
'#kvs-age-gate-logo{font-family:"Bebas Neue","Arial Narrow",sans-serif;font-size:2.8rem;letter-spacing:0.08em;background:linear-gradient(135deg,#00d4ff,#9b2dff,#ff2d9b);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:0.25rem;line-height:1;}',
'#kvs-age-gate-sub{font-size:0.7rem;letter-spacing:0.18em;text-transform:uppercase;color:rgba(144,144,176,0.7);margin-bottom:2rem;}',
'#kvs-age-gate-title{font-family:"Bebas Neue","Arial Narrow",sans-serif;font-size:1.9rem;letter-spacing:0.04em;color:#ffffff;margin-bottom:0.6rem;line-height:1.15;}',
'#kvs-age-gate-body{font-size:0.88rem;color:rgba(144,144,176,0.85);line-height:1.65;margin-bottom:2rem;}',
'#kvs-age-gate-body strong{color:rgba(240,240,255,0.9);}',
'.kvs-age-btn{display:block;width:100%;padding:0.9rem 1.5rem;border:none;border-radius:50px;font-family:"Barlow","Arial",sans-serif;font-weight:700;font-size:0.95rem;letter-spacing:0.06em;text-transform:uppercase;cursor:pointer;transition:transform 0.2s,box-shadow 0.2s;margin-bottom:0.75rem;}',
'#kvs-age-yes{background:linear-gradient(135deg,#00d4ff,#9b2dff,#ff2d9b);color:#ffffff;box-shadow:0 8px 30px rgba(155,45,255,0.4);}',
'#kvs-age-yes:hover{transform:translateY(-2px);box-shadow:0 12px 40px rgba(155,45,255,0.6);}',
'#kvs-age-no{background:transparent;color:rgba(144,144,176,0.7);border:1px solid rgba(155,45,255,0.25);}',
'#kvs-age-no:hover{border-color:rgba(155,45,255,0.5);color:rgba(240,240,255,0.8);}',
'#kvs-age-gate-legal{margin-top:1.5rem;font-size:0.68rem;color:rgba(144,144,176,0.45);line-height:1.5;letter-spacing:0.02em;}',
'@media(max-width:480px){#kvs-age-gate-box{padding:2.5rem 1.75rem 2rem;}#kvs-age-gate-logo{font-size:2.2rem;}#kvs-age-gate-title{font-size:1.6rem;}}'
].join('');
document.head.appendChild(style);
var gate = document.createElement('div');
gate.id = 'kvs-age-gate';
gate.setAttribute('role', 'dialog');
gate.setAttribute('aria-modal', 'true');
gate.setAttribute('aria-label', 'Age Verification');
gate.innerHTML = [
'
',
'
KOOTENAY VAPE
',
'
Trail • Creston • Kimberley • Grand Forks
',
'
Are You 19 or Older?
',
'
',
'Vaping products are for adults 19+ only in British Columbia.',
'
',
'By entering this site you confirm you are of legal age and agree to our Terms & Conditions.',
'
',
'
',
'
',
'
',
'This site sells nicotine products. Must be 19+ to purchase in BC.',
'
By entering you confirm you meet the legal age requirement.',
'
',
'
'
].join('');
document.documentElement.style.overflow = 'hidden';
document.body.appendChild(gate);
requestAnimationFrame(function() {
requestAnimationFrame(function() {
gate.classList.add('kvs-visible');
});
});
document.getElementById('kvs-age-yes').addEventListener('click', function() {
setCookie('kvs_age_ok', '1', 30);
gate.style.opacity = '0';
gate.style.transition = 'opacity 0.3s ease';
setTimeout(function() {
gate.remove();
document.documentElement.style.overflow = '';
}, 300);
});
document.getElementById('kvs-age-no').addEventListener('click', function() {
window.location.replace('https://www.google.com');
});
}
/* ──────────────────────────────────────
2. STOCK URGENCY BADGES (v2.1)
Rewrites "In stock: X available" text
with color-coded urgency messaging.
CSS classes defined in Custom CSS.
v2.1: Re-fires on SPA navigation via
Ecwid.OnPageLoaded hook.
────────────────────────────────────── */
function initStockBadges() {
var checkInterval = setInterval(function() {
var stockEl = document.querySelector('.details-product-purchase__place span');
if (!stockEl || stockEl.dataset.kvsProcessed) return;
var match = stockEl.textContent.trim().match(/In stock:\s*(\d+)\s*available/);
if (!match) return;
stockEl.dataset.kvsProcessed = '1';
var qty = parseInt(match[1]);
if (qty <= 2) {
stockEl.className += ' kvs-critical-stock';
stockEl.textContent = '\uD83D\uDD25 Only ' + qty + ' left — order soon!';
} else if (qty <= 5) {
stockEl.className += ' kvs-low-stock';
stockEl.textContent = '\u26A1 Only ' + qty + ' left in stock';
} else {
stockEl.className += ' kvs-in-stock';
stockEl.textContent = '\u2713 In stock — ' + qty + ' available';
}
}, 500);
setTimeout(function() { clearInterval(checkInterval); }, 15000);
}
/* ──────────────────────────────────────
3. TRUST BADGES (v2.1)
Injects trust indicators below the
Add to Bag button on product pages.
v2.1 FIX: Removes stale badges from
previous product on SPA navigation
before re-injecting. Re-fires via
Ecwid.OnPageLoaded hook.
────────────────────────────────────── */
function initTrustBadges() {
// v2.1: Remove stale badges from previous product (SPA navigation)
var old = document.querySelector('.kvs-trust-badges');
if (old) old.remove();
var checkInterval = setInterval(function() {
// Don't double-inject
if (document.querySelector('.kvs-trust-badges')) {
clearInterval(checkInterval);
return;
}
// Try multiple selectors to find the purchase area
// Different Ecwid layouts use different class names
var purchaseArea = null;
var selectors = [
'.details-product-purchase__add-to-bag',
'.details-product-purchase',
'[class*="product-purchase"]',
'.product-details-module'
];
for (var i = 0; i < selectors.length; i++) {
var el = document.querySelector(selectors[i]);
if (el) {
// For button-level selectors, walk up to the container
if (selectors[i].indexOf('add-to-bag') !== -1 || selectors[i].indexOf('button') !== -1) {
purchaseArea = el.closest('.details-product-purchase') ||
el.closest('[class*="product-purchase"]') ||
el.parentElement;
} else {
purchaseArea = el;
}
break;
}
}
if (!purchaseArea) return;
clearInterval(checkInterval);
var badges = document.createElement('div');
badges.className = 'kvs-trust-badges';
badges.innerHTML = [
'\uD83D\uDD12 Secure Checkout
',
'\uD83D\uDE9A Ships Across Canada
',
'\u23F0 Same-Day Dispatch
',
'\u2705 Tested by Our Team
'
].join('');
// Insert after the purchase area
if (purchaseArea.nextSibling) {
purchaseArea.parentNode.insertBefore(badges, purchaseArea.nextSibling);
} else {
purchaseArea.parentNode.appendChild(badges);
}
}, 500);
setTimeout(function() { clearInterval(checkInterval); }, 15000);
}
/* ──────────────────────────────────────
4. GTM NOSCRIPT FALLBACK
────────────────────────────────────── */
function initGTMNoscript() {
var noscript = document.createElement('noscript');
var iframe = document.createElement('iframe');
iframe.src = 'https://www.googletagmanager.com/ns.html?id=GTM-TWBWVRRR';
iframe.height = '0';
iframe.width = '0';
iframe.style.display = 'none';
iframe.style.visibility = 'hidden';
noscript.appendChild(iframe);
document.body.insertBefore(noscript, document.body.firstChild);
}
/* ──────────────────────────────────────
5. MAILCHIMP POPUP (mc.js)
────────────────────────────────────── */
function initMailchimp() {
!function(c,h,i,m,p){m=c.createElement(h),p=c.getElementsByTagName(h)[0],m.async=1,m.src=i,p.parentNode.insertBefore(m,p)}(document,"script","https://chimpstatic.com/mcjs-connected/js/users/967dfb1988c0dce580a3462f1/27a7a21cddeb06fe4c455de67.js");
}
/* ──────────────────────────────────────
6. "WE SHIP TO" FOOTER BAR
────────────────────────────────────── */
function initShippingBar() {
if (document.getElementById('kvs-ship-bar')) return;
var bar = document.createElement('div');
bar.id = 'kvs-ship-bar';
bar.innerHTML = [
'',
'
We Ship Across Canada',
'
',
'British Columbia',
'|',
'Saskatchewan',
'|',
'Nova Scotia',
'|',
'Newfoundland & Labrador',
'|',
'Full Shipping Info',
'',
'
'
].join('');
var style = document.createElement('style');
style.textContent = [
'#kvs-ship-bar{background:linear-gradient(135deg,rgba(14,14,26,0.95),rgba(20,20,40,0.95));border-top:1px solid rgba(155,45,255,0.25);border-bottom:1px solid rgba(155,45,255,0.25);padding:0;margin:0;font-family:"Barlow","Arial",sans-serif;}',
'#kvs-ship-bar-inner{max-width:1200px;margin:0 auto;padding:1rem 1.5rem;text-align:center;}',
'#kvs-ship-bar strong{display:block;font-family:"Bebas Neue","Arial Narrow",sans-serif;font-size:1.1rem;letter-spacing:0.06em;background:linear-gradient(135deg,#00d4ff,#9b2dff,#ff2d9b);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:0.5rem;}',
'.kvs-ship-links{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:0.25rem 0;}',
'.kvs-ship-links a{color:rgba(200,200,220,0.85);font-size:0.82rem;text-decoration:none;padding:0.15rem 0.5rem;transition:color 0.2s;}',
'.kvs-ship-links a:hover{color:#00d4ff;}',
'.kvs-ship-sep{color:rgba(155,45,255,0.35);font-size:0.75rem;padding:0 0.15rem;}',
'@media(max-width:600px){.kvs-ship-links{gap:0.1rem 0;}.kvs-ship-links a{font-size:0.78rem;padding:0.15rem 0.35rem;}.kvs-ship-sep{padding:0 0.05rem;}}'
].join('');
document.head.appendChild(style);
var footer = document.querySelector('.ec-footer') ||
document.querySelector('footer') ||
document.querySelector('[class*="footer"]');
if (footer) {
footer.parentNode.insertBefore(bar, footer);
} else {
document.body.appendChild(bar);
}
}
/* ──────────────────────────────────────
7. SHIPPING ELIGIBILITY GUARD (v2.0)
Shows a banner on cart/checkout pages.
v2.0: Inserts ABOVE cart area.
────────────────────────────────────── */
function initShippingGuard() {
var href = window.location.href;
var isCartPage = href.indexOf('/cart') !== -1 || href.indexOf('#!/~/cart') !== -1;
var isCheckout = href.indexOf('/checkout') !== -1 || href.indexOf('#!/~/checkout') !== -1;
if (!isCartPage && !isCheckout) return;
// Don't duplicate
if (document.getElementById('kvs-shipping-banner')) return;
try {
if (sessionStorage.getItem('kvs_shipping_banner_dismissed')) return;
} catch (e) {}
var style = document.createElement('style');
style.textContent = [
'#kvs-shipping-banner{background:linear-gradient(135deg,rgba(155,45,255,0.12),rgba(0,212,255,0.08));border:1px solid rgba(155,45,255,0.25);border-radius:10px;padding:0;margin:1rem auto;max-width:960px;width:calc(100% - 2rem);box-sizing:border-box;font-family:"Barlow","Arial",sans-serif;font-size:0.88rem;line-height:1.6;color:rgba(200,200,220,0.9);word-wrap:break-word;overflow-wrap:break-word;}',
'#kvs-shipping-banner-inner{padding:1rem 1.5rem;position:relative;padding-right:3rem;}',
'#kvs-shipping-banner a{color:#00d4ff;text-decoration:underline;}',
'#kvs-shipping-banner strong{color:#ffffff;}',
'#kvs-shipping-banner-close{position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:rgba(144,144,176,0.6);font-size:1.5rem;cursor:pointer;padding:0.5rem;line-height:1;}',
'#kvs-shipping-banner-close:hover{color:#ffffff;}',
'@media(max-width:600px){#kvs-shipping-banner{margin:0.75rem auto;font-size:0.8rem;border-radius:8px;}#kvs-shipping-banner-inner{padding:0.75rem 1rem;padding-right:2.5rem;}#kvs-shipping-banner-close{top:0.5rem;right:0.5rem;font-size:1.4rem;}}'
].join('');
document.head.appendChild(style);
var banner = document.createElement('div');
banner.id = 'kvs-shipping-banner';
banner.innerHTML = [
'',
'
Shipping Note: ',
'Nicotine products (e-liquid, disposables, pods) ship to
BC, Saskatchewan, Nova Scotia & Newfoundland only. ',
'Non-nicotine items (hardware, coils, accessories) ship Canada-wide. ',
'
Full details \u2192',
'
',
'
'
].join('');
var checkInterval = setInterval(function() {
var cartArea = document.querySelector('.ec-cart') ||
document.querySelector('.ec-cart__body') ||
document.querySelector('.ec-store__cart-page') ||
document.querySelector('.ec-store');
if (!cartArea) return;
clearInterval(checkInterval);
// v2.0: Insert BEFORE (above) the cart area
cartArea.parentNode.insertBefore(banner, cartArea);
document.getElementById('kvs-shipping-banner-close').addEventListener('click', function() {
banner.style.display = 'none';
try { sessionStorage.setItem('kvs_shipping_banner_dismissed', '1'); } catch (e) {}
});
}, 500);
setTimeout(function() { clearInterval(checkInterval); }, 15000);
}
/* Section 8 removed in v2.7 — default variant fix no longer needed.
Product options now set to "None" default via Ecwid admin/API,
so dropdowns load on "Please choose" and no sold-out badge appears. */
/* ──────────────────────────────────────
9. STICKY ADD-TO-CART BAR — MOBILE
Fixed bottom bar with price + ATC
when native button scrolls out of view.
Only activates on viewports < 768px.
────────────────────────────────────── */
function initStickyATC() {
if (window.innerWidth >= 768) return;
// v2.1: Don't duplicate on SPA navigation
var existing = document.getElementById('kvs-sticky-atc');
if (existing) existing.remove();
var style = document.createElement('style');
style.textContent = [
'#kvs-sticky-atc{position:fixed;bottom:0;left:0;right:0;z-index:9998;background:linear-gradient(160deg,#0e0e1a,#141428);border-top:1px solid rgba(155,45,255,0.35);padding:0.65rem 1rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;font-family:"Barlow","Arial",sans-serif;transform:translateY(100%);transition:transform 0.3s ease;box-shadow:0 -4px 20px rgba(0,0,0,0.5);}',
'#kvs-sticky-atc.kvs-sticky-visible{transform:translateY(0);}',
'#kvs-sticky-atc-info{flex:1;min-width:0;overflow:hidden;}',
'#kvs-sticky-atc-name{font-size:0.78rem;color:rgba(200,200,220,0.85);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:0.15rem;}',
'#kvs-sticky-atc-price{font-size:1.1rem;font-weight:700;color:#ffffff;}',
'#kvs-sticky-atc-btn{flex-shrink:0;background:linear-gradient(135deg,#00d4ff,#9b2dff,#ff2d9b);color:#ffffff;border:none;border-radius:50px;padding:0.7rem 1.5rem;font-family:"Barlow","Arial",sans-serif;font-weight:700;font-size:0.85rem;letter-spacing:0.04em;text-transform:uppercase;cursor:pointer;white-space:nowrap;box-shadow:0 4px 15px rgba(155,45,255,0.4);}',
'#kvs-sticky-atc-btn:active{transform:scale(0.97);}',
'#kvs-sticky-atc-btn.kvs-sticky-disabled{opacity:0.4;pointer-events:none;}'
].join('');
document.head.appendChild(style);
var attempts = 0;
var checkInterval = setInterval(function() {
attempts++;
if (attempts > 40) { clearInterval(checkInterval); return; }
var nativeBtn = document.querySelector('.details-product-purchase__add-to-bag .form-control__button--add-to-bag') ||
document.querySelector('.details-product-purchase__add-to-bag button') ||
document.querySelector('[class*="add-to-bag"] button');
if (!nativeBtn) return;
var nameEl = document.querySelector('.product-details__product-title') ||
document.querySelector('[class*="product-title"]') ||
document.querySelector('h1');
var priceEl = document.querySelector('.details-product-purchase__price .ec-price-item') ||
document.querySelector('.details-product-purchase__price');
if (!nameEl || !priceEl) return;
clearInterval(checkInterval);
var bar = document.createElement('div');
bar.id = 'kvs-sticky-atc';
bar.innerHTML = [
'',
''
].join('');
document.body.appendChild(bar);
var stickyName = document.getElementById('kvs-sticky-atc-name');
var stickyPrice = document.getElementById('kvs-sticky-atc-price');
var stickyBtn = document.getElementById('kvs-sticky-atc-btn');
function updateInfo() {
stickyName.textContent = nameEl.textContent.trim();
var currentPrice = document.querySelector('.details-product-purchase__price .ec-price-item') ||
document.querySelector('.details-product-purchase__price');
if (currentPrice) stickyPrice.textContent = currentPrice.textContent.trim();
var stockEl = document.querySelector('.details-product-purchase__place span');
if (stockEl && /out of stock|sold out/i.test(stockEl.textContent)) {
stickyBtn.classList.add('kvs-sticky-disabled');
stickyBtn.textContent = 'Sold Out';
} else {
stickyBtn.classList.remove('kvs-sticky-disabled');
stickyBtn.textContent = 'Add to Bag';
}
}
updateInfo();
// Watch for variant changes to update price/stock
var observer = new MutationObserver(function() { setTimeout(updateInfo, 300); });
var purchaseArea = nativeBtn.closest('.details-product-purchase') || nativeBtn.parentElement.parentElement;
if (purchaseArea) {
observer.observe(purchaseArea, { childList: true, subtree: true, characterData: true });
}
// Click triggers the real ATC button
stickyBtn.addEventListener('click', function() {
if (stickyBtn.classList.contains('kvs-sticky-disabled')) return;
nativeBtn.click();
});
// Show/hide via IntersectionObserver
var io = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
bar.classList.remove('kvs-sticky-visible');
} else {
bar.classList.add('kvs-sticky-visible');
}
});
}, { threshold: 0 });
io.observe(nativeBtn);
}, 750);
}
/* ──────────────────────────────────────
10. SHIPPING ESTIMATE NOTE — CART
"Shipping calculated at checkout" note
near the subtotal to reduce abandonment.
────────────────────────────────────── */
function initShippingEstimate() {
var href = window.location.href;
if (href.indexOf('/cart') === -1 && href.indexOf('#!/~/cart') === -1) return;
// Don't duplicate
if (document.getElementById('kvs-shipping-note')) return;
var style = document.createElement('style');
style.textContent = [
'#kvs-shipping-note{background:rgba(0,212,255,0.06);border:1px solid rgba(0,212,255,0.15);border-radius:8px;padding:0.6rem 1rem;margin:0.75rem 0;font-family:"Barlow","Arial",sans-serif;font-size:0.82rem;color:rgba(200,200,220,0.85);text-align:center;line-height:1.5;}',
'#kvs-shipping-note strong{color:#00d4ff;}',
'#kvs-shipping-note a{color:#00d4ff;text-decoration:underline;}'
].join('');
document.head.appendChild(style);
var checkInterval = setInterval(function() {
if (document.getElementById('kvs-shipping-note')) { clearInterval(checkInterval); return; }
var summaryArea = document.querySelector('.ec-cart__sidebar') ||
document.querySelector('.ec-cart-summary') ||
document.querySelector('.ec-cart__body-inner') ||
document.querySelector('[class*="cart-summary"]');
if (!summaryArea) {
var subtotalEl = document.querySelector('[class*="cart__total"]');
if (subtotalEl) summaryArea = subtotalEl.parentElement;
}
if (!summaryArea) return;
clearInterval(checkInterval);
var note = document.createElement('div');
note.id = 'kvs-shipping-note';
note.innerHTML = '\uD83D\uDCE6 Shipping calculated at the next step based on your address and order weight. ' +
'See where we ship \u2192';
var checkoutBtn = summaryArea.querySelector('[class*="checkout"]') ||
summaryArea.querySelector('button');
if (checkoutBtn) {
checkoutBtn.parentNode.insertBefore(note, checkoutBtn);
} else {
summaryArea.appendChild(note);
}
}, 800);
setTimeout(function() { clearInterval(checkInterval); }, 15000);
}
/* ──────────────────────────────────────
11. EMPTY SEARCH RECOVERY
When search returns zero results, injects
links to popular categories.
────────────────────────────────────── */
function initSearchRecovery() {
if (window.location.href.indexOf('/search') === -1) return;
// Don't duplicate
if (document.getElementById('kvs-search-recovery')) return;
var style = document.createElement('style');
style.textContent = [
'#kvs-search-recovery{background:linear-gradient(135deg,rgba(155,45,255,0.08),rgba(0,212,255,0.05));border:1px solid rgba(155,45,255,0.2);border-radius:12px;padding:1.5rem;margin:1.5rem auto;max-width:600px;text-align:center;font-family:"Barlow","Arial",sans-serif;}',
'#kvs-search-recovery h3{font-family:"Bebas Neue","Arial Narrow",sans-serif;font-size:1.3rem;letter-spacing:0.04em;color:#ffffff;margin:0 0 0.75rem 0;}',
'#kvs-search-recovery p{font-size:0.85rem;color:rgba(200,200,220,0.8);margin:0 0 1rem 0;line-height:1.5;}',
'.kvs-search-links{display:flex;flex-wrap:wrap;justify-content:center;gap:0.5rem;}',
'.kvs-search-links a{display:inline-block;background:rgba(155,45,255,0.15);border:1px solid rgba(155,45,255,0.3);border-radius:50px;padding:0.45rem 1rem;color:rgba(240,240,255,0.9);font-size:0.82rem;text-decoration:none;transition:background 0.2s,border-color 0.2s;}',
'.kvs-search-links a:hover{background:rgba(155,45,255,0.3);border-color:rgba(155,45,255,0.5);}'
].join('');
document.head.appendChild(style);
var checkInterval = setInterval(function() {
if (document.getElementById('kvs-search-recovery')) { clearInterval(checkInterval); return; }
var noResults = document.querySelector('.ec-search--no-results') ||
document.querySelector('[class*="search--no-results"]') ||
document.querySelector('.grid__no-products-found');
if (!noResults) {
var breadcrumb = document.querySelector('.ec-breadcrumbs');
if (breadcrumb && /no matches/i.test(breadcrumb.textContent)) {
noResults = breadcrumb;
}
}
if (!noResults) return;
clearInterval(checkInterval);
var recovery = document.createElement('div');
recovery.id = 'kvs-search-recovery';
recovery.innerHTML = [
'Try Browsing Our Categories
',
'We might have what you\'re looking for under a different name.
',
''
].join('');
noResults.parentNode.insertBefore(recovery, noResults.nextSibling);
}, 1000);
setTimeout(function() { clearInterval(checkInterval); }, 20000);
}
/* ──────────────────────────────────────
12. ACCESSIBILITY FIXES
Restores focus outlines and improves
contrast. WCAG 2.4.7 + 1.4.3.
────────────────────────────────────── */
function initAccessibilityFixes() {
var style = document.createElement('style');
style.textContent = [
'*:focus-visible{outline:2px solid #00d4ff !important;outline-offset:2px !important;}',
'a:focus-visible,button:focus-visible,select:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid #00d4ff !important;outline-offset:2px !important;}',
'*:focus:not(:focus-visible){outline:none !important;}'
].join('');
document.head.appendChild(style);
}
/* ──────────────────────────────────────
13. ECWID SPA PAGE HOOKS (NEW v2.1)
Ecwid uses SPA-style navigation —
clicking from category → product doesn't
trigger a full page reload. This hook
listens for Ecwid page changes and
re-fires product-page features.
────────────────────────────────────── */
function initEcwidPageHooks() {
if (typeof Ecwid === 'undefined' || !Ecwid.OnPageLoaded) {
// Ecwid not loaded yet — retry up to 10 seconds
if (!initEcwidPageHooks._retries) initEcwidPageHooks._retries = 0;
initEcwidPageHooks._retries++;
if (initEcwidPageHooks._retries < 20) {
setTimeout(initEcwidPageHooks, 500);
}
return;
}
Ecwid.OnPageLoaded.add(function(page) {
if (page.type === 'PRODUCT') {
// Re-fire all product-page features
initTrustBadges();
initStockBadges();
initStickyATC();
}
if (page.type === 'CART') {
initShippingGuard();
initShippingEstimate();
}
if (page.type === 'SEARCH') {
initSearchRecovery();
}
});
}
/* ──────────────────────────────────────
INIT — Run everything
────────────────────────────────────── */
initAgeGate();
initAccessibilityFixes();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
initStockBadges();
initTrustBadges();
initGTMNoscript();
initMailchimp();
initShippingBar();
initShippingGuard();
initStickyATC();
initShippingEstimate();
initSearchRecovery();
initEcwidPageHooks();
});
} else {
initStockBadges();
initTrustBadges();
initGTMNoscript();
initMailchimp();
initShippingBar();
initShippingGuard();
initStickyATC();
initShippingEstimate();
initSearchRecovery();
initEcwidPageHooks();
}
})();