// ===== GESTIONNAIRE D'ÉTAT DE L'APPLICATION ===== class AppState { constructor() { this.currentPage = 'home'; this.isMenuOpen = false; this.visitedPages = new Set(['home']); this.init(); } init() { // Suivre la navigation this.trackNavigation(); // Initialiser les écouteurs d'événements globaux this.initGlobalEventListeners(); } trackNavigation() { // Suivre les pages visitées pour des statistiques potentielles console.log(`Page actuelle: ${this.currentPage}`); } toggleMenu() { this.isMenuOpen = !this.isMenuOpen; document.dispatchEvent(new CustomEvent('menuStateChange', { detail: { isOpen: this.isMenuOpen } })); return this.isMenuOpen; } navigateTo(page) { this.currentPage = page; this.visitedPages.add(page); document.dispatchEvent(new CustomEvent('pageChange', { detail: { page } })); } initGlobalEventListeners() { // Gérer les changements de page document.addEventListener('pageChange', (e) => { console.log(`Navigation vers: ${e.detail.page}`); // Mettre à jour l'URL si nécessaire if (window.history && window.history.pushState) { window.history.pushState({ page: e.detail.page }, '', `#${e.detail.page}`); } }); // Gérer le bouton retour window.addEventListener('popstate', (e) => { if (e.state && e.state.page) { this.currentPage = e.state.page; document.dispatchEvent(new CustomEvent('pageChange', { detail: { page: e.state.page } })); } }); // Écouter les clics sur les liens internes document.addEventListener('click', (e) => { const link = e.target.closest('a[href^="#"]'); if (link) { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { this.smoothScrollTo(targetElement); } } }); } smoothScrollTo(element) { const elementPosition = element.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - 100; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } } // ===== GESTIONNAIRE D'ANIMATIONS ===== class AnimationManager { constructor() { this.observer = null; this.animatedElements = new Set(); this.init(); } init() { this.setupIntersectionObserver(); this.initScrollAnimations(); this.initHoverEffects(); } setupIntersectionObserver() { const options = { root: null, rootMargin: '0px', threshold: 0.1 }; this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.animateElement(entry.target); this.observer.unobserve(entry.target); } }); }, options); // Observer les éléments à animer document.querySelectorAll('.animate-on-scroll').forEach(el => { this.observer.observe(el); }); } animateElement(element) { if (this.animatedElements.has(element)) return; this.animatedElements.add(element); element.classList.add('animate-in'); // Ajouter un délai basé sur la position const delay = Array.from(element.parentNode.children).indexOf(element) * 100; element.style.animationDelay = `${delay}ms`; } initScrollAnimations() { let lastScrollTop = 0; const navbar = document.querySelector('custom-navbar'); window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; // Gérer la navbar if (navbar) { if (scrollTop > 100) { navbar.setAttribute('scrolled', 'true'); } else { navbar.removeAttribute('scrolled'); } } // Animation au défilement const scrolled = scrollTop > 50; document.body.classList.toggle('scrolled', scrolled); lastScrollTop = scrollTop; }, { passive: true }); } initHoverEffects() { // Effets de survol pour les cartes document.addEventListener('mouseover', (e) => { const card = e.target.closest('.factor-card, .process-step, custom-card'); if (card && !card.classList.contains('hovering')) { card.classList.add('hovering'); this.addHoverEffect(card); } }); document.addEventListener('mouseout', (e) => { const card = e.target.closest('.factor-card, .process-step, custom-card'); if (card) { card.classList.remove('hovering'); } }); } addHoverEffect(element) { // Effet visuel subtil au survol element.style.transition = 'all 0.3s ease'; } } // ===== GESTIONNAIRE DE THÈME ===== class ThemeManager { constructor() { this.currentTheme = 'dark'; this.themes = { dark: { '--color-bg-primary': '#0A1929', '--color-bg-secondary': '#132F4C', '--color-text-primary': '#E3F2FD' }, light: { '--color-bg-primary': '#F5F7FA', '--color-bg-secondary': '#FFFFFF', '--color-text-primary': '#0A1929' } }; this.init(); } init() { this.loadTheme(); this.initThemeToggle(); } loadTheme() { const savedTheme = localStorage.getItem('theme') || 'dark'; this.setTheme(savedTheme); } setTheme(themeName) { if (!this.themes[themeName]) return; this.currentTheme = themeName; const theme = this.themes[themeName]; Object.entries(theme).forEach(([property, value]) => { document.documentElement.style.setProperty(property, value); }); localStorage.setItem('theme', themeName); document.dispatchEvent(new CustomEvent('themeChange', { detail: { theme: themeName } })); } toggleTheme() { const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark'; this.setTheme(newTheme); } initThemeToggle() { // Créer un bouton de bascule de thème si nécessaire const themeToggle = document.createElement('button'); themeToggle.className = 'theme-toggle'; themeToggle.innerHTML = ''; themeToggle.style.cssText = ` position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px; border-radius: 50%; background: var(--color-primary); color: white; border: none; cursor: pointer; z-index: 1000; box-shadow: var(--shadow-md); display: flex; align-items: center; justify-content: center; font-size: 1.2rem; transition: all 0.3s ease; `; themeToggle.addEventListener('click', () => this.toggleTheme()); document.body.appendChild(themeToggle); } } // ===== GESTIONNAIRE DE PERFORMANCE ===== class PerformanceManager { constructor() { this.init(); } init() { this.optimizeImages(); this.deferNonCriticalResources(); this.monitorPerformance(); } optimizeImages() { // Chargement différé des images const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } deferNonCriticalResources() { // Différer le chargement des polices non critiques const link = document.createElement('link'); link.rel = 'preload'; link.as = 'style'; link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Space+Grotesk:wght@300;400;500;600;700&display=swap'; link.onload = () => link.rel = 'stylesheet'; document.head.appendChild(link); } monitorPerformance() { // Surveiller les métriques de performance if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(`[Performance] ${entry.name}: ${entry.duration.toFixed(2)}ms`); } }); observer.observe({ entryTypes: ['measure', 'paint', 'largest-contentful-paint'] }); } } } // ===== INITIALISATION DE L'APPLICATION ===== class App { constructor() { this.state = new AppState(); this.animations = new AnimationManager(); this.theme = new ThemeManager(); this.performance = new PerformanceManager(); this.init(); } init() { this.initSmoothScrolling(); this.initBackToTop(); this.initPrintButton(); this.initCopyButtons(); this.initAnalytics(); } initSmoothScrolling() { // Scrolling fluide pour les ancres document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } initBackToTop() { // Bouton retour en haut const backToTop = document.createElement('button'); backToTop.className = 'back-to-top'; backToTop.innerHTML = ''; backToTop.style.cssText = ` position: fixed; bottom: 80px; right: 20px; width: 50px; height: 50px; border-radius: 50%; background: var(--color-primary); color: white; border: none; cursor: pointer; z-index: 999; opacity: 0; visibility: hidden; transition: all 0.3s ease; box-shadow: var(--shadow-md); display: flex; align-items: center; justify-content: center; font-size: 1.2rem; `; backToTop.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); document.body.appendChild(backToTop); // Afficher/masquer le bouton window.addEventListener('scroll', () => { if (window.scrollY > 300) { backToTop.style.opacity = '1'; backToTop.style.visibility = 'visible'; } else { backToTop.style.opacity = '0'; backToTop.style.visibility = 'hidden'; } }); } initPrintButton() { // Bouton d'impression const printButton = document.createElement('button'); printButton.className = 'print-button'; printButton.innerHTML = ' Imprimer cette page'; printButton.style.cssText = ` position: fixed; bottom: 140px; right: 20px; padding: 12px 20px; background: var(--color-secondary); color: white; border: none; border-radius: 25px; cursor: pointer; z-index: 999; box-shadow: var(--shadow-md); font-family: var(--font-primary); font-size: 0.9rem; transition: all 0.3s ease; `; printButton.addEventListener('click', () => { window.print(); }); printButton.addEventListener('mouseenter', () => { printButton.style.transform = 'translateY(-2px)'; printButton.style.boxShadow = 'var(--shadow-lg)'; }); printButton.addEventListener('mouseleave', () => { printButton.style.transform = 'translateY(0)'; printButton.style.boxShadow = 'var(--shadow-md)'; }); document.body.appendChild(printButton); } initCopyButtons() { // Boutons de copie pour le code et équations document.querySelectorAll('.equation-box, pre code').forEach(element => { const copyButton = document.createElement('button'); copyButton.className = 'copy-button'; copyButton.innerHTML = ''; copyButton.title = 'Copier'; copyButton.style.cssText = ` position: absolute; top: 10px; right: 10px; background: rgba(255, 255, 255, 0.1); border: 1px solid var(--color-border); color: var(--color-text-muted); border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 0.8rem; transition: all 0.2s ease; `; copyButton.addEventListener('click', async () => { const text = element.textContent || element.innerText; try { await navigator.clipboard.writeText(text); copyButton.innerHTML = ''; copyButton.style.color = 'var(--color-primary-light)'; setTimeout(() => { copyButton.innerHTML = ''; copyButton.style.color = 'var(--color-text-muted)'; }, 2000); } catch (err) { console.error('Erreur de copie:', err); } }); element.style.position = 'relative'; element.appendChild(copyButton); }); } initAnalytics() { // Analytics simple (peut être étendu) window.addEventListener('load', () => { console.log('Page complètement chargée'); const loadTime = window.performance.timing.domContentLoadedEventEnd - window.performance.timing.navigationStart; console.log(`Temps de chargement: ${loadTime}ms`); }); } } // ===== INITIALISATION AU CHARGEMENT ===== document.addEventListener('DOMContentLoaded', () => { // Initialiser l'application window.app = new App(); // Vérifier la compatibilité Web Components if (!('customElements' in window)) { console.error('Web Components non supportés par ce navigateur'); return; } // Initialiser Feather Icons if (typeof feather !== 'undefined') { feather.replace(); } // Initialiser Font Awesome if (typeof FontAwesomeKitConfig !== 'undefined') { console.log('Font Awesome chargé'); } // Afficher un message de bienvenue console.log('%c🌱 Photosynthèse Éclatée 🌱', 'color: #4CAF50; font-size: 18px; font-weight: bold;'); console.log('Site éducatif sur la photosynthèse - Chargé avec succès'); });