- Formulaire HTML5 avec validation (nom, prénom, email, entreprise, catégorie, objet, message) - Validation JavaScript côté client (FormValidator) - Persistance localStorage des données (AppState) - Intégration reCAPTCHA v3 avec dégradation gracieuse - Traitement PHP sécurisé (CSRF, validation, envoi email) - Feedback utilisateur AJAX (succès/erreur) - Liens contact secondaires (LinkedIn, GitHub, Email protégé) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
106 lines
2.8 KiB
JavaScript
106 lines
2.8 KiB
JavaScript
// assets/js/main.js
|
|
// Script principal du portfolio
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initMobileMenu();
|
|
initNavbarScroll();
|
|
initEmailProtection();
|
|
});
|
|
|
|
/**
|
|
* Gestion du menu mobile
|
|
*/
|
|
function initMobileMenu() {
|
|
const toggle = document.getElementById('mobile-menu-toggle');
|
|
const menu = document.getElementById('mobile-menu');
|
|
|
|
if (!toggle || !menu) return;
|
|
|
|
const hamburgerIcon = toggle.querySelector('.hamburger-icon');
|
|
const closeIcon = toggle.querySelector('.close-icon');
|
|
|
|
function openMenu() {
|
|
menu.classList.remove('hidden');
|
|
menu.setAttribute('aria-hidden', 'false');
|
|
toggle.setAttribute('aria-expanded', 'true');
|
|
toggle.setAttribute('aria-label', 'Fermer le menu');
|
|
hamburgerIcon?.classList.add('hidden');
|
|
closeIcon?.classList.remove('hidden');
|
|
}
|
|
|
|
function closeMenu() {
|
|
menu.classList.add('hidden');
|
|
menu.setAttribute('aria-hidden', 'true');
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
toggle.setAttribute('aria-label', 'Ouvrir le menu');
|
|
hamburgerIcon?.classList.remove('hidden');
|
|
closeIcon?.classList.add('hidden');
|
|
}
|
|
|
|
function toggleMenu() {
|
|
const isOpen = toggle.getAttribute('aria-expanded') === 'true';
|
|
if (isOpen) {
|
|
closeMenu();
|
|
} else {
|
|
openMenu();
|
|
}
|
|
}
|
|
|
|
// Toggle au clic
|
|
toggle.addEventListener('click', toggleMenu);
|
|
|
|
// Fermer au clic sur un lien
|
|
menu.querySelectorAll('a').forEach(link => {
|
|
link.addEventListener('click', closeMenu);
|
|
});
|
|
|
|
// Fermer avec Escape
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') {
|
|
closeMenu();
|
|
toggle.focus();
|
|
}
|
|
});
|
|
|
|
// Fermer si on redimensionne vers desktop
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth >= 1024) {
|
|
closeMenu();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Effet d'ombre au scroll
|
|
*/
|
|
function initNavbarScroll() {
|
|
const navbar = document.getElementById('navbar');
|
|
if (!navbar) return;
|
|
|
|
window.addEventListener('scroll', () => {
|
|
if (window.scrollY > 10) {
|
|
navbar.classList.add('shadow-lg');
|
|
} else {
|
|
navbar.classList.remove('shadow-lg');
|
|
}
|
|
}, { passive: true });
|
|
}
|
|
|
|
/**
|
|
* Protection de l'email contre le scraping
|
|
* Reconstruit l'adresse email à partir de data-attributes
|
|
*/
|
|
function initEmailProtection() {
|
|
const emailLink = document.getElementById('email-link');
|
|
if (!emailLink) return;
|
|
|
|
const user = emailLink.dataset.user;
|
|
const domain = emailLink.dataset.domain;
|
|
|
|
if (user && domain) {
|
|
const email = `${user}@${domain}`;
|
|
emailLink.href = `mailto:${email}`;
|
|
emailLink.title = email;
|
|
}
|
|
}
|