/* ════════════════════════════════════════════════════ AMUSEMENT — Application Logic Particles, counters, cursor glow, service worker ════════════════════════════════════════════════════ */ (function () { 'use strict'; // ─── Cursor Glow ─── const glow = document.getElementById('cursor-glow'); let mouseX = -1000, mouseY = -1000; document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; glow.style.left = mouseX + 'px'; glow.style.top = mouseY + 'px'; }); // card glow follow document.querySelectorAll('.feature-card').forEach((card) => { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100; card.style.setProperty('--mouse-x', x + '%'); card.style.setProperty('--mouse-y', y + '%'); }); }); // ─── Particles Canvas ─── const canvas = document.getElementById('particles-canvas'); const ctx = canvas.getContext('2d'); let particles = []; const PARTICLE_COUNT = 60; function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resizeCanvas(); window.addEventListener('resize', resizeCanvas); class Particle { constructor() { this.reset(); } reset() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.size = Math.random() * 2 + 0.5; this.speedX = (Math.random() - 0.5) * 0.3; this.speedY = (Math.random() - 0.5) * 0.3; this.opacity = Math.random() * 0.5 + 0.1; this.hue = 260 + Math.random() * 80; // purple-pink range } update() { this.x += this.speedX; this.y += this.speedY; if (this.x < 0 || this.x > canvas.width) this.speedX *= -1; if (this.y < 0 || this.y > canvas.height) this.speedY *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fillStyle = `hsla(${this.hue}, 70%, 65%, ${this.opacity})`; ctx.fill(); } } for (let i = 0; i < PARTICLE_COUNT; i++) { particles.push(new Particle()); } function drawLines() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 150) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = `hsla(270, 60%, 60%, ${0.06 * (1 - dist / 150)})`; ctx.lineWidth = 0.5; ctx.stroke(); } } } } function animateParticles() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach((p) => { p.update(); p.draw(); }); drawLines(); requestAnimationFrame(animateParticles); } animateParticles(); // ─── Navigation ─── const nav = document.getElementById('main-nav'); const navToggle = document.getElementById('nav-toggle'); const mobileMenu = document.getElementById('mobile-menu'); const navLinks = document.querySelectorAll('.nav-link, .mobile-link'); window.addEventListener('scroll', () => { nav.classList.toggle('scrolled', window.scrollY > 40); updateActiveNavLink(); }); navToggle.addEventListener('click', () => { navToggle.classList.toggle('open'); mobileMenu.classList.toggle('show'); }); navLinks.forEach((link) => { link.addEventListener('click', () => { navToggle.classList.remove('open'); mobileMenu.classList.remove('show'); }); }); function updateActiveNavLink() { const sections = document.querySelectorAll('.section'); let current = ''; sections.forEach((s) => { const top = s.offsetTop - 120; if (window.scrollY >= top) current = s.getAttribute('id'); }); document.querySelectorAll('.nav-link').forEach((l) => { l.classList.toggle('active', l.dataset.section === current); }); } // ─── Scroll Reveal ─── const revealElements = document.querySelectorAll( '.feature-card, .stat-card, .gallery-item, .contact-container, .section-header' ); revealElements.forEach((el) => el.classList.add('reveal')); const revealObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('visible'); revealObserver.unobserve(entry.target); } }); }, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' } ); revealElements.forEach((el) => revealObserver.observe(el)); // ─── Stat Counters ─── const statNumbers = document.querySelectorAll('.stat-number'); const statFills = document.querySelectorAll('.stat-fill'); let statAnimated = false; function animateCounters() { if (statAnimated) return; statAnimated = true; statNumbers.forEach((el) => { const target = parseInt(el.dataset.target, 10); const duration = 2000; const start = performance.now(); function tick(now) { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic el.textContent = Math.round(target * eased).toLocaleString('fr-FR'); if (progress < 1) requestAnimationFrame(tick); } requestAnimationFrame(tick); }); statFills.forEach((el) => { const w = el.dataset.width; setTimeout(() => { el.style.width = w + '%'; }, 200); }); } const statsSection = document.getElementById('stats'); const statsObserver = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { animateCounters(); statsObserver.unobserve(statsSection); } }, { threshold: 0.3 } ); statsObserver.observe(statsSection); // ─── Contact Form ─── const contactForm = document.getElementById('contact-form'); contactForm.addEventListener('submit', (e) => { e.preventDefault(); showToast('✅ Message envoyé avec succès !'); contactForm.reset(); }); // ─── Toast ─── const toastEl = document.getElementById('toast'); let toastTimeout; function showToast(msg) { toastEl.textContent = msg; toastEl.classList.add('show'); clearTimeout(toastTimeout); toastTimeout = setTimeout(() => toastEl.classList.remove('show'), 3000); } // ─── PWA Install Prompt ─── let deferredPrompt; const installPrompt = document.getElementById('install-prompt'); const installBtn = document.getElementById('install-btn'); const dismissInstall = document.getElementById('dismiss-install'); window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; installPrompt.hidden = false; }); installBtn.addEventListener('click', async () => { if (!deferredPrompt) return; deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; if (outcome === 'accepted') showToast('📲 Application installée !'); deferredPrompt = null; installPrompt.hidden = true; }); dismissInstall.addEventListener('click', () => { installPrompt.hidden = true; }); // ─── Service Worker Registration ─── if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('sw.js') .then((reg) => console.log('SW registered:', reg.scope)) .catch((err) => console.warn('SW registration failed:', err)); }); } })();