| |
| |
| |
|
|
| |
| const DEFAULT_PRICING = { |
| products: { |
| 'business-cards': { |
| name: 'Business Cards', |
| icon: 'credit-card', |
| image: 'http://static.photos/office/320x240/42', |
| basePrice: 19.00, |
| sizes: [ |
| { id: '3.5x2', name: '3.5" x 2"', price: 0 }, |
| { id: '3.5x2-rounded', name: '3.5" x 2" Rounded', price: 4 }, |
| { id: '3.5x2-square', name: '3.5" x 3.5" Square', price: 6 } |
| ], |
| paperTypes: [ |
| { id: '14pt-gloss', name: '14pt Gloss', price: 0 }, |
| { id: '16pt-matte', name: '16pt Matte', price: 6 }, |
| { id: '14pt-uncoated', name: '14pt Uncoated', price: 3 }, |
| { id: '16pt-silk', name: '16pt Silk', price: 8 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'matte', name: 'Matte Coating', price: 5 }, |
| { id: 'gloss', name: 'Gloss Coating', price: 5 }, |
| { id: 'uv', name: 'UV Spot Gloss', price: 12 }, |
| { id: 'foil', name: 'Foil Stamp', price: 18 } |
| ], |
| quantityTiers: [ |
| { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, |
| { min: 251, max: 500, multiplier: 0.75, label: '251-500' }, |
| { min: 501, max: 1000, multiplier: 0.55, label: '501-1K' }, |
| { min: 1001, max: 2500, multiplier: 0.40, label: '1K-2.5K' }, |
| { min: 2501, max: 5000, multiplier: 0.30, label: '2.5K-5K' }, |
| { min: 5001, max: 10000, multiplier: 0.22, label: '5K-10K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 15 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 30 } |
| ], |
| defaultQuantity: 500 |
| }, |
| 'flyers': { |
| name: 'Flyers', |
| icon: 'file-text', |
| image: 'http://static.photos/office/320x240/55', |
| basePrice: 35.00, |
| sizes: [ |
| { id: '8.5x11', name: '8.5" x 11"', price: 0 }, |
| { id: '5.5x8.5', name: '5.5" x 8.5"', price: -3 }, |
| { id: '11x17', name: '11" x 17"', price: 8 } |
| ], |
| paperTypes: [ |
| { id: '100lb-gloss', name: '100lb Gloss', price: 0 }, |
| { id: '80lb-matte', name: '80lb Matte', price: -2 }, |
| { id: '100lb-uncoated', name: '100lb Uncoated', price: 2 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'matte', name: 'Matte Coating', price: 8 }, |
| { id: 'gloss', name: 'Gloss Coating', price: 8 } |
| ], |
| quantityTiers: [ |
| { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, |
| { min: 251, max: 500, multiplier: 0.70, label: '251-500' }, |
| { min: 501, max: 1000, multiplier: 0.50, label: '501-1K' }, |
| { min: 1001, max: 5000, multiplier: 0.35, label: '1K-5K' }, |
| { min: 5001, max: 10000, multiplier: 0.25, label: '5K-10K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 20 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 40 } |
| ], |
| defaultQuantity: 250 |
| }, |
| 'brochures': { |
| name: 'Brochures', |
| icon: 'book-open', |
| image: 'http://static.photos/office/320x240/67', |
| basePrice: 55.00, |
| sizes: [ |
| { id: '8.5x11-bi', name: '8.5"x11" Bi-Fold', price: 0 }, |
| { id: '8.5x11-tri', name: '8.5"x11" Tri-Fold', price: 3 }, |
| { id: '11x17-bi', name: '11"x17" Bi-Fold', price: 10 } |
| ], |
| paperTypes: [ |
| { id: '100lb-gloss', name: '100lb Gloss', price: 0 }, |
| { id: '80lb-matte', name: '80lb Matte', price: -3 }, |
| { id: '100lb-silk', name: '100lb Silk', price: 5 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'matte', name: 'Matte Coating', price: 10 }, |
| { id: 'gloss', name: 'Gloss Coating', price: 10 } |
| ], |
| quantityTiers: [ |
| { min: 25, max: 100, multiplier: 1.0, label: '25-100' }, |
| { min: 101, max: 250, multiplier: 0.70, label: '101-250' }, |
| { min: 251, max: 500, multiplier: 0.52, label: '251-500' }, |
| { min: 501, max: 1000, multiplier: 0.40, label: '501-1K' }, |
| { min: 1001, max: 5000, multiplier: 0.30, label: '1K-5K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 25 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 50 } |
| ], |
| defaultQuantity: 100 |
| }, |
| 'banners': { |
| name: 'Banners', |
| icon: 'flag', |
| image: 'http://static.photos/office/320x240/78', |
| basePrice: 45.00, |
| sizes: [ |
| { id: '24x36', name: '24" x 36"', price: 0 }, |
| { id: '36x48', name: '36" x 48"', price: 15 }, |
| { id: '48x72', name: '48" x 72"', price: 35 } |
| ], |
| paperTypes: [ |
| { id: 'vinyl', name: '13oz Vinyl', price: 0 }, |
| { id: 'mesh', name: 'Mesh Vinyl', price: 8 }, |
| { id: 'fabric', name: 'Fabric', price: 20 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'grommets', name: 'Grommets', price: 5 }, |
| { id: 'pole-pockets', name: 'Pole Pockets', price: 10 } |
| ], |
| quantityTiers: [ |
| { min: 1, max: 5, multiplier: 1.0, label: '1-5' }, |
| { min: 6, max: 10, multiplier: 0.80, label: '6-10' }, |
| { min: 11, max: 25, multiplier: 0.65, label: '11-25' }, |
| { min: 26, max: 50, multiplier: 0.50, label: '26-50' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 20 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 45 } |
| ], |
| defaultQuantity: 1 |
| }, |
| 'stickers': { |
| name: 'Stickers & Labels', |
| icon: 'sticky-note', |
| image: 'http://static.photos/office/320x240/91', |
| basePrice: 25.00, |
| sizes: [ |
| { id: '2x2', name: '2" x 2"', price: 0 }, |
| { id: '3x3', name: '3" x 3"', price: 5 }, |
| { id: '4x4', name: '4" x 4"', price: 8 }, |
| { id: 'custom-die', name: 'Custom Die-Cut', price: 15 } |
| ], |
| paperTypes: [ |
| { id: 'white-vinyl', name: 'White Vinyl', price: 0 }, |
| { id: 'clear-vinyl', name: 'Clear Vinyl', price: 5 }, |
| { id: 'metallic', name: 'Metallic', price: 10 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'matte-lam', name: 'Matte Laminate', price: 6 }, |
| { id: 'gloss-lam', name: 'Gloss Laminate', price: 6 } |
| ], |
| quantityTiers: [ |
| { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, |
| { min: 251, max: 500, multiplier: 0.70, label: '251-500' }, |
| { min: 501, max: 1000, multiplier: 0.48, label: '501-1K' }, |
| { min: 1001, max: 2500, multiplier: 0.35, label: '1K-2.5K' }, |
| { min: 2501, max: 5000, multiplier: 0.25, label: '2.5K-5K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 12 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 28 } |
| ], |
| defaultQuantity: 250 |
| }, |
| 'postcards': { |
| name: 'Postcards', |
| icon: 'mail', |
| image: 'http://static.photos/office/320x240/33', |
| basePrice: 28.00, |
| sizes: [ |
| { id: '4x6', name: '4" x 6"', price: 0 }, |
| { id: '5x7', name: '5" x 7"', price: 4 }, |
| { id: '6x9', name: '6" x 9"', price: 7 }, |
| { id: '6x11', name: '6" x 11"', price: 10 } |
| ], |
| paperTypes: [ |
| { id: '14pt-gloss', name: '14pt Gloss', price: 0 }, |
| { id: '16pt-matte', name: '16pt Matte', price: 5 }, |
| { id: '14pt-uncoated', name: '14pt Uncoated', price: 2 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'matte', name: 'Matte Coating', price: 5 }, |
| { id: 'gloss', name: 'Gloss Coating', price: 5 }, |
| { id: 'uv-front', name: 'UV Front Only', price: 8 } |
| ], |
| quantityTiers: [ |
| { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, |
| { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' }, |
| { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' }, |
| { min: 2501, max: 5000, multiplier: 0.38, label: '2.5K-5K' }, |
| { min: 5001, max: 10000, multiplier: 0.28, label: '5K-10K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 12 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 28 } |
| ], |
| defaultQuantity: 500 |
| }, |
| 'letterhead': { |
| name: 'Letterhead', |
| icon: 'file', |
| image: 'http://static.photos/office/320x240/44', |
| basePrice: 40.00, |
| sizes: [ |
| { id: '8.5x11', name: '8.5" x 11"', price: 0 }, |
| { id: '8.5x14', name: '8.5" x 14"', price: 5 } |
| ], |
| paperTypes: [ |
| { id: '24lb-bond', name: '24lb Bond', price: 0 }, |
| { id: '28lb-bond', name: '28lb Bond', price: 5 }, |
| { id: '32lb-bond', name: '32lb Bond', price: 10 }, |
| { id: 'linen', name: 'Linen Finish', price: 15 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'watermark', name: 'Watermark', price: 8 } |
| ], |
| quantityTiers: [ |
| { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, |
| { min: 501, max: 1000, multiplier: 0.70, label: '501-1K' }, |
| { min: 1001, max: 2500, multiplier: 0.48, label: '1K-2.5K' }, |
| { min: 2501, max: 5000, multiplier: 0.35, label: '2.5K-5K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 18 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 38 } |
| ], |
| defaultQuantity: 500 |
| }, |
| 'envelopes': { |
| name: 'Envelopes', |
| icon: 'mail-open', |
| image: 'http://static.photos/office/320x240/56', |
| basePrice: 32.00, |
| sizes: [ |
| { id: '10-standard', name: '#10 Standard', price: 0 }, |
| { id: 'a7', name: 'A7 (5.25x7.25)', price: 3 }, |
| { id: 'a2', name: 'A2 (4.375x5.75)', price: 0 } |
| ], |
| paperTypes: [ |
| { id: '24lb-white', name: '24lb White Wove', price: 0 }, |
| { id: '28lb-white', name: '28lb White Wove', price: 4 }, |
| { id: 'kraft', name: 'Kraft Brown', price: 3 } |
| ], |
| finishing: [ |
| { id: 'none', name: 'None', price: 0 }, |
| { id: 'window', name: 'Window', price: 5 }, |
| { id: 'peel-seal', name: 'Peel & Seal', price: 4 } |
| ], |
| quantityTiers: [ |
| { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, |
| { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' }, |
| { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' }, |
| { min: 2501, max: 5000, multiplier: 0.36, label: '2.5K-5K' } |
| ], |
| turnaround: [ |
| { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, |
| { id: 'rush', name: 'Rush (3-4 days)', price: 15 }, |
| { id: 'express', name: 'Express (1-2 days)', price: 32 } |
| ], |
| defaultQuantity: 250 |
| } |
| } |
| }; |
|
|
| |
| function getPricing() { |
| const stored = localStorage.getItem('printpro_pricing'); |
| if (stored) { |
| try { |
| return JSON.parse(stored); |
| } catch (e) { |
| return DEFAULT_PRICING; |
| } |
| } |
| |
| localStorage.setItem('printpro_pricing', JSON.stringify(DEFAULT_PRICING)); |
| return DEFAULT_PRICING; |
| } |
|
|
| |
| let pricing = getPricing(); |
| let selectedProduct = 'business-cards'; |
| let selectedSize = null; |
| let selectedPaper = null; |
| let selectedFinishing = null; |
| let selectedTurnaround = null; |
| let currentQuantity = 500; |
|
|
| |
| const productGrid = document.getElementById('productGrid'); |
| const productSelector = document.getElementById('productSelector'); |
| const sizeOptions = document.getElementById('sizeOptions'); |
| const paperOptions = document.getElementById('paperOptions'); |
| const finishingOptions = document.getElementById('finishingOptions'); |
| const turnaroundOptions = document.getElementById('turnaroundOptions'); |
| const quantitySlider = document.getElementById('quantitySlider'); |
| const quantityDisplay = document.getElementById('quantityDisplay'); |
| const qtyMin = document.getElementById('qtyMin'); |
| const qtyMax = document.getElementById('qtyMax'); |
| const totalPrice = document.getElementById('totalPrice'); |
| const pricePerUnit = document.getElementById('pricePerUnit'); |
| const heroPrice = document.getElementById('heroPrice'); |
| const toast = document.getElementById('toast'); |
| const toastMsg = document.getElementById('toastMsg'); |
| const mobileMenuBtn = document.getElementById('mobileMenuBtn'); |
| const mobileMenu = document.getElementById('mobileMenu'); |
|
|
| |
| mobileMenuBtn.addEventListener('click', () => { |
| mobileMenu.classList.toggle('hidden'); |
| }); |
|
|
| |
| mobileMenu.querySelectorAll('a').forEach(link => { |
| link.addEventListener('click', () => { |
| mobileMenu.classList.add('hidden'); |
| }); |
| }); |
|
|
| |
| function renderProductGrid() { |
| const products = pricing.products; |
| productGrid.innerHTML = Object.entries(products).map(([key, product]) => ` |
| <div class="product-card bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden cursor-pointer group relative" |
| onclick="selectProductAndScroll('${key}')"> |
| <div class="relative overflow-hidden h-48"> |
| <img src="${product.image}" alt="${product.name}" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"> |
| <div class="product-overlay absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end p-4"> |
| <span class="text-white font-bold text-sm">Get Quote →</span> |
| </div> |
| </div> |
| <div class="p-4"> |
| <div class="flex items-center gap-2 mb-1"> |
| <i data-lucide="${product.icon}" class="w-4 h-4 text-brand-500"></i> |
| <h3 class="font-semibold text-gray-900 text-sm">${product.name}</h3> |
| </div> |
| <p class="text-brand-600 font-bold">From $${product.basePrice.toFixed(2)}</p> |
| </div> |
| </div> |
| `).join(''); |
| lucide.createIcons(); |
| } |
|
|
| |
| function renderProductSelector() { |
| const products = pricing.products; |
| productSelector.innerHTML = Object.entries(products).map(([key, product]) => ` |
| <button class="option-btn px-4 py-2.5 rounded-xl text-sm font-semibold transition-all border-2 flex items-center gap-2 |
| ${selectedProduct === key |
| ? 'bg-brand-600 text-white border-brand-600 shadow-md active' |
| : 'bg-white text-gray-600 border-gray-200 hover:border-brand-300 hover:text-brand-600'}" |
| onclick="selectProduct('${key}')"> |
| <i data-lucide="${product.icon}" class="w-4 h-4"></i> |
| ${product.name} |
| </button> |
| `).join(''); |
| lucide.createIcons(); |
| } |
|
|
| |
| function renderOptionButtons(containerId, options, selectedValue, onChange) { |
| const container = document.getElementById(containerId); |
| if (!container) return; |
| container.innerHTML = options.map(opt => ` |
| <button class="option-btn px-3.5 py-2 rounded-lg text-sm font-medium transition-all border-2 |
| ${selectedValue === opt.id |
| ? 'bg-brand-50 text-brand-700 border-brand-500 active shadow-sm' |
| : 'bg-white text-gray-600 border-gray-200 hover:border-gray-300'}" |
| onclick="${onChange}('${opt.id}')"> |
| ${opt.name} |
| ${opt.price > 0 ? `<span class="text-xs text-gray-400 ml-1">+$${opt.price.toFixed(2)}</span>` : ''} |
| ${opt.price < 0 ? `<span class="text-xs text-green-500 ml-1">-$${Math.abs(opt.price).toFixed(2)}</span>` : ''} |
| ${opt.price === 0 && opt.id !== 'none' && opt.id !== 'standard' ? `<span class="text-xs text-gray-300 ml-1">Included</span>` : ''} |
| </button> |
| `).join(''); |
| } |
|
|
| |
| function selectProduct(productKey) { |
| selectedProduct = productKey; |
| const product = pricing.products[productKey]; |
| selectedSize = product.sizes[0].id; |
| selectedPaper = product.paperTypes[0].id; |
| selectedFinishing = product.finishing[0].id; |
| selectedTurnaround = product.turnaround[0].id; |
| currentQuantity = product.defaultQuantity; |
|
|
| renderAllOptions(); |
| updateSlider(); |
| calculatePrice(); |
| renderProductSelector(); |
| } |
|
|
| function selectProductAndScroll(productKey) { |
| selectProduct(productKey); |
| document.getElementById('calculator').scrollIntoView({ behavior: 'smooth' }); |
| } |
|
|
| |
| function updateSlider() { |
| const product = pricing.products[selectedProduct]; |
| const tiers = product.quantityTiers; |
| const minQty = tiers[0].min; |
| const maxQty = tiers[tiers.length - 1].max; |
|
|
| qtyMin.textContent = minQty; |
| qtyMax.textContent = maxQty; |
|
|
| |
| const sliderPos = ((currentQuantity - minQty) / (maxQty - minQty)) * 100; |
| quantitySlider.value = Math.round(sliderPos); |
| quantityDisplay.textContent = currentQuantity; |
| } |
|
|
| |
| function getQuantityFromSlider(sliderValue) { |
| const product = pricing.products[selectedProduct]; |
| const tiers = product.quantityTiers; |
| const minQty = tiers[0].min; |
| const maxQty = tiers[tiers.length - 1].max; |
| return Math.round(minQty + (sliderValue / 100) * (maxQty - minQty)); |
| } |
|
|
| |
| function getTierMultiplier(quantity) { |
| const product = pricing.products[selectedProduct]; |
| const tiers = product.quantityTiers; |
| for (const tier of tiers) { |
| if (quantity >= tier.min && quantity <= tier.max) { |
| return tier.multiplier; |
| } |
| } |
| |
| return tiers[tiers.length - 1].multiplier; |
| } |
|
|
| |
| function calculatePrice() { |
| const product = pricing.products[selectedProduct]; |
|
|
| const sizeObj = product.sizes.find(s => s.id === selectedSize) || product.sizes[0]; |
| const paperObj = product.paperTypes.find(p => p.id === selectedPaper) || product.paperTypes[0]; |
| const finishObj = product.finishing.find(f => f.id === selectedFinishing) || product.finishing[0]; |
| const turnaroundObj = product.turnaround.find(t => t.id === selectedTurnaround) || product.turnaround[0]; |
| const multiplier = getTierMultiplier(currentQuantity); |
|
|
| const baseWithAddons = product.basePrice + sizeObj.price + paperObj.price + finishObj.price; |
| const subtotal = baseWithAddons * multiplier * (currentQuantity / 100); |
| const total = subtotal + turnaroundObj.price; |
|
|
| const formattedTotal = total.toFixed(2); |
| const perUnit = total / currentQuantity; |
|
|
| |
| totalPrice.classList.remove('price-pulse'); |
| void totalPrice.offsetWidth; |
| totalPrice.classList.add('price-pulse'); |
| totalPrice.textContent = `$${formattedTotal}`; |
| pricePerUnit.textContent = `$${perUnit.toFixed(3)} / unit`; |
|
|
| |
| if (selectedProduct === 'business-cards' && currentQuantity === 500 && |
| selectedSize === '3.5x2' && selectedPaper === '14pt-gloss' && |
| selectedFinishing === 'none' && selectedTurnaround === 'standard') { |
| heroPrice.textContent = formattedTotal; |
| } |
| } |
|
|
| |
| function renderAllOptions() { |
| const product = pricing.products[selectedProduct]; |
|
|
| renderOptionButtons('sizeOptions', product.sizes, selectedSize, |
| (val) => { selectedSize = val; renderAllOptions(); calculatePrice(); }); |
|
|
| renderOptionButtons('paperOptions', product.paperTypes, selectedPaper, |
| (val) => { selectedPaper = val; renderAllOptions(); calculatePrice(); }); |
|
|
| renderOptionButtons('finishingOptions', product.finishing, selectedFinishing, |
| (val) => { selectedFinishing = val; renderAllOptions(); calculatePrice(); }); |
|
|
| renderOptionButtons('turnaroundOptions', product.turnaround, selectedTurnaround, |
| (val) => { selectedTurnaround = val; renderAllOptions(); calculatePrice(); }); |
|
|
| lucide.createIcons(); |
| } |
|
|
| |
| quantitySlider.addEventListener('input', () => { |
| currentQuantity = getQuantityFromSlider(parseInt(quantitySlider.value)); |
| quantityDisplay.textContent = currentQuantity; |
| calculatePrice(); |
| }); |
|
|
| |
| function showToast(message) { |
| toastMsg.textContent = message; |
| toast.classList.remove('hidden'); |
| void toast.offsetWidth; |
| toast.classList.add('toast'); |
| setTimeout(() => { |
| toast.classList.add('hidden'); |
| toast.classList.remove('toast'); |
| }, 3000); |
| } |
|
|
| |
| window.addEventListener('storage', (e) => { |
| if (e.key === 'printpro_pricing') { |
| pricing = getPricing(); |
| selectProduct(selectedProduct); |
| renderProductGrid(); |
| renderProductSelector(); |
| calculatePrice(); |
| showToast('Pricing updated from admin panel! 🔄'); |
| } |
| }); |
|
|
| |
| window.addEventListener('pricingUpdated', () => { |
| pricing = getPricing(); |
| selectProduct(selectedProduct); |
| renderProductGrid(); |
| renderProductSelector(); |
| calculatePrice(); |
| }); |
|
|
| |
| function init() { |
| renderProductGrid(); |
| renderProductSelector(); |
| selectProduct('business-cards'); |
| updateSlider(); |
| calculatePrice(); |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', init); |
|
|
| |
| window.selectProduct = selectProduct; |
| window.selectProductAndScroll = selectProductAndScroll; |
| window.showToast = showToast; |