Spaces:
Running
Running
| // ===== 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 = '<i class="fas fa-moon"></i>'; | |
| 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 = '<i class="fas fa-chevron-up"></i>'; | |
| 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 = '<i class="fas fa-print"></i> 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 = '<i class="far fa-copy"></i>'; | |
| 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 = '<i class="fas fa-check"></i>'; | |
| copyButton.style.color = 'var(--color-primary-light)'; | |
| setTimeout(() => { | |
| copyButton.innerHTML = '<i class="far fa-copy"></i>'; | |
| 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'); | |
| }); |