// ===== 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 `

${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.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.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 = `
Thank you, ${orderData.customer.firstName}!
Your order has been received and we'll contact you within 24 hours to:
- Confirm your order details
- Discuss any customization requirements
- Arrange payment and delivery/pickup
- Finalize your cake design
Order Summary:
Event Date: ${orderData.event.date}
Total: $${orderData.totals.total.toFixed(2)}
Delivery: ${orderData.delivery.method === 'delivery' ? 'Home Delivery' : 'Pickup at Bakery'}
What's next? Check your email for a detailed confirmation. We'll be in touch soon!
`;
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');
});
});
}