document.addEventListener('DOMContentLoaded', () => { /** * Handles mobile menu toggle functionality. */ function initMobileMenu() { const menuButton = document.getElementById('mobile-menu-button'); const mobileMenu = document.getElementById('mobile-menu'); if (!menuButton || !mobileMenu) return; menuButton.addEventListener('click', () => { const isExpanded = menuButton.getAttribute('aria-expanded') === 'true'; menuButton.setAttribute('aria-expanded', !isExpanded); mobileMenu.classList.toggle('hidden'); }); } /** * Handles interactive plan selection and updates the order summary. */ function initPlanSelection() { const planContainer = document.getElementById('plan-container'); if (!planContainer) return; const planOptions = Array.from(planContainer.querySelectorAll('.plan-option')); const orderSummary = document.getElementById('orderSummary'); const selectedPlanInput = document.getElementById('selectedPlanInput'); // Function to format currency const formatCurrency = (amount) => `$${amount.toLocaleString()}`; // Function to update UI based on selected plan const updateSelection = (selectedOption) => { const planData = JSON.parse(selectedOption.dataset.plan); // Update styles for all plan cards planOptions.forEach(option => { const radio = option.querySelector('.plan-radio'); if (option === selectedOption) { option.classList.add('border-indigo-600', 'shadow-lg'); option.classList.remove('border-gray-200'); radio.classList.add('bg-indigo-600'); } else { option.classList.remove('border-indigo-600', 'shadow-lg'); option.classList.add('border-gray-200'); radio.classList.remove('bg-indigo-600'); } }); // Update hidden form input selectedPlanInput.value = planData.name; // Update order summary document.getElementById('summary-plan').textContent = planData.name; if (planData.custom) { document.getElementById('summary-setup').textContent = 'Custom'; document.getElementById('summary-monthly').textContent = 'Custom'; document.getElementById('summary-total').textContent = 'Quote'; } else { document.getElementById('summary-setup').textContent = formatCurrency(planData.setup); document.getElementById('summary-monthly').textContent = formatCurrency(planData.monthly) + '/month'; document.getElementById('summary-total').textContent = formatCurrency(planData.setup + planData.monthly); } orderSummary.classList.remove('hidden'); }; // Use event delegation on the container planContainer.addEventListener('click', (e) => { const selectedOption = e.target.closest('.plan-option'); if (selectedOption) { updateSelection(selectedOption); } }); // Pre-select the 'Pro' plan on page load const proPlan = planContainer.querySelector('[data-plan*="Pro"]'); if (proPlan) { updateSelection(proPlan); } } /** * Handles form submission with async fetch and UI feedback. */ function initFormSubmission() { const form = document.getElementById('orderForm'); if (!form) return; const submitButton = document.getElementById('submitButton'); const submitText = document.getElementById('submitText'); const loadingSpinner = document.getElementById('loadingSpinner'); const formMessage = document.getElementById('form-message'); form.addEventListener('submit', async (e) => { e.preventDefault(); // Check if a plan is selected if (!document.getElementById('selectedPlanInput').value) { formMessage.textContent = 'Please select a plan before submitting.'; formMessage.className = 'p-4 rounded-lg text-sm bg-red-100 border border-red-200 text-red-800'; formMessage.classList.remove('hidden'); return; } // Set loading state submitButton.disabled = true; submitText.textContent = 'Processing...'; loadingSpinner.classList.remove('hidden'); formMessage.classList.add('hidden'); try { const formData = new FormData(form); const response = await fetch('/api/submit-order', { // FAKE API ENDPOINT method: 'POST', body: formData }); // Simulate network delay for demo await new Promise(res => setTimeout(res, 1500)); // For demonstration, we'll simulate a successful response. // Replace with `if (response.ok)` in production. if (true) { formMessage.textContent = "Thank you! We've received your request and will contact you within 24 hours."; formMessage.className = 'p-4 rounded-lg text-sm bg-green-100 border border-green-200 text-green-800'; form.reset(); // Optionally reset plan selection here } else { // This block would run if `response.ok` was false. throw new Error('Server responded with an error.'); } } catch (error) { console.error('Submission failed:', error); formMessage.textContent = 'Sorry, there was an error. Please try again or contact us directly.'; formMessage.className = 'p-4 rounded-lg text-sm bg-red-100 border border-red-200 text-red-800'; } finally { // Reset button state submitButton.disabled = false; submitText.textContent = 'Submit Project Request'; loadingSpinner.classList.add('hidden'); formMessage.classList.remove('hidden'); } }); } /** * Animate elements into view on scroll using IntersectionObserver. */ function initScrollReveal() { const revealElements = document.querySelectorAll('[data-scroll-reveal]'); if (!revealElements.length) return; const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); // Animate only once } }); }, { threshold: 0.1 }); revealElements.forEach(el => observer.observe(el)); } // Initialize all modules initMobileMenu(); initPlanSelection(); initFormSubmission(); initScrollReveal(); });