// ===== GLOBAL VARIABLES ===== let products = []; let cart = JSON.parse(localStorage.getItem('sweetDreamsCart')) || []; let filteredProducts = []; // EmailJS Configuration const emailJSConfig = { serviceID: 'YOUR_SERVICE_ID', // Replace with actual EmailJS service ID templateID: 'YOUR_TEMPLATE_ID', // Replace with actual EmailJS template ID userID: 'YOUR_USER_ID' // Replace with actual EmailJS user ID }; // ===== INITIALIZATION ===== document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); function initializeApp() { // Initialize EmailJS if (typeof emailjs !== 'undefined') { emailjs.init(emailJSConfig.userID); } // Load products loadProducts(); // Initialize event listeners initializeEventListeners(); // Update cart UI updateCartUI(); // Initialize smooth scrolling initializeSmoothScrolling(); // Initialize header scroll effect initializeHeaderEffects(); } // ===== PRODUCT LOADING ===== async function loadProducts() { try { showLoading(); const response = await fetch('cake_inventory.csv'); const csvText = await response.text(); products = parseCSV(csvText); filteredProducts = [...products]; hideLoading(); renderProducts(filteredProducts); } catch (error) { console.error('Error loading products:', error); hideLoading(); showError('Failed to load products. Using demo data.'); // Fallback to demo data if CSV fails to load products = getDemoProducts(); filteredProducts = [...products]; renderProducts(filteredProducts); } } function parseCSV(csvText) { const lines = csvText.trim().split('\n'); const headers = lines[0].split(','); const products = []; for (let i = 1; i < lines.length; i++) { const values = parseCSVLine(lines[i]); const product = {}; headers.forEach((header, index) => { const key = header.trim(); let value = values[index] ? values[index].trim() : ''; // Convert numeric fields if (['id', 'price', 'size_inches', 'serves_people', 'weight_kg', 'stock_quantity'].includes(key)) { value = parseFloat(value) || 0; } // Convert boolean fields if (['featured', 'custom_available'].includes(key)) { value = value.toLowerCase() === 'true'; } product[key] = value; }); products.push(product); } return products; } function parseCSVLine(line) { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { result.push(current); current = ''; } else { current += char; } } result.push(current); return result; } function getDemoProducts() { // Fallback demo data in case CSV loading fails return [ { id: 1, name: "Classic Vanilla Wedding Cake", description: "Elegant three-tier vanilla sponge with buttercream roses and pearl details. Perfect centerpiece for your special day.", price: 299.99, category: "Wedding Cakes", size_inches: 10, serves_people: 50, weight_kg: 3.5, flavor: "Vanilla", image_url: "https://images.unsplash.com/photo-1578985545062-69928b1d9587?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", image_alt: "Three-tier vanilla wedding cake with white buttercream roses", stock_quantity: 2, featured: true, custom_available: true, dietary_options: "gluten-free available, vegan available", baker_name: "Sarah Mitchell", occasion_type: "wedding" }, { id: 2, name: "Chocolate Decadence Birthday Cake", description: "Rich chocolate cake layered with dark chocolate ganache and fresh berries. A chocolate lover's dream.", price: 89.99, category: "Birthday Cakes", size_inches: 8, serves_people: 12, weight_kg: 2.2, flavor: "Dark Chocolate", image_url: "https://images.unsplash.com/photo-1606313564200-e75d5e30476c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80", image_alt: "Dark chocolate birthday cake with ganache and berries", stock_quantity: 5, featured: true, custom_available: true, dietary_options: "gluten-free available", baker_name: "Sarah Mitchell", occasion_type: "birthday" } ]; } // ===== PRODUCT RENDERING ===== function renderProducts(productsToRender) { const grid = document.getElementById('products-grid'); const noResults = document.getElementById('no-results'); if (productsToRender.length === 0) { grid.innerHTML = ''; noResults.style.display = 'block'; return; } noResults.style.display = 'none'; const html = productsToRender.map(product => createProductCard(product)).join(''); grid.innerHTML = html; // Add event listeners to product cards addProductEventListeners(); } function createProductCard(product) { const isOutOfStock = product.stock_quantity === 0; const featuredBadge = product.featured ? '
Featured
' : ''; return `
${product.image_alt} ${featuredBadge}
${product.category}

${product.name}

${product.description}

Serves ${product.serves_people} ${product.size_inches}"
`; } function addProductEventListeners() { // Product card click to show details document.querySelectorAll('.product__card').forEach(card => { card.addEventListener('click', function(e) { if (!e.target.closest('.add-to-cart')) { const productId = parseInt(this.dataset.productId); showProductDetails(productId); } }); }); // Add to cart buttons document.querySelectorAll('.add-to-cart').forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); const productId = parseInt(this.dataset.productId); addToCart(productId); }); }); } // ===== PRODUCT FILTERING & SEARCH ===== function initializeFiltering() { const searchInput = document.getElementById('search-input'); const categoryFilter = document.getElementById('category-filter'); const sortSelect = document.getElementById('sort-select'); searchInput.addEventListener('input', debounce(filterProducts, 300)); categoryFilter.addEventListener('change', filterProducts); sortSelect.addEventListener('change', filterProducts); } function filterProducts() { const searchTerm = document.getElementById('search-input').value.toLowerCase(); const category = document.getElementById('category-filter').value; const sortBy = document.getElementById('sort-select').value; // Filter by search term filteredProducts = products.filter(product => { const searchFields = [ product.name, product.description, product.flavor, product.category, product.occasion_type ].join(' ').toLowerCase(); return searchFields.includes(searchTerm); }); // Filter by category if (category !== 'all') { filteredProducts = filteredProducts.filter(product => product.category === category); } // Sort products filteredProducts.sort((a, b) => { switch (sortBy) { case 'price-low': return a.price - b.price; case 'price-high': return b.price - a.price; case 'name': return a.name.localeCompare(b.name); case 'featured': default: return (b.featured ? 1 : 0) - (a.featured ? 1 : 0); } }); renderProducts(filteredProducts); } // ===== SHOPPING CART ===== function addToCart(productId, quantity = 1) { const product = products.find(p => p.id === productId); if (!product) return; if (product.stock_quantity === 0) { showNotification('This item is currently out of stock', 'error'); return; } const existingItem = cart.find(item => item.id === productId); if (existingItem) { const newQuantity = existingItem.quantity + quantity; if (newQuantity <= product.stock_quantity) { existingItem.quantity = newQuantity; showNotification(`Updated ${product.name} quantity in cart`, 'success'); } else { showNotification(`Only ${product.stock_quantity} items available`, 'warning'); return; } } else { cart.push({ id: product.id, name: product.name, price: product.price, image_url: product.image_url, quantity: quantity, max_quantity: product.stock_quantity }); showNotification(`Added ${product.name} to cart`, 'success'); } saveCart(); updateCartUI(); } function removeFromCart(productId) { cart = cart.filter(item => item.id !== productId); saveCart(); updateCartUI(); renderCartItems(); } function updateCartQuantity(productId, quantity) { const item = cart.find(item => item.id === productId); if (!item) return; if (quantity <= 0) { removeFromCart(productId); return; } if (quantity > item.max_quantity) { showNotification(`Only ${item.max_quantity} items available`, 'warning'); return; } item.quantity = quantity; saveCart(); updateCartUI(); renderCartItems(); } function saveCart() { localStorage.setItem('sweetDreamsCart', JSON.stringify(cart)); } function updateCartUI() { const cartCount = document.getElementById('cart-count'); const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); cartCount.textContent = totalItems; cartCount.style.display = totalItems > 0 ? 'flex' : 'none'; } function getCartTotal() { return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); } // ===== MODAL MANAGEMENT ===== function initializeModals() { // Cart modal const cartBtn = document.getElementById('cart-btn'); const cartModal = document.getElementById('cart-modal'); const cartClose = document.getElementById('cart-modal-close'); cartBtn.addEventListener('click', showCartModal); cartClose.addEventListener('click', () => hideModal('cart-modal')); // Product modal const productModal = document.getElementById('product-modal'); const productClose = document.getElementById('product-modal-close'); productClose.addEventListener('click', () => hideModal('product-modal')); // Checkout modal const checkoutBtn = document.getElementById('checkout-btn'); const checkoutModal = document.getElementById('checkout-modal'); const checkoutClose = document.getElementById('checkout-modal-close'); checkoutBtn.addEventListener('click', showCheckoutModal); checkoutClose.addEventListener('click', () => hideModal('checkout-modal')); // Close modals on backdrop click [cartModal, productModal, checkoutModal].forEach(modal => { modal.addEventListener('click', function(e) { if (e.target === this) { hideModal(this.id); } }); }); // Close modals on Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { const activeModal = document.querySelector('.modal.active'); if (activeModal) { hideModal(activeModal.id); } } }); } function showModal(modalId) { const modal = document.getElementById(modalId); modal.classList.add('active'); document.body.style.overflow = 'hidden'; } function hideModal(modalId) { const modal = document.getElementById(modalId); modal.classList.remove('active'); document.body.style.overflow = ''; } function showCartModal() { renderCartItems(); showModal('cart-modal'); } function renderCartItems() { const cartItems = document.getElementById('cart-items'); const cartEmpty = document.getElementById('cart-empty'); const cartFooter = document.getElementById('cart-footer'); const cartTotal = document.getElementById('cart-total'); if (cart.length === 0) { cartItems.innerHTML = ''; cartEmpty.style.display = 'block'; cartFooter.style.display = 'none'; return; } cartEmpty.style.display = 'none'; cartFooter.style.display = 'block'; const html = cart.map(item => `
${item.name}
${item.name}
$${item.price.toFixed(2)} each
`).join(''); cartItems.innerHTML = html; cartTotal.textContent = `$${getCartTotal().toFixed(2)}`; } function showProductDetails(productId) { const product = products.find(p => p.id === productId); if (!product) return; const modalTitle = document.getElementById('product-modal-title'); const productDetail = document.getElementById('product-detail'); modalTitle.textContent = product.name; const isOutOfStock = product.stock_quantity === 0; productDetail.innerHTML = `
${product.image_alt}
${product.category}

${product.name}

${product.description}

Specifications

  • Size: ${product.size_inches}" diameter
  • Serves: ${product.serves_people} people
  • Weight: ${product.weight_kg} kg
  • Flavor: ${product.flavor}
  • Baker: ${product.baker_name}
${product.dietary_options && product.dietary_options !== 'none available' ? `

Dietary Options

${product.dietary_options}

` : ''} ${product.custom_available ? `
Custom designs available - contact us for personalization
` : ''}
$${product.price.toFixed(2)}
${product.stock_quantity > 0 ? ` ${product.stock_quantity} available` : ' Currently out of stock' }
`; showModal('product-modal'); } // ===== CHECKOUT ===== function showCheckoutModal() { if (cart.length === 0) { showNotification('Your cart is empty', 'warning'); return; } renderOrderSummary(); calculateOrderTotal(); hideModal('cart-modal'); showModal('checkout-modal'); } function renderOrderSummary() { const orderSummary = document.getElementById('order-summary'); const html = cart.map(item => `
${item.name} × ${item.quantity} $${(item.price * item.quantity).toFixed(2)}
`).join(''); orderSummary.innerHTML = html; } function calculateOrderTotal() { const deliverySelect = document.getElementById('checkout-delivery'); const subtotal = getCartTotal(); const deliveryFee = deliverySelect.value === 'delivery' ? 15 : 0; const total = subtotal + deliveryFee; document.getElementById('order-subtotal').textContent = `$${subtotal.toFixed(2)}`; document.getElementById('order-delivery').textContent = `$${deliveryFee.toFixed(2)}`; document.getElementById('order-total').textContent = `$${total.toFixed(2)}`; } // ===== FORM HANDLING ===== function initializeForms() { // Contact form const contactForm = document.getElementById('contact-form'); contactForm.addEventListener('submit', handleContactForm); // Checkout form const checkoutForm = document.getElementById('checkout-form'); checkoutForm.addEventListener('submit', handleCheckoutForm); // Newsletter form const newsletterForm = document.getElementById('newsletter-form'); newsletterForm.addEventListener('submit', handleNewsletterForm); // Delivery method change const deliverySelect = document.getElementById('checkout-delivery'); deliverySelect.addEventListener('change', function() { const deliveryAddress = document.getElementById('delivery-address'); const isDelivery = this.value === 'delivery'; deliveryAddress.style.display = isDelivery ? 'block' : 'none'; // Update required attributes const addressInputs = deliveryAddress.querySelectorAll('input'); addressInputs.forEach(input => { input.required = isDelivery; }); calculateOrderTotal(); }); } async function handleContactForm(e) { e.preventDefault(); const form = e.target; const submitBtn = form.querySelector('button[type="submit"]'); const formData = new FormData(form); // Show loading state submitBtn.classList.add('loading'); try { // Prepare email data const emailData = { from_name: formData.get('name'), from_email: formData.get('email'), phone: formData.get('phone') || 'Not provided', inquiry_type: formData.get('inquiry_type'), message: formData.get('message') }; // Send email via EmailJS if (typeof emailjs !== 'undefined' && emailJSConfig.serviceID !== 'YOUR_SERVICE_ID') { await emailjs.send(emailJSConfig.serviceID, emailJSConfig.templateID, emailData); showNotification('Message sent successfully! We\'ll get back to you soon.', 'success'); } else { // Demo mode - simulate success await new Promise(resolve => setTimeout(resolve, 1000)); showNotification('Demo mode: Message would be sent via EmailJS', 'info'); console.log('Contact form data:', emailData); } form.reset(); } catch (error) { console.error('Error sending message:', error); showNotification('Failed to send message. Please try again.', 'error'); } finally { submitBtn.classList.remove('loading'); } } async function handleCheckoutForm(e) { e.preventDefault(); const form = e.target; const submitBtn = form.querySelector('button[type="submit"]'); const formData = new FormData(form); // Validate form if (!validateCheckoutForm(formData)) { return; } // Show loading state submitBtn.classList.add('loading'); try { // Prepare order data const orderData = { customer: { firstName: formData.get('first_name'), lastName: formData.get('last_name'), email: formData.get('email'), phone: formData.get('phone') }, delivery: { method: formData.get('delivery_method'), address: formData.get('address') || 'Pickup at bakery', city: formData.get('city') || '', zip: formData.get('zip') || '' }, event: { date: formData.get('event_date'), time: formData.get('event_time') || 'Flexible', specialRequests: formData.get('special_requests') || 'None' }, items: cart, totals: { subtotal: getCartTotal(), delivery: formData.get('delivery_method') === 'delivery' ? 15 : 0, total: getCartTotal() + (formData.get('delivery_method') === 'delivery' ? 15 : 0) }, orderDate: new Date().toISOString() }; // Send order via EmailJS if (typeof emailjs !== 'undefined' && emailJSConfig.serviceID !== 'YOUR_SERVICE_ID') { await sendOrderEmails(orderData); showNotification('Order placed successfully! Check your email for confirmation.', 'success'); } else { // Demo mode - simulate success await new Promise(resolve => setTimeout(resolve, 2000)); showNotification('Demo mode: Order would be processed via EmailJS', 'info'); console.log('Order data:', orderData); } // Clear cart and close modal cart = []; saveCart(); updateCartUI(); hideModal('checkout-modal'); // Show success message showOrderConfirmation(orderData); } catch (error) { console.error('Error processing order:', error); showNotification('Failed to process order. Please try again.', 'error'); } finally { submitBtn.classList.remove('loading'); } } async function handleNewsletterForm(e) { e.preventDefault(); const form = e.target; const email = form.querySelector('input[type="email"]').value; try { // In a real application, this would be sent to your email service console.log('Newsletter subscription:', email); showNotification('Successfully subscribed to newsletter!', 'success'); form.reset(); } catch (error) { showNotification('Failed to subscribe. Please try again.', 'error'); } } function validateCheckoutForm(formData) { const requiredFields = ['first_name', 'last_name', 'email', 'phone', 'delivery_method', 'event_date']; for (const field of requiredFields) { if (!formData.get(field)) { showNotification(`Please fill in the ${field.replace('_', ' ')} field`, 'warning'); return false; } } // Validate email const email = formData.get('email'); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { showNotification('Please enter a valid email address', 'warning'); return false; } // Validate event date (should be in the future) const eventDate = new Date(formData.get('event_date')); const today = new Date(); today.setHours(0, 0, 0, 0); if (eventDate < today) { showNotification('Event date must be in the future', 'warning'); return false; } return true; } // ===== EMAIL INTEGRATION ===== async function sendOrderEmails(orderData) { // Prepare email templates const customerEmailData = { to_name: `${orderData.customer.firstName} ${orderData.customer.lastName}`, to_email: orderData.customer.email, order_number: generateOrderNumber(), order_items: formatOrderItems(orderData.items), order_total: orderData.totals.total.toFixed(2), event_date: orderData.event.date, delivery_method: orderData.delivery.method, special_requests: orderData.event.specialRequests }; const businessEmailData = { customer_name: `${orderData.customer.firstName} ${orderData.customer.lastName}`, customer_email: orderData.customer.email, customer_phone: orderData.customer.phone, order_details: JSON.stringify(orderData, null, 2) }; // Send customer confirmation email await emailjs.send(emailJSConfig.serviceID, 'customer_confirmation', customerEmailData); // Send business notification email await emailjs.send(emailJSConfig.serviceID, 'business_notification', businessEmailData); } function generateOrderNumber() { const timestamp = Date.now().toString().slice(-6); const random = Math.random().toString(36).substr(2, 3).toUpperCase(); return `SD${timestamp}${random}`; } function formatOrderItems(items) { return items.map(item => `${item.name} (Quantity: ${item.quantity}) - $${(item.price * item.quantity).toFixed(2)}` ).join('\n'); } // ===== NAVIGATION & UI ===== function initializeNavigation() { const navToggle = document.getElementById('nav-toggle'); const navClose = document.getElementById('nav-close'); const navMenu = document.getElementById('nav-menu'); const navLinks = document.querySelectorAll('.nav__link'); // Mobile menu toggle navToggle.addEventListener('click', () => { navMenu.classList.add('show-menu'); }); navClose.addEventListener('click', () => { navMenu.classList.remove('show-menu'); }); // Close menu on link click navLinks.forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('show-menu'); }); }); // Active link highlighting updateActiveLink(); window.addEventListener('scroll', updateActiveLink); } function updateActiveLink() { const sections = document.querySelectorAll('section[id]'); const navLinks = document.querySelectorAll('.nav__link'); let current = ''; sections.forEach(section => { const sectionTop = section.offsetTop - 100; const sectionHeight = section.offsetHeight; if (window.scrollY >= sectionTop && window.scrollY < sectionTop + sectionHeight) { current = section.getAttribute('id'); } }); navLinks.forEach(link => { link.classList.remove('active-link'); if (link.getAttribute('href') === `#${current}`) { link.classList.add('active-link'); } }); } function initializeSmoothScrolling() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { const headerHeight = document.querySelector('.header').offsetHeight; const targetPosition = target.offsetTop - headerHeight; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); } function initializeHeaderEffects() { const header = document.getElementById('header'); window.addEventListener('scroll', () => { if (window.scrollY > 100) { header.style.background = 'rgba(250, 248, 245, 0.98)'; header.style.boxShadow = '0 2px 20px rgba(61, 53, 48, 0.1)'; } else { header.style.background = 'rgba(250, 248, 245, 0.95)'; header.style.boxShadow = 'none'; } }); } // ===== UTILITY FUNCTIONS ===== function showLoading() { document.getElementById('products-loading').style.display = 'block'; } function hideLoading() { document.getElementById('products-loading').style.display = 'none'; } function showNotification(message, type = 'info') { const notification = document.getElementById('notification'); const icon = notification.querySelector('.notification__icon'); const messageElement = notification.querySelector('.notification__message'); // Set icon based on type const icons = { success: 'fas fa-check-circle', error: 'fas fa-exclamation-circle', warning: 'fas fa-exclamation-triangle', info: 'fas fa-info-circle' }; icon.className = `notification__icon ${icons[type]}`; messageElement.textContent = message; notification.className = `notification ${type} show`; // Auto-hide after 5 seconds setTimeout(() => { notification.classList.remove('show'); }, 5000); } function showError(message) { showNotification(message, 'error'); } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function showOrderConfirmation(orderData) { const confirmationModal = document.createElement('div'); confirmationModal.className = 'modal active'; confirmationModal.innerHTML = ` `; document.body.appendChild(confirmationModal); // Auto-remove after user interaction confirmationModal.addEventListener('click', function(e) { if (e.target === this || e.target.closest('.modal__close')) { this.remove(); } }); } // ===== EVENT LISTENERS INITIALIZATION ===== function initializeEventListeners() { initializeNavigation(); initializeModals(); initializeForms(); initializeFiltering(); // Close notifications document.addEventListener('click', function(e) { if (e.target.closest('.notification')) { e.target.closest('.notification').classList.remove('show'); } }); } // ===== ERROR HANDLING ===== window.addEventListener('error', function(e) { console.error('Application error:', e.error); showNotification('An unexpected error occurred. Please refresh the page.', 'error'); }); // ===== SERVICE WORKER (OPTIONAL) ===== if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js') .then(function(registration) { console.log('ServiceWorker registration successful'); }, function(err) { console.log('ServiceWorker registration failed'); }); }); }