| |
|
|
| |
| const themeManager = { |
| init() { |
| const savedTheme = localStorage.getItem('theme') || 'light'; |
| this.setTheme(savedTheme); |
| |
| |
| document.addEventListener('theme-change', (e) => { |
| this.setTheme(e.detail.theme); |
| }); |
| }, |
| |
| setTheme(theme) { |
| const html = document.documentElement; |
| if (theme === 'dark') { |
| html.classList.add('dark'); |
| } else { |
| html.classList.remove('dark'); |
| } |
| localStorage.setItem('theme', theme); |
| }, |
| |
| toggle() { |
| const isDark = document.documentElement.classList.contains('dark'); |
| this.setTheme(isDark ? 'light' : 'dark'); |
| } |
| }; |
|
|
| |
| const testimonials = [ |
| { |
| name: "Sarah Johnson", |
| role: "Busy Mom", |
| image: "http://static.photos/people/200x200/1", |
| rating: 5, |
| text: "Suds & Bubbles has been a lifesaver! With three kids, I never have time for laundry. Their pickup and delivery service is incredible." |
| }, |
| { |
| name: "Michael Chen", |
| role: "Software Engineer", |
| image: "http://static.photos/people/200x200/2", |
| rating: 5, |
| text: "The app makes it so easy to track my orders. I love getting notifications when my clothes are ready. Highly recommend!" |
| }, |
| { |
| name: "Emily Rodriguez", |
| role: "Restaurant Owner", |
| image: "http://static.photos/people/200x200/3", |
| rating: 5, |
| text: "We use their commercial service for our restaurant linens. Always pristine, always on time. Best laundry service in the city!" |
| } |
| ]; |
|
|
| |
| function renderTestimonials() { |
| const container = document.getElementById('testimonials'); |
| if (!container) return; |
| |
| container.innerHTML = testimonials.map(t => ` |
| <div class="testimonial-card p-6 rounded-2xl bg-gray-50 dark:bg-gray-700 relative"> |
| <div class="flex items-center gap-1 mb-4"> |
| ${Array(t.rating).fill('<i data-feather="star" class="w-4 h-4 text-yellow-400 fill-current"></i>').join('')} |
| </div> |
| <p class="text-gray-600 dark:text-gray-300 mb-6 relative z-10">${t.text}</p> |
| <div class="flex items-center gap-3"> |
| <img src="${t.image}" alt="${t.name}" class="w-12 h-12 rounded-full object-cover"> |
| <div> |
| <div class="font-semibold text-gray-900 dark:text-white">${t.name}</div> |
| <div class="text-sm text-gray-500 dark:text-gray-400">${t.role}</div> |
| </div> |
| </div> |
| </div> |
| `).join(''); |
| |
| |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| } |
|
|
| |
| const orderTracker = { |
| statuses: [ |
| { id: 'received', label: 'Order Received', icon: 'inbox', time: '9:00 AM' }, |
| { id: 'picked', label: 'Picked Up', icon: 'truck', time: '10:30 AM' }, |
| { id: 'processing', label: 'In Progress', icon: 'loader', time: '2:00 PM' }, |
| { id: 'ready', label: 'Ready for Delivery', icon: 'package', time: '5:00 PM' }, |
| { id: 'delivered', label: 'Delivered', icon: 'check-circle', time: '6:30 PM' } |
| ], |
| |
| simulateProgress(orderId) { |
| const container = document.getElementById('tracking-timeline'); |
| if (!container) return; |
| |
| let currentStep = 0; |
| |
| const updateTimeline = () => { |
| const progress = (currentStep / (this.statuses.length - 1)) * 100; |
| |
| container.innerHTML = ` |
| <div class="relative"> |
| <div class="timeline-line"></div> |
| <div class="timeline-line-progress" style="height: ${progress}%"></div> |
| ${this.statuses.map((status, index) => ` |
| <div class="relative flex items-start gap-4 mb-8 ${index <= currentStep ? 'opacity-100' : 'opacity-40'}"> |
| <div class="w-10 h-10 rounded-full flex items-center justify-center ${index <= currentStep ? 'bg-primary-500 text-white' : 'bg-gray-200 dark:bg-gray-600 text-gray-500'} relative z-10"> |
| <i data-feather="${status.icon}" class="w-5 h-5"></i> |
| </div> |
| <div> |
| <div class="font-semibold text-gray-900 dark:text-white">${status.label}</div> |
| <div class="text-sm text-gray-500 dark:text-gray-400">${index <= currentStep ? status.time : 'Pending'}</div> |
| </div> |
| </div> |
| `).join('')} |
| </div> |
| `; |
| |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| |
| if (currentStep < this.statuses.length - 1) { |
| currentStep++; |
| setTimeout(updateTimeline, 2000); |
| } |
| }; |
| |
| updateTimeline(); |
| } |
| }; |
|
|
| |
| const services = { |
| washFold: { |
| name: 'Wash & Fold', |
| basePrice: 1.50, |
| unit: 'lb', |
| options: [ |
| { name: 'Regular', price: 1.50 }, |
| { name: 'Eco-Friendly', price: 2.00 }, |
| { name: 'Hypoallergenic', price: 2.50 } |
| ] |
| }, |
| dryCleaning: { |
| name: 'Dry Cleaning', |
| items: [ |
| { name: 'Shirt/Blouse', price: 5.99 }, |
| { name: 'Pants/Skirt', price: 7.99 }, |
| { name: 'Suit (2pc)', price: 14.99 }, |
| { name: 'Dress', price: 12.99 }, |
| { name: 'Coat/Jacket', price: 15.99 } |
| ] |
| }, |
| homeItems: { |
| name: 'Home Items', |
| items: [ |
| { name: 'Bedding Set', price: 25.99 }, |
| { name: 'Comforter', price: 35.99 }, |
| { name: 'Curtains (per panel)', price: 15.99 }, |
| { name: 'Rug (small)', price: 29.99 }, |
| { name: 'Rug (large)', price: 49.99 } |
| ] |
| } |
| }; |
|
|
| |
| function calculateTotal(items) { |
| return items.reduce((sum, item) => sum + (item.price * item.quantity), 0); |
| } |
|
|
| |
| function formatCurrency(amount) { |
| return new Intl.NumberFormat('en-US', { |
| style: 'currency', |
| currency: 'USD' |
| }).format(amount); |
| } |
|
|
| |
| function showToast(message, type = 'success') { |
| const toast = document.createElement('div'); |
| toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-xl shadow-lg transform translate-y-full transition-transform duration-300 z-50 ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`; |
| toast.textContent = message; |
| document.body.appendChild(toast); |
| |
| setTimeout(() => toast.classList.remove('translate-y-full'), 100); |
| setTimeout(() => { |
| toast.classList.add('translate-y-full'); |
| setTimeout(() => toast.remove(), 300); |
| }, 3000); |
| } |
|
|
| |
| function validateForm(form) { |
| const required = form.querySelectorAll('[required]'); |
| let valid = true; |
| |
| required.forEach(field => { |
| if (!field.value.trim()) { |
| field.classList.add('border-red-500'); |
| valid = false; |
| } else { |
| field.classList.remove('border-red-500'); |
| } |
| }); |
| |
| return valid; |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| themeManager.init(); |
| renderTestimonials(); |
| |
| |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| |
| |
| const trackForm = document.getElementById('track-form'); |
| if (trackForm) { |
| trackForm.addEventListener('submit', (e) => { |
| e.preventDefault(); |
| const orderId = document.getElementById('order-id').value; |
| if (orderId) { |
| orderTracker.simulateProgress(orderId); |
| } |
| }); |
| } |
| |
| |
| const orderForm = document.getElementById('order-form'); |
| if (orderForm) { |
| orderForm.addEventListener('submit', (e) => { |
| e.preventDefault(); |
| if (validateForm(orderForm)) { |
| showToast('Order placed successfully! Order #LB' + Date.now().toString().slice(-6)); |
| orderForm.reset(); |
| } else { |
| showToast('Please fill in all required fields', 'error'); |
| } |
| }); |
| } |
| |
| |
| document.querySelectorAll('.qty-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| const input = e.target.closest('.qty-control').querySelector('input'); |
| const action = e.target.dataset.action; |
| let value = parseInt(input.value) || 0; |
| |
| if (action === 'inc') value++; |
| if (action === 'dec' && value > 0) value--; |
| |
| input.value = value; |
| updateOrderSummary(); |
| }); |
| }); |
| }); |
|
|
| |
| function updateOrderSummary() { |
| const summary = document.getElementById('order-summary'); |
| if (!summary) return; |
| |
| const items = []; |
| document.querySelectorAll('.service-item').forEach(item => { |
| const qty = parseInt(item.querySelector('input').value) || 0; |
| if (qty > 0) { |
| const price = parseFloat(item.dataset.price); |
| const name = item.dataset.name; |
| items.push({ name, price, quantity: qty }); |
| } |
| }); |
| |
| const subtotal = calculateTotal(items); |
| const tax = subtotal * 0.08; |
| const total = subtotal + tax; |
| |
| summary.innerHTML = ` |
| <div class="space-y-2 mb-4"> |
| ${items.map(i => ` |
| <div class="flex justify-between text-sm"> |
| <span class="text-gray-600 dark:text-gray-300">${i.name} x${i.quantity}</span> |
| <span class="font-medium text-gray-900 dark:text-white">${formatCurrency(i.price * i.quantity)}</span> |
| </div> |
| `).join('')} |
| </div> |
| <div class="border-t border-gray-200 dark:border-gray-700 pt-4 space-y-2"> |
| <div class="flex justify-between text-sm"> |
| <span class="text-gray-600 dark:text-gray-300">Subtotal</span> |
| <span class="font-medium text-gray-900 dark:text-white">${formatCurrency(subtotal)}</span> |
| </div> |
| <div class="flex justify-between text-sm"> |
| <span class="text-gray-600 dark:text-gray-300">Tax (8%)</span> |
| <span class="font-medium text-gray-900 dark:text-white">${formatCurrency(tax)}</span> |
| </div> |
| <div class="flex justify-between text-lg font-bold pt-2 border-t border-gray-200 dark:border-gray-700"> |
| <span class="text-gray-900 dark:text-white">Total</span> |
| <span class="text-primary-600 dark:text-primary-400">${formatCurrency(total)}</span> |
| </div> |
| </div> |
| `; |
| } |
|
|
| |
| window.SudsBubbles = { |
| themeManager, |
| orderTracker, |
| services, |
| calculateTotal, |
| formatCurrency, |
| showToast, |
| validateForm |
| }; |