const productVariants = window.shopConfig.productVariants; var updateDebounceTimers = {}; function showCartError(msg) { $('#cart-error-banner').text(msg).slideDown(200); setTimeout(function() { $('#cart-error-banner').slideUp(200); }, 4000); } function updateVariantSelection() { const selectedOptions = {}; $('.variant-option').each(function() { selectedOptions[$(this).attr('name')] = $(this).val(); }); const matchedVariant = productVariants.find(edge => { return edge.node.selectedOptions.every(opt => selectedOptions[opt.name] === opt.value); }); if (matchedVariant) { $('#selected-variant-id').val(matchedVariant.node.id); const amt = parseFloat(matchedVariant.node.price.amount); const formattedPrice = (amt % 1 === 0) ? amt : amt.toFixed(2); const isAvailable = matchedVariant.node.availableForSale; if (isAvailable) { $('#price-val').text(formattedPrice); $('#add-to-cart-btn').text('Add to Cart').prop('disabled', false); } else { $('#price-val').text(formattedPrice); $('#add-to-cart-btn').text('Out of Stock').prop('disabled', true); } } } $('.variant-option').on('change', updateVariantSelection); function openCart() { $('#cart-overlay').fadeIn(200); $('#cart-drawer').addClass('open'); $('body').addClass('no-scroll'); } function closeCart() { $('#cart-overlay').fadeOut(200); $('#cart-drawer').removeClass('open'); $('#cart-error-banner').hide(); $('body').removeClass('no-scroll'); } $('#open-cart-btn').on('click', openCart); $('#close-cart, #cart-overlay').on('click', closeCart); $('#add-to-cart-btn').on('click', function() { const variantId = $('#selected-variant-id').val(); const btn = $(this); //btn.text('Adding...').prop('disabled', true); btn.text('Adding...'); $.ajax({ url: '/cart/add', type: 'POST', contentType: 'application/json', data: JSON.stringify({ variantId: variantId, quantity: 1 }), success: function(res) { btn.text('Add to Cart').prop('disabled', false); if (res.success && res.cart) { renderCartDrawer(res.cart); openCart(); } else { showCartError(res.message || 'Item is out of stock.'); } }, error: function() { btn.text('Add to Cart').prop('disabled', false); console.log('An error occurred connecting to the server.'); } }); }); function renderCartDrawer(cart) { if (!cart) return; let rawCheckoutUrl = cart.checkoutUrl || '#'; if (rawCheckoutUrl !== '#') { const analyticsParams = getShopifyAnalyticsParams(); if (analyticsParams) { rawCheckoutUrl += (rawCheckoutUrl.includes('?') ? '&' : '?') + analyticsParams; } } $('#checkout-link').attr('href', cart.checkoutUrl || '#'); const container = $('#cart-items'); container.empty(); let totalItemCount = 0; let computedSubtotal = 0; let currencyCode = ''; if (cart.lines && cart.lines.edges && cart.lines.edges.length > 0) { cart.lines.edges.forEach(edge => { const lineItem = edge.node; const lineId = lineItem.id; const qty = parseInt(lineItem.quantity, 10) || 1; totalItemCount += qty; const merch = lineItem.merchandise; const title = merch.product.title + (merch.title && merch.title !== 'Default Title' ? ' (' + merch.title + ')' : ''); const imgUrl = (merch.product.featuredImage && merch.product.featuredImage.url) ? merch.product.featuredImage.url : '/img/logo.png'; const unitPrice = parseFloat(merch.price.amount); currencyCode = merch.price.currencyCode || currencyCode; computedSubtotal += (unitPrice * qty); const formattedUnitPrice = (unitPrice % 1 === 0) ? unitPrice : unitPrice.toFixed(2); const itemHtml = `
Your cart is currently empty.
'); $('#cart-subtotal').html('$0'); } $('#cart-count-total').text(totalItemCount); $('#nav-cart-count').text(totalItemCount); } function adjustQuantityLocally(lineId, delta) { var itemEl = $('.cart-item[data-line-id="' + lineId + '"]'); if (!itemEl.length) return; var qtySpan = itemEl.find('.item-qty-display'); var currentQty = parseInt(qtySpan.text(), 10) || 1; var targetQty = currentQty + delta; if (targetQty < 1) return; qtySpan.text(targetQty); updateSubtotalInDOM(); if (updateDebounceTimers[lineId]) { clearTimeout(updateDebounceTimers[lineId]); } updateDebounceTimers[lineId] = setTimeout(function() { delete updateDebounceTimers[lineId]; executeQuantitySync(lineId, targetQty, 1); }, 400); } function updateSubtotalInDOM() { var totalQty = 0; var subtotal = 0; $('.cart-item').each(function() { var qty = parseInt($(this).find('.item-qty-display').text(), 10) || 0; var unitPrice = parseFloat($(this).attr('data-price')) || 0; totalQty += qty; subtotal += (qty * unitPrice); }); var formattedSubtotal = (subtotal % 1 === 0) ? subtotal : subtotal.toFixed(2); $('#cart-subtotal').html('$' + formattedSubtotal + ''); $('#cart-count-total').text(totalQty); $('#nav-cart-count').text(totalQty); } function executeQuantitySync(lineId, targetQty, attempt) { $.ajax({ url: '/cart/update', type: 'POST', contentType: 'application/json', data: JSON.stringify({ lineId: String(lineId), quantity: parseInt(targetQty, 10) }), success: function(res) { if (res.success && res.cart) { // Find the updated line item inside Shopify's returned cart object let returnedQty = targetQty; if (res.cart.lines && res.cart.lines.edges) { const lineEdge = res.cart.lines.edges.find(edge => edge.node.id === lineId); if (lineEdge) { returnedQty = parseInt(lineEdge.node.quantity, 10); } } // Render the cart state from Shopify renderCartDrawer(res.cart); // If Shopify capped the quantity lower than requested, notify the user if (returnedQty < targetQty) { showCartError('Maximum available stock reached (' + returnedQty + ' in stock).'); } } else if (res.message && res.message.indexOf('Throttled') !== -1 && attempt <= 3) { setTimeout(function() { executeQuantitySync(lineId, targetQty, attempt + 1); }, 1200); } else { showCartError(res.message || 'Requested quantity is not available.'); $.ajax({ url: '/cart/get', type: 'GET', success: function(refreshedRes) { if (refreshedRes.success && refreshedRes.cart) { renderCartDrawer(refreshedRes.cart); } } }); } } }); } function removeLineItem(lineId) { if (updateDebounceTimers[lineId]) { clearTimeout(updateDebounceTimers[lineId]); delete updateDebounceTimers[lineId]; } $('.cart-item[data-line-id="' + lineId + '"]').fadeOut(200, function() { $(this).remove(); updateSubtotalInDOM(); }); $.ajax({ url: '/cart/remove', type: 'POST', contentType: 'application/json', data: JSON.stringify({ lineId: String(lineId) }), success: function(res) { if (res.success && res.cart) { renderCartDrawer(res.cart); } } }); } function changeMainImage(src) { var $mainImg =$('#main-product-image'); if ($mainImg.attr('src') === src) return; $mainImg.css('opacity', '0'); setTimeout(function() { $mainImg.attr('src', src);$mainImg.css('opacity', '1'); }, 200); } $(document).ready(function() { // Pre-rendered PHP cart data passed directly to client JS const initialCart = window.shopConfig.initialCart; if (initialCart) { renderCartDrawer(initialCart); } else { // Fallback fetch if session was empty or expired $.ajax({ url: '/cart/get', type: 'GET', success: function(res) { if (res.success && res.cart) { renderCartDrawer(res.cart); } } }); } // Animate the product image view transition $('.product-card a').on('click', function() { // Clear transition name from any previously set images $('.product-card img').css('view-transition-name', ''); // Assign transition name dynamically ONLY to the clicked product's image $(this).find('img, + img').css('view-transition-name', 'product_image'); $(this).closest('.product-card').find('img').css('view-transition-name', 'product_image'); }); window.addEventListener('pageswap', async (e) => { if (e.viewTransition) { // Preserves the transition state when leaving PDP via back button document.querySelector('.main-img')?.style.setProperty('view-transition-name', 'product_image'); } }); window.addEventListener('pagereveal', async (e) => { if (e.viewTransition) { // If returning from a product page, match the clicked card if stored const lastClickedUrl = sessionStorage.getItem('last_product_url'); if (lastClickedUrl) { $(`.product-card a[href="\){lastClickedUrl}"] img`).css('view-transition-name', 'product_image'); } } }); $('.product-card a').on('click', function() { sessionStorage.setItem('last_product_url', $(this).attr('href')); }); });