item.id === id);
if (existing) existing.qty += qty;
else cart.push({ id, name, price, image, qty });
localStorage.setItem('cart', JSON.stringify(cart));
updateCartCount();
openCart();
renderCartItems();
}
// Add a 2-pack discount bundle: 2 of the product at unit tier price PLUS
// a sentinel discount line (-savings). Net cart total = bundleTier exactly.
// Each click adds another bundle (qty 2 → 4 → 6 of product; discount qty 1 → 2 → 3).
// Discount lines have id starting '__bundle_disc_' so the StarNet CRM can
// filter them before SKU lookup; they're accounting-only adjustments.
function addBundle2Pack(id, name, image, unitPrice, savings) {
if (!Array.isArray(cart)) cart = [];
// 1) Add 2 of the real product (merges with any existing line for this id)
const existingProduct = cart.find(item => item.id === id);
if (existingProduct) existingProduct.qty += 2;
else cart.push({ id: id, name: name, price: parseFloat(unitPrice), image: image, qty: 2 });
// 2) Add or increment the linked discount line
const discountId = '__bundle_disc_' + id;
const existingDisc = cart.find(item => item.id === discountId);
if (existingDisc) {
existingDisc.qty += 1;
} else {
cart.push({
id: discountId,
name: '2-Pack Bundle Discount',
price: -Math.abs(parseFloat(savings)),
image: image,
qty: 1
});
}
localStorage.setItem('cart', JSON.stringify(cart));
updateCartCount();
openCart();
renderCartItems();
}
function buyNow(id, name, price, image, qty = 1) {
// Go directly to checkout with this item (don't add to cart)
const items = [{ id, name, price: parseFloat(price), image, qty }];
const cartData = btoa(JSON.stringify(items));
window.location.href = '/checkout?cart=' + encodeURIComponent(cartData);
}
function removeFromCart(id) {
if (!Array.isArray(cart)) cart = [];
const isDiscountLine = typeof id === 'string' && id.indexOf('__bundle_disc_') === 0;
cart = cart.filter(item => item.id !== id);
// If a regular product is removed, also remove its linked bundle discount
// (the bundle no longer applies). Discount lines can be removed directly.
if (!isDiscountLine) {
cart = cart.filter(item => item.id !== '__bundle_disc_' + id);
}
localStorage.setItem('cart', JSON.stringify(cart));
updateCartCount();
renderCartItems();
if (document.getElementById('cart-page-items')) renderCartPage();
}
function updateQty(id, qty) {
if (qty < 1) return removeFromCart(id);
if (!Array.isArray(cart)) cart = [];
const isDiscountLine = typeof id === 'string' && id.indexOf('__bundle_disc_') === 0;
const item = cart.find(i => i.id === id);
if (item) {
item.qty = qty;
// If user manually changes qty on a bundled product, remove the discount.
// (The bundle was sized for a specific qty; manual changes break it.)
// Discount lines themselves can be qty-adjusted directly.
if (!isDiscountLine) {
cart = cart.filter(i => i.id !== '__bundle_disc_' + id);
}
localStorage.setItem('cart', JSON.stringify(cart));
updateCartCount();
renderCartItems();
if (document.getElementById('cart-page-items')) renderCartPage();
}
}
function openCart() {
const drawer = document.getElementById('cart-drawer');
const overlay = document.getElementById('cart-overlay');
// If no drawer exists (cartStyle is 'page'), go to cart page instead
if (!drawer) {
window.location.href = '/cart';
return;
}
// Use class AND inline style for mobile compatibility
drawer.classList.add('open');
drawer.style.right = '0';
if (overlay) {
overlay.style.display = 'block';
setTimeout(() => overlay.style.opacity = '1', 10);
}
document.body.style.overflow = 'hidden';
updateCartEmpty();
// Close mobile menu if open
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenu) mobileMenu.style.display = 'none';
}
function closeCart() {
const drawer = document.getElementById('cart-drawer');
const overlay = document.getElementById('cart-overlay');
if (drawer) {
drawer.classList.remove('open');
drawer.style.right = '-100%';
}
if (overlay) {
overlay.style.opacity = '0';
setTimeout(() => overlay.style.display = 'none', 300);
}
document.body.style.overflow = '';
}
// Close cart with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeCart();
});
// Safety: ensure body scroll is restored if cart state gets stuck
setInterval(function() {
const drawer = document.getElementById('cart-drawer');
if (drawer && drawer.style.right !== '0px' && drawer.style.right !== '0') {
if (document.body.style.overflow === 'hidden') {
document.body.style.overflow = '';
}
}
}, 2000);
function toggleMobileMenu() {
const menu = document.getElementById('mobile-menu');
if (menu) {
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
}
function updateCartCount() {
if (!Array.isArray(cart)) cart = [];
const count = cart.reduce((sum, item) => sum + (item.qty || 1), 0);
const countEl = document.getElementById('cart-count');
const countElMobile = document.getElementById('cart-count-mobile');
if (countEl) countEl.textContent = count;
if (countElMobile) countElMobile.textContent = count;
}
function updateCartEmpty() {
const emptyMsg = document.getElementById('cart-empty-msg');
const cartItems = document.getElementById('cart-items');
const cartFooter = document.getElementById('cart-footer');
if (cart.length === 0) {
if (emptyMsg) emptyMsg.style.display = 'block';
if (cartItems) cartItems.style.display = 'none';
if (cartFooter) cartFooter.style.display = 'none';
} else {
if (emptyMsg) emptyMsg.style.display = 'none';
if (cartItems) cartItems.style.display = 'block';
if (cartFooter) cartFooter.style.display = 'block';
}
}
function renderCartItems() {
const container = document.getElementById('cart-items');
if (!container) return;
updateCartEmpty();
if (cart.length === 0) {
container.innerHTML = '';
const totalEl = document.getElementById('cart-total');
if (totalEl) totalEl.textContent = '$0.00';
return;
}
container.innerHTML = cart.map(item => {
const isDisc = typeof item.id === 'string' && item.id.indexOf('__bundle_disc_') === 0;
if (isDisc) {
// Discount line: green, italic, no qty controls, total shown as savings
const discAmt = Math.abs(parseFloat(item.price)) * (item.qty || 1);
return '
' +
'
−
' +
'
' +
'
' + item.name + (item.qty > 1 ? ' × ' + item.qty : '') + '
' +
'
Auto-applied with 2-pack purchase
' +
'
Remove ' +
'
' +
'
−$' + discAmt.toFixed(2) + '
' +
'
';
}
return '
' +
'
' +
'
' +
'
' + item.name + '
' +
'
$' + parseFloat(item.price).toFixed(2) + '
' +
'
' +
'- ' +
'' + item.qty + ' ' +
'+ ' +
'Remove ' +
'
' +
'
' +
'
';
}).join('');
const total = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
const totalEl = document.getElementById('cart-total');
if (totalEl) totalEl.textContent = '$' + total.toFixed(2);
}
function renderCartPage() {
const container = document.getElementById('cart-page-items');
const emptyEl = document.getElementById('cart-empty');
const summaryEl = document.getElementById('cart-summary');
if (!container) return;
if (cart.length === 0) {
container.innerHTML = '';
if (emptyEl) emptyEl.style.display = 'block';
if (summaryEl) summaryEl.style.display = 'none';
return;
}
if (emptyEl) emptyEl.style.display = 'none';
if (summaryEl) summaryEl.style.display = 'block';
container.innerHTML = cart.map(item => {
const isDisc = typeof item.id === 'string' && item.id.indexOf('__bundle_disc_') === 0;
if (isDisc) {
const discAmt = Math.abs(parseFloat(item.price)) * (item.qty || 1);
return '
' +
'
−
' +
'
' +
'
' + item.name + (item.qty > 1 ? ' × ' + item.qty : '') + ' ' +
'
Auto-applied with 2-pack purchase
' +
'
' +
'
' +
'
−$' + discAmt.toFixed(2) + '
' +
'
Remove ' +
'
' +
'
';
}
return '
' +
'
' +
'
' +
'
' + item.name + ' ' +
'
$' + parseFloat(item.price).toFixed(2) + '
' +
'
' +
'- ' +
'' + item.qty + ' ' +
'+ ' +
'
' +
'
' +
'
' +
'
$' + (item.price * item.qty).toFixed(2) + '
' +
'
Remove ' +
'
' +
'
';
}).join('');
const total = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
const totalEl = document.getElementById('cart-page-total');
if (totalEl) totalEl.textContent = '$' + total.toFixed(2);
}
// Proceed to checkout with Shopify-style URL
function proceedToCheckout() {
if (cart.length === 0) {
alert('Your cart is empty');
return;
}
// Generate a random token for the checkout URL
const token = Array.from({length: 22}, () => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'[Math.floor(Math.random() * 62)]).join('');
// Encode cart data in URL param
const cartData = btoa(JSON.stringify(cart));
// Redirect to Shopify-style checkout URL
window.location.href = '/checkouts/cn/' + token + '?cart=' + encodeURIComponent(cartData);
}
updateCartCount();
renderCartItems();
// If on cart page, render it
if (document.getElementById('cart-page-items')) renderCartPage();
// ---- Bundle selector handled by CONFIG.bundleSettings ----