// UTF-8 Encoding Declaration // Performance optimizations const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; // Throttle function for scroll events const throttle = (func, limit) => { let inThrottle; return function executedFunction(...args) { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }; document.addEventListener('DOMContentLoaded', () => { // Cache DOM elements const header = document.querySelector('.header'); const menuToggle = document.querySelector('.menu-toggle'); const navLinks = document.querySelector('.nav-links'); const sections = document.querySelectorAll('section'); const contactForm = document.querySelector('.contact-form'); const inputs = contactForm ? contactForm.querySelectorAll('input, textarea') : []; // Smooth scroll with offset document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { const headerHeight = header.offsetHeight; const targetPosition = target.getBoundingClientRect().top + window.pageYOffset; window.scrollTo({ top: targetPosition - headerHeight, behavior: 'smooth' }); // Close mobile menu if open if (navLinks.classList.contains('active')) { navLinks.classList.remove('active'); menuToggle.classList.remove('active'); } } }); }); // Header scroll effect with throttle let lastScroll = 0; const handleScroll = throttle(() => { const currentScroll = window.pageYOffset; if (currentScroll <= 0) { header.classList.remove('scroll-up'); return; } if (currentScroll > lastScroll && !header.classList.contains('scroll-down')) { // Scroll Down header.classList.remove('scroll-up'); header.classList.add('scroll-down'); } else if (currentScroll < lastScroll && header.classList.contains('scroll-down')) { // Scroll Up header.classList.remove('scroll-down'); header.classList.add('scroll-up'); } lastScroll = currentScroll; }, 100); window.addEventListener('scroll', handleScroll); // Mobile menu with improved animation if (menuToggle) { menuToggle.addEventListener('click', () => { navLinks.classList.toggle('active'); menuToggle.classList.toggle('active'); }); } // Close mobile menu when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.nav') && navLinks.classList.contains('active')) { navLinks.classList.remove('active'); menuToggle.classList.remove('active'); } }); // Close mobile menu on escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && navLinks.classList.contains('active')) { navLinks.classList.remove('active'); menuToggle.classList.remove('active'); } }); // Intersection Observer for animations const observerOptions = { root: null, rootMargin: '0px', threshold: 0.1 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate'); observer.unobserve(entry.target); } }); }, observerOptions); // Observe elements const animatedElements = document.querySelectorAll('.fade-in, .slide-in, .scale-in'); animatedElements.forEach(el => observer.observe(el)); // Parallax effect const parallaxElements = document.querySelectorAll('.parallax'); const handleParallax = throttle(() => { const scrolled = window.pageYOffset; parallaxElements.forEach(element => { const speed = element.dataset.speed || 0.5; element.style.transform = `translateY(${scrolled * speed}px)`; }); }, 10); window.addEventListener('scroll', handleParallax); // Form validation and WhatsApp functionality if (contactForm) { const validateInput = (input) => { const value = input.value.trim(); const type = input.type; const name = input.name; let isValid = true; let errorMessage = ''; switch (type) { case 'email': const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { isValid = false; errorMessage = 'Veuillez entrer une adresse email valide'; } break; case 'tel': const phoneRegex = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/; if (value && !phoneRegex.test(value)) { isValid = false; errorMessage = 'Veuillez entrer un numéro de téléphone valide'; } break; default: if (value.length < 2) { isValid = false; errorMessage = 'Ce champ est requis'; } } const formGroup = input.closest('.form-group'); if (!isValid) { formGroup.classList.add('error'); const errorElement = formGroup.querySelector('.error-message') || document.createElement('div'); errorElement.className = 'error-message'; errorElement.textContent = errorMessage; if (!formGroup.querySelector('.error-message')) { formGroup.appendChild(errorElement); } } else { formGroup.classList.remove('error'); const errorElement = formGroup.querySelector('.error-message'); if (errorElement) { errorElement.remove(); } } return isValid; }; // WhatsApp button functionality const whatsappButton = document.getElementById('whatsappButton'); if (whatsappButton) { whatsappButton.addEventListener('click', () => { let isValid = true; inputs.forEach(input => { if (!validateInput(input)) { isValid = false; } }); if (!isValid) { showNotification('Veuillez corriger les erreurs dans le formulaire', 'error'); return; } // Get form data const name = document.getElementById('name').value.trim(); const email = document.getElementById('email').value.trim(); const phone = document.getElementById('phone').value.trim(); const day = document.getElementById('day').value.trim(); const message = document.getElementById('message').value.trim(); // Create WhatsApp message const whatsappMessage = `Bonjour Dr. Tber,\n\nJe souhaite prendre rendez-vous.\n\n*Informations de contact :*\n• Nom : ${name}\n• Email : ${email}\n• Téléphone : ${phone}\n• Jour souhaité : ${day}\n\n*Message :*\n${message}\n\nMerci de me recontacter.`; // Encode message for URL const encodedMessage = encodeURIComponent(whatsappMessage); // WhatsApp number (212661942375) const whatsappNumber = '212661942375'; // Create WhatsApp URL const whatsappUrl = `https://wa.me/${whatsappNumber}?text=${encodedMessage}`; // Open WhatsApp window.open(whatsappUrl, '_blank'); // Show success notification showNotification('WhatsApp ouvert avec votre message pré-rempli !', 'success'); }); } // WhatsApp fallback button const whatsappFallback = document.getElementById('whatsappFallback'); if (whatsappFallback) { whatsappFallback.addEventListener('click', () => { // Get form data const name = document.getElementById('name').value.trim(); const phone = document.getElementById('phone').value.trim(); const typeConsult = document.getElementById('typeConsult').value; const date = document.getElementById('date').value; if (!name || !phone || !date) { alert('Veuillez remplir tous les champs obligatoires.'); return; } // Create WhatsApp message const whatsappMessage = `Bonjour Dr. Tber,\n\nJe souhaite prendre rendez-vous.\n\n*Informations de contact :*\n• Nom : ${name}\n• Téléphone : ${phone}\n• Type de consultation : ${typeConsult}\n• Date souhaitée : ${date}\n\nMerci de me recontacter.`; // Encode message for URL const encodedMessage = encodeURIComponent(whatsappMessage); // WhatsApp number (212661942375) const whatsappNumber = '212661942375'; // Create WhatsApp URL const whatsappUrl = `https://wa.me/${whatsappNumber}?text=${encodedMessage}`; // Open WhatsApp window.open(whatsappUrl, '_blank'); // Show thank you step formStep.classList.remove('active'); thankyouStep.classList.add('active'); formStep.reset(); }); } // Intercept the contact form submission, send it via AJAX to Formspree, and show the thank you modal on success. Reset the form after showing the modal. Show an alert on error. contactForm.addEventListener('submit', function(e) { e.preventDefault(); var formData = new FormData(contactForm); fetch(contactForm.action, { method: 'POST', body: formData, headers: { 'Accept': 'application/json' } }).then(function(response) { if (response.ok) { document.getElementById('thankYouModal').style.display = 'flex'; contactForm.reset(); } else { alert('Une erreur est survenue. Veuillez réessayer.'); } }).catch(function() { alert('Une erreur est survenue. Veuillez réessayer.'); }); }); } // Add CSS animations const style = document.createElement('style'); style.textContent = ` @keyframes navItemFade { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } } .notification { position: fixed; bottom: 20px; right: 20px; padding: 15px 25px; border-radius: 5px; color: white; transform: translateY(100px); opacity: 0; transition: all 0.3s ease; z-index: 1000; } .notification.show { transform: translateY(0); opacity: 1; } .notification.success { background-color: #4CAF50; } .notification.error { background-color: #f44336; } .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(255,255,255,.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite; margin-right: 10px; } @keyframes spin { to { transform: rotate(360deg); } } `; document.head.appendChild(style); // Initialization document.querySelectorAll('.fade-in, .slide-in, .scale-in').forEach(el => { el.style.opacity = '0'; }); // Rendez-vous Modal Logic const openRdvBtn = document.getElementById('openRdvModal'); const rdvModalBg = document.getElementById('rdvModal'); const closeRdvBtn = document.getElementById('closeRdvModal'); const calendarStep = document.getElementById('calendarStep'); const formStep = document.getElementById('rdvForm'); const thankyouStep = document.getElementById('thankyouStep'); const calendarNextBtn = document.getElementById('calendarNextBtn'); const rdvDate = document.getElementById('rdvDate'); const dateInput = document.getElementById('date'); const closeThankYou = document.getElementById('closeThankYou'); // Ensure modal is hidden on page load if (rdvModalBg) { rdvModalBg.classList.remove('active'); rdvModalBg.style.display = 'none'; } console.log('Modal elements found:', { openRdvBtn: !!openRdvBtn, rdvModalBg: !!rdvModalBg, closeRdvBtn: !!closeRdvBtn, calendarStep: !!calendarStep, formStep: !!formStep, thankyouStep: !!thankyouStep, calendarNextBtn: !!calendarNextBtn, rdvDate: !!rdvDate, dateInput: !!dateInput, closeThankYou: !!closeThankYou }); if (rdvDate) { rdvDate.min = new Date().toISOString().split('T')[0]; } // Function to open modal and show calendar const openRdvModal = () => { console.log('Opening modal to calendar step'); rdvModalBg.classList.add('active'); calendarStep.classList.add('active'); formStep.classList.remove('active'); thankyouStep.classList.remove('active'); }; if (openRdvBtn && rdvModalBg && closeRdvBtn && calendarStep && formStep && thankyouStep && calendarNextBtn && rdvDate && dateInput && closeThankYou) { console.log('All modal elements found, setting up event listeners'); // Simple button click openRdvBtn.addEventListener('click', (e) => { e.preventDefault(); openRdvModal(); }); // Add listeners for other "Prendre Rendez-vous" buttons const heroButton = document.querySelector('.hero .cta-button'); const navbarButton = document.querySelector('.navbar-rdv-btn'); if (heroButton) { heroButton.addEventListener('click', (e) => { e.preventDefault(); openRdvModal(); }); } if (navbarButton) { navbarButton.addEventListener('click', (e) => { e.preventDefault(); openRdvModal(); }); } closeRdvBtn.addEventListener('click', () => { console.log('Closing modal'); rdvModalBg.classList.remove('active'); }); rdvModalBg.addEventListener('click', (e) => { if (e.target === rdvModalBg) { console.log('Closing modal via background click'); rdvModalBg.classList.remove('active'); } }); closeThankYou.addEventListener('click', () => { console.log('Closing modal via thank you button'); rdvModalBg.classList.remove('active'); }); // Calendar step calendarNextBtn.addEventListener('click', (e) => { e.preventDefault(); console.log('Calendar next clicked, date value:', rdvDate.value); if (!rdvDate.value) { rdvDate.style.borderColor = '#E63946'; return; } rdvDate.style.borderColor = '#e3e7ed'; calendarStep.classList.remove('active'); formStep.classList.add('active'); dateInput.value = rdvDate.value; }); // Toggle group logic const toggleBtns = document.querySelectorAll('.toggle-btn'); const typeConsultInput = document.getElementById('typeConsult'); toggleBtns.forEach(btn => { btn.addEventListener('click', () => { toggleBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); typeConsultInput.value = btn.getAttribute('data-value'); }); }); // Form submit formStep.addEventListener('submit', function(e) { e.preventDefault(); console.log('Form submitted'); // Get form data const formData = new FormData(formStep); // Show loading state const submitBtn = formStep.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Envoi en cours...'; submitBtn.disabled = true; // Submit to Formspree fetch(formStep.action, { method: 'POST', body: formData, headers: { 'Accept': 'application/json' } }) .then(function(response) { console.log('Formspree response:', response); if (response.ok) { console.log('Form submitted successfully'); formStep.classList.remove('active'); thankyouStep.classList.add('active'); formStep.reset(); } else { console.log('Form submission failed, but showing thank you anyway'); // Show thank you even if Formspree fails for better UX formStep.classList.remove('active'); thankyouStep.classList.add('active'); formStep.reset(); } }) .catch(function(error) { console.log('Form submission error:', error); // Show thank you even if there's an error for better UX formStep.classList.remove('active'); thankyouStep.classList.add('active'); formStep.reset(); }) .finally(function() { // Reset button state submitBtn.textContent = originalText; submitBtn.disabled = false; }); }); } else { console.log('Some modal elements missing'); } }); // Utility functions function showNotification(message, type) { // Remove existing notifications const existingNotifications = document.querySelectorAll('.notification'); existingNotifications.forEach(notification => notification.remove()); const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); // Trigger animation requestAnimationFrame(() => { notification.classList.add('show'); }); // Remove notification setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notification.remove(); }, 300); }, 3000); }