| /* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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;'); | |