// Application JavaScript pour MRM & Co // Génération dynamique des sections depuis config.js document.addEventListener('DOMContentLoaded', function () { // Initialisation initializeColors(); renderServices(); renderTeam(); updateContactInfo(); setCurrentYear(); initializeMobileMenu(); initializeContactForm(); initializeSmoothScroll(); initializeHeaderScroll(); initializeHeroParallax(); initializeTestimonials(); }); /** * Applique les couleurs personnalisées depuis la configuration */ function initializeColors() { if (typeof config !== 'undefined' && config.colors) { document.documentElement.style.setProperty('--color-primary', config.colors.primary); document.documentElement.style.setProperty('--color-secondary', config.colors.secondary); document.documentElement.style.setProperty('--color-accent', config.colors.accent); } } /** * Génère dynamiquement la section Services */ function renderServices() { const servicesContainer = document.getElementById('services-container'); if (!servicesContainer || typeof config === 'undefined' || !config.services) { console.error('Configuration des services non trouvée'); return; } servicesContainer.innerHTML = config.services.map((service, idx) => { var iconId = 'icon-' + (service.icon || 'web'); return `

${service.title}

${service.description}

`; }).join(''); } /** * Génère dynamiquement la section Équipe */ function renderTeam() { const teamContainer = document.getElementById('team-container'); if (!teamContainer || typeof config === 'undefined' || !config.team) { console.error('Configuration de l\'équipe non trouvée'); return; } teamContainer.innerHTML = config.team.map(function (member, idx) { // fallback avatar via ui-avatars (utilisé si l'image externe échoue) var primaryColor = (config.colors && config.colors.primary) ? config.colors.primary.replace('#', '') : '2563eb'; var avatarFallback = 'https://ui-avatars.com/api/?name=' + encodeURIComponent(member.name) + '&background=ffffff&color=' + primaryColor + '&size=512'; return `
${member.name}

${member.name}

${member.role}

${member.description}

`; }).map((html, i) => html.replace('card-hover', 'card-hover reveal')).join(''); // Attacher les handlers d'erreur d'image après insertion pour éviter les attributs inline attachImageFallbacks(); } function attachImageFallbacks() { document.querySelectorAll('#team-container img[data-fallback]').forEach(function (img) { if (img.__fallbackAttached) return; img.addEventListener('error', function () { var fb = img.getAttribute('data-fallback'); if (fb) img.src = fb; }); img.__fallbackAttached = true; }); } /** * Met à jour les informations de contact depuis la configuration */ function updateContactInfo() { if (typeof config === 'undefined' || !config.company) { return; } // Mise à jour du hero const heroDesc = document.getElementById('hero-description'); if (heroDesc && config.company.description) { heroDesc.textContent = config.company.description; } // Mise à jour des coordonnées const emailEl = document.getElementById('contact-email'); if (emailEl && config.company.email) { emailEl.textContent = config.company.email; } const addressEl = document.getElementById('contact-address'); if (addressEl && config.company.address) { addressEl.textContent = config.company.address; } // Mise à jour du footer const footerDesc = document.getElementById('footer-description'); if (footerDesc && config.company.tagline) { footerDesc.textContent = config.company.tagline; } } /** * Initialise le menu mobile responsive */ function initializeMobileMenu() { const mobileMenuBtn = document.getElementById('mobile-menu-btn'); const mobileMenu = document.getElementById('mobile-menu'); if (mobileMenuBtn && mobileMenu) { mobileMenuBtn.addEventListener('click', function () { mobileMenu.classList.toggle('hidden'); }); // Fermer le menu lors du clic sur un lien const mobileLinks = mobileMenu.querySelectorAll('a'); mobileLinks.forEach(link => { link.addEventListener('click', function () { mobileMenu.classList.add('hidden'); }); }); } } /** * Gestion du formulaire de contact */ function initializeContactForm() { const contactForm = document.getElementById('contact-form'); if (contactForm) { contactForm.addEventListener('submit', function (e) { e.preventDefault(); // Récupération des données du formulaire const formData = { name: document.getElementById('name').value, email: document.getElementById('email').value, message: document.getElementById('message').value }; // Simulation d'envoi (à remplacer par un vrai backend) console.log('Formulaire soumis:', formData); // Message de confirmation alert('Merci pour votre message ! Nous vous répondrons dans les plus brefs délais.'); // Réinitialisation du formulaire contactForm.reset(); }); } } /** * Scroll fluide pour les ancres de navigation */ function initializeSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const targetId = this.getAttribute('href'); if (targetId === '#') return; const targetElement = document.querySelector(targetId); if (targetElement) { const headerOffset = 80; // Hauteur du header fixe const elementPosition = targetElement.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }); }); } /** * Animation au scroll pour les éléments */ function initializeScrollAnimations() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-fade-in'); observer.unobserve(entry.target); } }); }, { threshold: 0.12 }); // Observer tous les éléments avec la classe reveal document.querySelectorAll('.reveal').forEach(el => { observer.observe(el); }); } // Initialiser les animations au scroll après le chargement window.addEventListener('load', initializeScrollAnimations); /** * Insère l'année courante dans le footer */ function setCurrentYear() { var el = document.getElementById('current-year'); if (el) { el.textContent = new Date().getFullYear(); } } /** * Change l'apparence du header quand on scroll */ function initializeHeaderScroll() { var header = document.querySelector('header'); if (!header) return; function onScroll() { if (window.scrollY > 18) header.classList.add('header-scrolled'); else header.classList.remove('header-scrolled'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); } /** * Micro-parallax for hero illustration */ function initializeHeroParallax() { var hero = document.getElementById('hero-illustration'); if (!hero) return; hero.addEventListener('mousemove', function (e) { var rect = hero.getBoundingClientRect(); var x = (e.clientX - rect.left) / rect.width - 0.5; // -0.5..0.5 var y = (e.clientY - rect.top) / rect.height - 0.5; var blobs = hero.querySelectorAll('.bg-blob'); blobs.forEach(function (b, i) { var depth = (i === 0) ? 18 : -12; b.style.transform = 'translate(' + (-x * depth) + 'px,' + (-y * depth) + 'px)'; }); }); // reset on leave hero.addEventListener('mouseleave', function () { var blobs = hero.querySelectorAll('.bg-blob'); blobs.forEach(function (b) { b.style.transform = ''; }); }); } /** * Testimonials slider simple */ function initializeTestimonials() { var slides = Array.from(document.querySelectorAll('#testimonial-slides .testimonial-slide')); if (!slides.length) return; var dotsContainer = document.getElementById('testimonial-dots'); var current = 0; var autoplay = true; var timer = null; function goTo(idx) { slides.forEach((s, i) => s.classList.toggle('active', i === idx)); Array.from(dotsContainer.children).forEach((d, i) => d.classList.toggle('active', i === idx)); current = idx; } function next() { goTo((current + 1) % slides.length); } function prev() { goTo((current - 1 + slides.length) % slides.length); } // dots slides.forEach((s, i) => { var dot = document.createElement('div'); dot.className = 'dot' + (i === 0 ? ' active' : ''); dot.addEventListener('click', function () { goTo(i); resetTimer(); }); dotsContainer.appendChild(dot); }); document.getElementById('next-testimonial').addEventListener('click', function () { next(); resetTimer(); }); document.getElementById('prev-testimonial').addEventListener('click', function () { prev(); resetTimer(); }); function startTimer() { if (timer) clearInterval(timer); timer = setInterval(next, 5000); } function resetTimer() { if (autoplay) startTimer(); } // pause on hover var container = document.querySelector('.testimonials'); container.addEventListener('mouseenter', function () { autoplay = false; if (timer) clearInterval(timer); }); container.addEventListener('mouseleave', function () { autoplay = true; startTimer(); }); // start goTo(0); startTimer(); } /** * Initialisation du canvas d'arrière-plan interactif */ function initializeBackgroundCanvas() { if (typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; var canvas = document.getElementById('bg-canvas'); if (!canvas) return; var ctx = canvas.getContext('2d'); var DPR = window.devicePixelRatio || 1; var width, height; var mouse = { x: -9999, y: -9999 }; function resize() { width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); canvas.width = Math.floor(width * DPR); canvas.height = Math.floor(height * DPR); canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); } window.addEventListener('resize', resize); resize(); canvas.addEventListener('mousemove', function (e) { mouse.x = e.clientX; mouse.y = e.clientY; }); canvas.addEventListener('mouseleave', function () { mouse.x = -9999; mouse.y = -9999; }); // particles/blobs var blobs = []; var count = Math.max(6, Math.min(28, Math.floor((width * height) / 200000))); for (var i = 0; i < count; i++) { blobs.push({ x: Math.random() * width, y: Math.random() * height, r: 80 + Math.random() * 260, vx: (Math.random() - 0.5) * 0.2, vy: (Math.random() - 0.5) * 0.2, hue: 210 + Math.random() * 60 }); } function drawBlob(b) { var grd = ctx.createRadialGradient(b.x, b.y, b.r * 0.15, b.x, b.y, b.r); var h = b.hue; grd.addColorStop(0, 'hsla(' + h + ',90%,70%,0.20)'); grd.addColorStop(0.4, 'hsla(' + (h + 30) + ',80%,60%,0.12)'); grd.addColorStop(1, 'hsla(' + (h + 60) + ',70%,50%,0.02)'); ctx.fillStyle = grd; ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); ctx.fill(); } function step() { ctx.clearRect(0, 0, width, height); // subtle background overlay ctx.globalCompositeOperation = 'source-over'; for (var i = 0; i < blobs.length; i++) { var b = blobs[i]; // move b.x += b.vx; b.y += b.vy; // slight attraction to mouse if (mouse.x > -9998) { var dx = mouse.x - b.x; var dy = mouse.y - b.y; var dist = Math.sqrt(dx * dx + dy * dy) + 1; var force = Math.min(80 / dist, 0.9); b.vx += dx * 0.0008 * force; b.vy += dy * 0.0008 * force; } // slow down velocity b.vx *= 0.995; b.vy *= 0.995; // wrap if (b.x < -b.r) b.x = width + b.r; if (b.x > width + b.r) b.x = -b.r; if (b.y < -b.r) b.y = height + b.r; if (b.y > height + b.r) b.y = -b.r; drawBlob(b); } // small radial highlight near mouse for interactivity if (mouse.x > -9998) { var radial = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, 180); radial.addColorStop(0, 'rgba(255,255,255,0.06)'); radial.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = radial; ctx.fillRect(mouse.x - 180, mouse.y - 180, 360, 360); } requestAnimationFrame(step); } step(); } // lancer le background après DOM ready window.addEventListener('load', function () { initializeBackgroundCanvas(); });