tag in your theme. var BUNDLE_BUILDER_CONFIG = { triggerSelector: '.collection-hero, .collection-list-item, [data-collection-handle]', drawerTitle: 'Build Your Bundle', maxItems: 5, currency: 'USD' }; function createDrawer() { var overlay = document.createElement('div'); overlay.id = 'bundle-builder-overlay'; overlay.style.cssText = [ 'display:none', 'position:fixed', 'inset:0', 'background:rgba(0,0,0,0.5)', 'z-index:9998' ].join(';'); var drawer = document.createElement('div'); drawer.id = 'bundle-builder-drawer'; drawer.setAttribute('role', 'dialog'); drawer.setAttribute('aria-modal', 'true'); drawer.setAttribute('aria-label', BUNDLE_BUILDER_CONFIG.drawerTitle); drawer.style.cssText = [ 'position:fixed', 'top:0', 'right:0', 'bottom:0', 'width:400px', 'max-width:100vw', 'background:#fff', 'z-index:9999', 'overflow-y:auto', 'box-shadow:-4px 0 24px rgba(0,0,0,0.15)', 'transform:translateX(100%)', 'transition:transform 0.3s ease' ].join(';'); var header = document.createElement('div'); header.style.cssText = [ 'display:flex', 'align-items:center', 'justify-content:space-between', 'padding:20px', 'border-bottom:1px solid #e1e3e5' ].join(';'); var title = document.createElement('h2'); title.textContent = BUNDLE_BUILDER_CONFIG.drawerTitle; title.style.cssText = 'margin:0;font-size:18px;font-weight:600;'; var closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.setAttribute('aria-label', 'Close bundle builder'); closeBtn.style.cssText = [ 'background:none', 'border:none', 'font-size:24px', 'cursor:pointer', 'padding:4px 8px', 'line-height:1' ].join(';'); closeBtn.addEventListener('click', closeDrawer); header.appendChild(title); header.appendChild(closeBtn); var body = document.createElement('div'); body.id = 'bundle-builder-body'; body.style.cssText = 'padding:20px;'; var placeholder = document.createElement('p'); placeholder.textContent = 'Select products from this collection to add to your bundle.'; placeholder.style.cssText = 'color:#6d7175;font-size:14px;'; body.appendChild(placeholder); drawer.appendChild(header); drawer.appendChild(body); overlay.appendChild(drawer); document.body.appendChild(overlay); overlay.addEventListener('click', function(e) { if (e.target === overlay) closeDrawer(); }); return { overlay: overlay, drawer: drawer }; } var elements = null; function openDrawer() { if (!elements) elements = createDrawer(); elements.overlay.style.display = 'block'; requestAnimationFrame(function() { elements.drawer.style.transform = 'translateX(0)'; }); document.body.style.overflow = 'hidden'; } function closeDrawer() { if (!elements) return; elements.drawer.style.transform = 'translateX(100%)'; setTimeout(function() { elements.overlay.style.display = 'none'; document.body.style.overflow = ''; }, 300); } function attachTriggers() { var triggers = document.querySelectorAll(BUNDLE_BUILDER_CONFIG.triggerSelector); triggers.forEach(function(el) { if (el.dataset.bundleBuilderAttached) return; el.dataset.bundleBuilderAttached = 'true'; el.addEventListener('click', function(e) { e.preventDefault(); openDrawer(); }); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', attachTriggers); } else { attachTriggers(); } })(); window.floBundle = (function() { const GROUPS = [ { index: 1, label: "Leggings", required: true, products: [ { title: "Leggings - Black", handle: "leggings-black", id: 15458227323255 }, { title: "Leggings - Navy", handle: "leggings-navy-3", id: 15458227421559 }, { title: "Leggings - Pine", handle: "leggings-pine-1", id: 15079904805239 }, { title: "Leggings - Dark Cherry", handle: "leggings-dark-cherry", id: 15139594928503 }, { title: "Leggings - Powder Blue", handle: "leggings-powder-blue", id: 14990887616887 } ]}, { index: 2, label: "Tops & Bras", required: true, products: [ { title: "Racerback Tank - White", handle: "racerback-tank-top-white", id: 15458231615863 }, { title: "Racerback Tank - Black", handle: "racerback-tank-top-black", id: 15458231583095 }, { title: "V-Neck Sports Bra - Navy", handle: "v-neck-sports-bra-navy", id: 15458230075767 }, { title: "V-Neck Sports Bra - White", handle: "v-neck-sports-bra-white", id: 15458230960503 }, { title: "V-Neck Sports Bra - Black", handle: "v-neck-sports-bra-black", id: 15458228371831 }, { title: "V-Neck Sports Bra - Pine", handle: "v-neck-sports-bra-pine", id: 15079903887735 }, { title: "V-Neck Sports Bra - Powder Blue", handle: "v-neck-sports-bra-powder-blue", id: 14990887813495 }, { title: "Racerback Sports Bra - Dark Cherry", handle: "racerback-sports-bra-dark-cherry", id: 15139595157879 }, { title: "Racerback Sports Bra - Pine", handle: "racerback-sports-bra-pine-2", id: 15164108964215 } ]}, { index: 3, label: "Jackets", required: false, products: [ { title: "Full-Zip Jacket - Black", handle: "essential-full-zip-funnel-jacket-black", id: 15458227159415 }, { title: "Full-Zip Jacket - Pine", handle: "untitled-aug7_17-35", id: 15079905132919 }, { title: "Full-Zip Jacket - Dark Cherry", handle: "essential-full-zip-funnel-jacket-dark-cherry", id: 15139594895735 }, { title: "Reversible Track Jacket - Blue/Navy", handle: "reversible-track-jacket", id: 14990886699383 }, { title: "Reversible Track Jacket - Dark Cherry/Grey", handle: "reversible-track-jacket-burgundy-grey", id: 15139595354487 }, { title: "Collared Sweatshirt - White/Green", handle: "collared-sweatshirt-white-green", id: 15068121039223 } ]}, { index: 4, label: "Accessories", required: false, products: [ { title: "Headband - Black", handle: "headband-black", id: 15458231124343 }, { title: "Headband - Navy", handle: "headband-navy", id: 15458231058807 }, { title: "Headband - Pine", handle: "headband-pine", id: 15096387862903 }, { title: "Headband - Dark Cherry", handle: "headband-dark-cherry", id: 15139594830199 }, { title: "Headband - Powder Blue", handle: "headband-powder-blue", id: 14990888305015 }, { title: "Trucker Cap - Cream", handle: "trucker-cap-cream", id: 6975870697537 }, { title: "Trucker Cap - Black", handle: "trucker-cap-black-1", id: 6975870730305 }, { title: "Tote Bag - Natural", handle: "tote-bag-cream-black", id: 6690055159873 }, { title: "Tote Bag - Navy", handle: "tote-bag-navy", id: 6746092896321 } ]}, { index: 5, label: "Socks", required: false, products: [ { title: "Socks - White/Black", handle: "socks-white-black", id: 15159731945847 }, { title: "Socks - White/Navy", handle: "socks-white-navy", id: 15458912698743 }, { title: "Socks - Pack of 3", handle: "socks-pack-of-3", id: 6690297479233 }, { title: "Pilates Socks - Black", handle: "pilates-socks-black", id: 14902319579511 }, { title: "Pilates Socks - White", handle: "pilates-socks-white", id: 14902308995447 }, { title: "Pilates Socks - Cream", handle: "pilates-socks-cream", id: 14902318596471 } ]} ]; let currentStep = 0; let selections = {}; // { groupIndex: { productHandle, variantId, title, price, image } } let productCache = {}; function open() { currentStep = 0; selections = {}; document.getElementById('flo-bundle-drawer').style.display = 'flex'; document.getElementById('flo-bundle-overlay').style.display = 'block'; document.body.style.overflow = 'hidden'; renderStep(); } function close() { document.getElementById('flo-bundle-drawer').style.display = 'none'; document.getElementById('flo-bundle-overlay').style.display = 'none'; document.body.style.overflow = ''; } async function fetchProduct(handle) { if (productCache[handle]) return productCache[handle]; const res = await fetch(`/products/${handle}.js`); const data = await res.json(); productCache[handle] = data; return data; } async function renderStep() { const group = GROUPS[currentStep]; const content = document.getElementById('flo-bundle-content'); content.innerHTML = '
'; // Step label document.getElementById('flo-bundle-step-label').textContent = `Step ${group.index} of ${GROUPS.length}${group.required ? '' : ' (optional)'}`; // Progress dots const progress = document.getElementById('flo-bundle-progress'); progress.innerHTML = GROUPS.map((g, i) => `
`).join(''); // Back/Next buttons document.getElementById('flo-bundle-back').style.display = currentStep > 0 ? 'block' : 'none'; const nextBtn = document.getElementById('flo-bundle-next'); nextBtn.textContent = currentStep === GROUPS.length - 1 ? 'Add Bundle to Cart' : 'Next'; // Fetch all products for this group const products = await Promise.all(group.products.map(p => fetchProduct(p.handle))); // Render product cards const selected = selections[group.index]; content.innerHTML = `
${group.required ? 'Required — pick one' : 'Optional — pick one or skip'}
`; updateSummary(); } function select(groupIndex, handle, variantId, title, price, image) { // Toggle off if already selected if (selections[groupIndex] && selections[groupIndex].variantId === variantId) { delete selections[groupIndex]; } else { selections[groupIndex] = { handle, variantId, title, price, image }; } renderStep(); } function updateSummary() { const summary = document.getElementById('flo-bundle-summary'); const items = Object.values(selections); if (items.length === 0) { summary.textContent = 'No items selected yet'; return; } const total = items.reduce((sum, i) => sum + i.price, 0); summary.innerHTML = `Your bundle: ${items.map(i => i.title).join(', ')} — £${(total / 100).toFixed(2)}`; } function nextStep() { const group = GROUPS[currentStep]; if (group.required && !selections[group.index]) { alert(`Please select a ${group.label} to continue.`); return; } if (currentStep === GROUPS.length - 1) { addToCart(); return; } currentStep++; renderStep(); } function prevStep() { if (currentStep > 0) { currentStep--; renderStep(); } } async function addToCart() { const items = Object.values(selections); if (items.length === 0) { alert('Please select at least one item.'); return; } const nextBtn = document.getElementById('flo-bundle-next'); nextBtn.textContent = 'Adding...'; nextBtn.disabled = true; try { const res = await fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: items.map(item => ({ id: item.variantId, quantity: 1, properties: { _bundle: 'flo-bundle' } })) }) }); if (res.ok) { close(); // Open cart drawer or redirect document.dispatchEvent(new CustomEvent('cart:refresh')); window.location.href = '/cart'; } else { throw new Error('Cart error'); } } catch(e) { alert('Something went wrong adding to cart. Please try again.'); nextBtn.textContent = 'Add Bundle to Cart'; nextBtn.disabled = false; } }