/** * NEXT GEN LANDSCAPING & YARD CARE * Core Interactive Logic & Calculator Engine */ document.addEventListener('DOMContentLoaded', () => { // 1. Current Year in Footer const yearSpan = document.getElementById('current-year'); if (yearSpan) { yearSpan.textContent = new Date().getFullYear(); } // 2. Mobile Navigation Toggle const mobileToggle = document.getElementById('mobile-toggle'); const mobileDrawer = document.getElementById('mobile-drawer'); const mobileLinks = document.querySelectorAll('.mobile-link'); if (mobileToggle && mobileDrawer) { mobileToggle.addEventListener('click', () => { mobileDrawer.classList.toggle('open'); }); mobileLinks.forEach(link => { link.addEventListener('click', () => { mobileDrawer.classList.remove('open'); }); }); } // 3. Native Modal Management const quoteDialog = document.getElementById('quote-dialog'); const modalCloseBtn = document.getElementById('modal-close'); const openQuoteButtons = document.querySelectorAll('.open-quote-btn'); const quoteForm = document.getElementById('quote-form'); const serviceSelect = document.getElementById('lead-service'); const notesTextarea = document.getElementById('lead-notes'); const toast = document.getElementById('toast'); // Open Modal function const openModal = (initialScope = null, initialNotes = null) => { if (!quoteDialog) return; if (initialScope && serviceSelect) { for (let i = 0; i < serviceSelect.options.length; i++) { if (serviceSelect.options[i].text.toLowerCase().includes(initialScope.toLowerCase()) || serviceSelect.options[i].value.toLowerCase().includes(initialScope.toLowerCase())) { serviceSelect.selectedIndex = i; break; } } } if (initialNotes && notesTextarea) { notesTextarea.value = initialNotes; } quoteDialog.showModal(); }; openQuoteButtons.forEach(btn => { btn.addEventListener('click', (e) => { const scope = btn.getAttribute('data-scope'); openModal(scope); }); }); if (modalCloseBtn) { modalCloseBtn.addEventListener('click', () => { quoteDialog.close(); }); } // Click outside to close native dialog if (quoteDialog) { quoteDialog.addEventListener('click', (e) => { const rect = quoteDialog.getBoundingClientRect(); const isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { quoteDialog.close(); } }); } // Handle Modal Quote Form Submission if (quoteForm) { quoteForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('lead-name')?.value || ''; const phone = document.getElementById('lead-phone')?.value || ''; const address = document.getElementById('lead-address')?.value || ''; const service = document.getElementById('lead-service')?.value || 'Yard Cleanup'; const notes = document.getElementById('lead-notes')?.value || ''; const leadData = { id: 'NG-' + Date.now().toString(36).toUpperCase(), source: 'modal_dialog', name, phone, address, service, notes, submittedAt: new Date().toISOString() }; try { const existing = JSON.parse(localStorage.getItem('nextgen_leads') || '[]'); existing.unshift(leadData); localStorage.setItem('nextgen_leads', JSON.stringify(existing)); } catch (err) { console.error('Storage error:', err); } // Close dialog quoteDialog.close(); // Show Toast Notification if (toast) { toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 6000); } // Reset Form quoteForm.reset(); }); } // Handle Dedicated Contact Page Form Submission const contactForm = document.getElementById('contact-form'); const contactSuccessBox = document.getElementById('contact-success-box'); const contactSmsLink = document.getElementById('contact-sms-link'); const contactLeadIdSpan = document.getElementById('contact-lead-id'); if (contactForm) { contactForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('contact-name')?.value || ''; const phone = document.getElementById('contact-phone')?.value || ''; const address = document.getElementById('contact-address')?.value || ''; const service = document.getElementById('contact-service')?.value || 'Yard Cleanup'; const urgency = document.getElementById('contact-urgency')?.value || 'Flexible'; const size = document.getElementById('contact-size')?.value || 'Standard'; const notes = document.getElementById('contact-notes')?.value || ''; const leadId = 'NG-' + Date.now().toString(36).toUpperCase(); const leadData = { id: leadId, source: 'contact_page', name, phone, address, service, urgency, size, notes, submittedAt: new Date().toISOString() }; try { const existing = JSON.parse(localStorage.getItem('nextgen_leads') || '[]'); existing.unshift(leadData); localStorage.setItem('nextgen_leads', JSON.stringify(existing)); } catch (err) { console.error('Storage error:', err); } // Update UI to success state contactForm.style.display = 'none'; if (contactLeadIdSpan) { contactLeadIdSpan.textContent = leadId; } if (contactSuccessBox) { contactSuccessBox.classList.add('show'); } // Build instant mobile SMS follow-up link if (contactSmsLink) { const textMessage = `Hi Next Gen! My name is ${name} at ${address}. I submitted quote request #${leadId} for ${service} (${size}). Please let me know your availability!`; contactSmsLink.href = `sms:6617439600?body=${encodeURIComponent(textMessage)}`; } // Trigger Toast if (toast) { toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 6000); } }); } // ========================================================================== // 4. INTERACTIVE YARD COST ESTIMATOR LOGIC // ========================================================================== const priceDisplay = document.getElementById('calc-price-display'); const timeDisplay = document.getElementById('calc-time-display'); const calcBookBtn = document.getElementById('calc-book-btn'); // Base pricing matrix [Min, Max] const serviceRates = { 'cleanup': { base: [350, 550], time: 'Same Day (3 - 5 Hours)' }, 'fire-abatement': { base: [450, 800], time: 'Same Day (4 - 6 Hours)' }, 'turf': { base: [2200, 4800], time: '2 - 3 Days' }, 'rockscape': { base: [1800, 3900], time: '2 - 4 Days' }, 'trees': { base: [400, 950], time: 'Same Day (3 - 6 Hours)' }, 'maintenance': { base: [180, 320], time: 'Routine (1.5 - 2.5 Hours)' } }; const sizeMultipliers = { 'small': 0.75, 'medium': 1.0, 'large': 1.8, 'acreage': 3.2 }; const conditionMultipliers = { 'mild': 0.85, 'heavy': 1.15, 'severe': 1.55 }; function updateEstimate() { const selectedService = document.querySelector('input[name="calc-service"]:checked')?.value || 'cleanup'; const selectedSize = document.querySelector('input[name="calc-size"]:checked')?.value || 'medium'; const selectedCondition = document.querySelector('input[name="calc-condition"]:checked')?.value || 'heavy'; const serviceData = serviceRates[selectedService] || serviceRates['cleanup']; const sizeMult = sizeMultipliers[selectedSize] || 1.0; const condMult = conditionMultipliers[selectedCondition] || 1.0; let minPrice = Math.round((serviceData.base[0] * sizeMult * condMult) / 25) * 25; let maxPrice = Math.round((serviceData.base[1] * sizeMult * condMult) / 25) * 25; if (priceDisplay) { priceDisplay.textContent = `$${minPrice.toLocaleString()} – $${maxPrice.toLocaleString()}`; } if (timeDisplay) { let timeText = serviceData.time; if (selectedSize === 'acreage' && !timeText.includes('Days')) { timeText = '1 - 2 Days (Full Crew)'; } if (document.documentElement.lang === 'es') { timeText = timeText.replace(/Same Day/gi, 'Mismo Día') .replace(/Same-Day/gi, 'Mismo Día') .replace(/Hours/gi, 'Horas') .replace(/Days/gi, 'Días') .replace(/Routine/gi, 'Rutina') .replace(/\(Full Crew\)/gi, '(Equipo Completo)'); } timeDisplay.textContent = timeText; } } // Listen for language changes to update localized calculator output window.addEventListener('languageChanged', () => { updateEstimate(); }); // Listen to all calculator radio buttons const calcInputs = document.querySelectorAll('input[name="calc-service"], input[name="calc-size"], input[name="calc-condition"]'); calcInputs.forEach(input => { input.addEventListener('change', updateEstimate); }); // Calculate Initial Estimate updateEstimate(); // Book with current estimate scope if (calcBookBtn) { calcBookBtn.addEventListener('click', () => { const selectedServiceEl = document.querySelector('input[name="calc-service"]:checked'); const selectedSizeEl = document.querySelector('input[name="calc-size"]:checked'); const selectedConditionEl = document.querySelector('input[name="calc-condition"]:checked'); const serviceTitle = selectedServiceEl?.closest('.calc-option')?.querySelector('.calc-option-title')?.textContent || 'Yardwork'; const sizeTitle = selectedSizeEl?.closest('.calc-option')?.querySelector('.calc-option-title')?.textContent || 'Medium Lot'; const conditionTitle = selectedConditionEl?.closest('.calc-option')?.querySelector('.calc-option-title')?.textContent || 'Heavy Overgrowth'; const currentPrice = priceDisplay?.textContent || ''; const notes = `Calculator Estimate Scope: ${serviceTitle} | Property: ${sizeTitle} | Condition: ${conditionTitle} | Estimated Range: ${currentPrice}`; openModal(serviceTitle, notes); }); } // ========================================================================== // 5. INTERACTIVE BEFORE & AFTER SLIDER // ========================================================================== const baContainer = document.getElementById('ba-slider'); const baLayer = document.getElementById('ba-layer'); const baHandle = document.getElementById('ba-handle'); if (baContainer && baLayer && baHandle) { let isDragging = false; const setSliderPosition = (x) => { const rect = baContainer.getBoundingClientRect(); let pos = ((x - rect.left) / rect.width) * 100; pos = Math.max(0, Math.min(100, pos)); // clamp 0 - 100 baContainer.style.setProperty('--slider-pos', `${pos}%`); }; // Mouse events baContainer.addEventListener('mousedown', (e) => { isDragging = true; setSliderPosition(e.clientX); }); window.addEventListener('mousemove', (e) => { if (!isDragging) return; setSliderPosition(e.clientX); }); window.addEventListener('mouseup', () => { isDragging = false; }); // Touch events for mobile/tablet baContainer.addEventListener('touchstart', (e) => { isDragging = true; if (e.touches.length > 0) { setSliderPosition(e.touches[0].clientX); } }, { passive: true }); window.addEventListener('touchmove', (e) => { if (!isDragging || e.touches.length === 0) return; setSliderPosition(e.touches[0].clientX); }, { passive: true }); window.addEventListener('touchend', () => { isDragging = false; }); } // 6. Smooth Scroll Header Offset document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { const href = this.getAttribute('href'); if (href === '#' || !href.startsWith('#')) return; const target = document.querySelector(href); if (target) { e.preventDefault(); const headerHeight = document.querySelector('.site-header')?.offsetHeight || 80; const targetPos = target.getBoundingClientRect().top + window.pageYOffset - headerHeight; window.scrollTo({ top: targetPos, behavior: 'smooth' }); } }); }); // ========================================================================== // 7. PODCAST SAMPLE AUDIO CONTROLS (Zero Image / Pure SVG) // ========================================================================== const playBtn = document.getElementById('podcast-play-sample'); if (playBtn) { let isPlaying = false; let playInterval = null; let currentSeconds = 0; const playerTrackFill = document.querySelector('.player-fill'); const playerTime = document.querySelector('.player-time'); playBtn.addEventListener('click', () => { isPlaying = !isPlaying; if (isPlaying) { playBtn.innerHTML = ''; playBtn.setAttribute('aria-label', 'Pause sample episode audio'); playInterval = setInterval(() => { currentSeconds += 1; if (currentSeconds > 1458) currentSeconds = 0; const mins = Math.floor(currentSeconds / 60); const secs = (currentSeconds % 60).toString().padStart(2, '0'); if (playerTime) playerTime.textContent = `${mins}:${secs} / 24:18`; if (playerTrackFill) { const pct = Math.min(100, Math.max(0, (currentSeconds / 1458) * 100)); playerTrackFill.style.width = `${pct}%`; } }, 1000); } else { clearInterval(playInterval); playBtn.innerHTML = ''; playBtn.setAttribute('aria-label', 'Play sample episode audio'); } }); } });