/* ═══════════════════════════════════════════════════════════════════════════ ML ACADEMY — SHARED JAVASCRIPT Animations et interactions communes à toutes les pages ═══════════════════════════════════════════════════════════════════════════ */ // ═══════════════════════════════════════════════════════════════════════════ // NAVIGATION ACTIVE STATE // ═══════════════════════════════════════════════════════════════════════════ document.addEventListener('DOMContentLoaded', () => { // Mark active nav link based on current page const currentPage = window.location.pathname.split('/').pop() || 'index.html'; document.querySelectorAll('.nav-link').forEach(link => { const href = link.getAttribute('href'); if (href === currentPage || (currentPage === '' && href === 'index.html')) { link.classList.add('active'); } else { link.classList.remove('active'); } }); }); // ═══════════════════════════════════════════════════════════════════════════ // SMOOTH SCROLL FOR ANCHOR LINKS // ═══════════════════════════════════════════════════════════════════════════ document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { const offsetTop = target.offsetTop - 100; // Account for fixed navbar window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } }); }); // ═══════════════════════════════════════════════════════════════════════════ // SCROLL ANIMATIONS OBSERVER // ═══════════════════════════════════════════════════════════════════════════ const scrollObserverOptions = { root: null, rootMargin: '0px 0px -100px 0px', threshold: 0.1 }; const scrollObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); // Add stagger delay for child elements if needed const staggerChildren = entry.target.querySelectorAll('.stagger-child'); staggerChildren.forEach((child, index) => { child.style.animationDelay = `${index * 0.1}s`; child.classList.add('animate-in'); }); } }); }, scrollObserverOptions); // Observe all elements with scroll-animate class document.querySelectorAll('.scroll-animate').forEach(el => { scrollObserver.observe(el); }); // ═══════════════════════════════════════════════════════════════════════════ // NAVBAR SCROLL EFFECT // ═══════════════════════════════════════════════════════════════════════════ let lastScrollY = window.scrollY; let ticking = false; function updateNavbar() { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.style.background = 'rgba(15, 15, 26, 0.95)'; navbar.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; } else { navbar.style.background = 'rgba(15, 15, 26, 0.85)'; navbar.style.boxShadow = 'none'; } ticking = false; } window.addEventListener('scroll', () => { lastScrollY = window.scrollY; if (!ticking) { window.requestAnimationFrame(updateNavbar); ticking = true; } }); // ═══════════════════════════════════════════════════════════════════════════ // PARALLAX EFFECT FOR HERO BACKGROUND // ═══════════════════════════════════════════════════════════════════════════ const heroBg = document.querySelector('.hero-bg'); if (heroBg) { window.addEventListener('scroll', () => { const scrolled = window.scrollY; heroBg.style.transform = `translateY(${scrolled * 0.3}px)`; }); } // ═══════════════════════════════════════════════════════════════════════════ // TYPING EFFECT FOR HERO TEXT (optional) // ═══════════════════════════════════════════════════════════════════════════ function typeWriter(element, text, speed = 50) { let i = 0; element.textContent = ''; function type() { if (i < text.length) { element.textContent += text.charAt(i); i++; setTimeout(type, speed); } } type(); } // ═══════════════════════════════════════════════════════════════════════════ // COUNTER ANIMATION FOR STATS // ═══════════════════════════════════════════════════════════════════════════ function animateCounter(element, target, duration = 2000) { let start = 0; const increment = target / (duration / 16); function updateCounter() { start += increment; if (start < target) { element.textContent = Math.floor(start); requestAnimationFrame(updateCounter); } else { element.textContent = target; } } updateCounter(); } // Observe stat elements and animate when visible const statObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !entry.target.classList.contains('counted')) { entry.target.classList.add('counted'); const target = parseInt(entry.target.dataset.target); if (!isNaN(target)) { animateCounter(entry.target, target); } } }); }, { threshold: 0.5 }); document.querySelectorAll('[data-target]').forEach(el => { statObserver.observe(el); }); // ═══════════════════════════════════════════════════════════════════════════ // MAGNETIC BUTTON EFFECT // ═══════════════════════════════════════════════════════════════════════════ document.querySelectorAll('.btn').forEach(button => { button.addEventListener('mousemove', (e) => { const rect = button.getBoundingClientRect(); const x = e.clientX - rect.left - rect.width / 2; const y = e.clientY - rect.top - rect.height / 2; button.style.transform = `translate(${x * 0.1}px, ${y * 0.1}px)`; }); button.addEventListener('mouseleave', () => { button.style.transform = ''; }); }); // ═══════════════════════════════════════════════════════════════════════════ // CARD HOVER 3D EFFECT // ═══════════════════════════════════════════════════════════════════════════ document.querySelectorAll('.card, .feature-card, .module-card').forEach(card => { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const centerX = rect.width / 2; const centerY = rect.height / 2; const rotateX = (y - centerY) / 20; const rotateY = (centerX - x) / 20; card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(10px)`; }); card.addEventListener('mouseleave', () => { card.style.transform = ''; }); }); // ═══════════════════════════════════════════════════════════════════════════ // GLITCH EFFECT FOR TEXT (optional) // ═══════════════════════════════════════════════════════════════════════════ function glitchText(element, originalText) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let iterations = 0; const interval = setInterval(() => { element.textContent = originalText .split('') .map((char, index) => { if (index < iterations) { return originalText[index]; } return chars[Math.floor(Math.random() * chars.length)]; }) .join(''); if (iterations >= originalText.length) { clearInterval(interval); } iterations += 1 / 3; }, 30); } // ═══════════════════════════════════════════════════════════════════════════ // PARTICLE MOUSE INTERACTION // ═══════════════════════════════════════════════════════════════════════════ const particlesContainer = document.querySelector('.particles-container'); if (particlesContainer) { document.addEventListener('mousemove', (e) => { const particles = particlesContainer.querySelectorAll('.particle'); const mouseX = e.clientX / window.innerWidth; const mouseY = e.clientY / window.innerHeight; particles.forEach((particle, index) => { const speed = (index + 1) * 0.5; const x = (mouseX - 0.5) * speed * 20; const y = (mouseY - 0.5) * speed * 20; particle.style.transform = `translate(${x}px, ${y}px)`; }); }); } // ═══════════════════════════════════════════════════════════════════════════ // LOADING ANIMATION // ═══════════════════════════════════════════════════════════════════════════ window.addEventListener('load', () => { document.body.classList.add('loaded'); // Animate elements with delay document.querySelectorAll('[data-delay]').forEach(el => { const delay = parseInt(el.dataset.delay); setTimeout(() => { el.classList.add('animate-in'); }, delay); }); }); // ═══════════════════════════════════════════════════════════════════════════ // FORM VALIDATION HELPERS // ═══════════════════════════════════════════════════════════════════════════ function validateEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } function validateRequired(value) { return value.trim().length > 0; } // ═══════════════════════════════════════════════════════════════════════════ // TOOLTIP SYSTEM // ═══════════════════════════════════════════════════════════════════════════ function createTooltip(element, text) { const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.textContent = text; tooltip.style.cssText = ` position: absolute; background: var(--bg-tertiary); color: var(--text-primary); padding: 8px 12px; border-radius: 6px; font-size: 0.8rem; white-space: nowrap; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; `; document.body.appendChild(tooltip); element.addEventListener('mouseenter', () => { const rect = element.getBoundingClientRect(); tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`; tooltip.style.top = `${rect.top - tooltip.offsetHeight - 8}px`; tooltip.style.opacity = '1'; }); element.addEventListener('mouseleave', () => { tooltip.style.opacity = '0'; }); } // ═══════════════════════════════════════════════════════════════════════════ // COPY TO CLIPBOARD // ═══════════════════════════════════════════════════════════════════════════ async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showNotification('Copié dans le presse-papier !', 'success'); } catch (err) { showNotification('Erreur lors de la copie', 'error'); } } // ═══════════════════════════════════════════════════════════════════════════ // NOTIFICATION SYSTEM // ═══════════════════════════════════════════════════════════════════════════ function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 80px; right: 20px; padding: 12px 24px; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px; font-size: 0.9rem; z-index: 10000; animation: slideInRight 0.3s ease; `; if (type === 'success') { notification.style.borderColor = 'var(--success)'; notification.style.color = 'var(--success)'; } else if (type === 'error') { notification.style.borderColor = 'var(--danger)'; notification.style.color = 'var(--danger)'; } document.body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => notification.remove(), 300); }, 3000); } // ═══════════════════════════════════════════════════════════════════════════ // KEYBOARD SHORTCUTS // ═══════════════════════════════════════════════════════════════════════════ document.addEventListener('keydown', (e) => { // Ctrl/Cmd + K for search (if implemented) if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); // Open search modal } // Escape to close modals if (e.key === 'Escape') { document.querySelectorAll('.modal.open').forEach(modal => { modal.classList.remove('open'); }); } }); // ═══════════════════════════════════════════════════════════════════════════ // PREFERS REDUCED MOTION // ═══════════════════════════════════════════════════════════════════════════ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); if (prefersReducedMotion.matches) { document.documentElement.style.setProperty('--transition-fast', '0s'); document.documentElement.style.setProperty('--transition-base', '0s'); document.documentElement.style.setProperty('--transition-slow', '0s'); } // ═══════════════════════════════════════════════════════════════════════════ // CONSOLE EASTER EGG // ═══════════════════════════════════════════════════════════════════════════ console.log('%c🧠 ML Academy', 'font-size: 24px; font-weight: bold; color: #6366f1;'); console.log('%cFormation Machine Learning — GE-MCI 4A', 'font-size: 14px; color: #94a3b8;'); console.log('%cBienvenue dans la console ! Curieux de voir comment ça marche ?', 'font-size: 12px; color: #64748b;');