/** * Portfolio Alexandre — Scripts JS * Filtres dynamiques, navigation mobile, lightbox */ document.addEventListener('DOMContentLoaded', () => { // ─── Navigation mobile ───────────────────────────────────────────────── const navToggle = document.getElementById('navToggle'); const navLinks = document.querySelector('.nav-links'); if (navToggle && navLinks) { navToggle.addEventListener('click', () => { navLinks.classList.toggle('open'); }); // Fermer le menu au clic sur un lien navLinks.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', () => navLinks.classList.remove('open')); }); } // ─── Filtres dynamiques (page projets) ──────────────────────────────── const filterPills = document.querySelectorAll('.filter-pill:not(.pill-more)'); filterPills.forEach(pill => { pill.addEventListener('click', () => { const filterType = pill.dataset.filter; // 'category' ou 'tech' const filterValue = pill.dataset.value; // Mettre à jour l'input hidden correspondant const inputId = filterType === 'category' ? 'categoryInput' : 'techInput'; const hiddenInput = document.getElementById(inputId); if (hiddenInput) { hiddenInput.value = filterValue; } // Mettre à jour l'apparence des pills du même groupe const group = pill.closest('.filter-pills'); if (group) { group.querySelectorAll('.filter-pill').forEach(p => p.classList.remove('active')); pill.classList.add('active'); } // Soumettre le formulaire automatiquement const form = document.getElementById('filtersForm'); if (form) form.submit(); }); }); // ─── Smooth scroll pour ancres internes ─────────────────────────────── document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const targetId = anchor.getAttribute('href').slice(1); const target = document.getElementById(targetId); if (target) { e.preventDefault(); const navHeight = parseInt( getComputedStyle(document.documentElement).getPropertyValue('--nav-height') ) || 68; const top = target.getBoundingClientRect().top + window.scrollY - navHeight - 16; window.scrollTo({ top, behavior: 'smooth' }); } }); }); // ─── Animation d'apparition des cartes ──────────────────────────────── const cards = document.querySelectorAll('.project-card'); if ('IntersectionObserver' in window) { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry, i) => { if (entry.isIntersecting) { setTimeout(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }, i * 80); observer.unobserve(entry.target); } }); }, { threshold: 0.1 } ); cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; card.style.transition = 'opacity 0.4s ease, transform 0.4s ease'; observer.observe(card); }); } // ─── Navbar : réduire au scroll ─────────────────────────────────────── const navbar = document.querySelector('.navbar'); let lastScrollY = window.scrollY; window.addEventListener('scroll', () => { if (window.scrollY > 80) { navbar?.classList.add('scrolled'); } else { navbar?.classList.remove('scrolled'); } lastScrollY = window.scrollY; }, { passive: true }); }); // ─── Lightbox (fonctions globales appelées depuis HTML) ────────────────── function openLightbox(src) { const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightboxImg'); if (lightbox && lightboxImg) { lightboxImg.src = src; lightbox.classList.add('open'); document.body.style.overflow = 'hidden'; } } function closeLightbox() { const lightbox = document.getElementById('lightbox'); if (lightbox) { lightbox.classList.remove('open'); document.body.style.overflow = ''; } } // Fermer la lightbox avec Échap document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeLightbox(); });