1// Fonctions utilitaires 2const utils = { 3 debounce(func, wait) { 4 let timeout; 5 return function executedFunction(...args) { 6 const later = () => { 7 clearTimeout(timeout); 8 func(...args); 9 }; 10 clearTimeout(timeout); 11 timeout = setTimeout(later, wait); 12 }; 13 } 14}; 15 16// Gestionnaire du site 17class SiteManager { 18 constructor() { 19 // Attendre que le DOM soit complètement chargé 20 if (document.readyState === 'loading') { 21 document.addEventListener('DOMContentLoaded', () => this.init()); 22 } else { 23 this.init(); 24 } 25 } 26 27 init() { 28 console.log('ð Initialisation du SiteManager...'); 29 this.setupNavigation(); 30 this.setupHeroSlider(); 31 this.setupSearch(); 32 this.setupVisitorsCounter(); 33 this.setupNewsletter(); 34 } 35 36 // Navigation mobile CORRIGÃE 37 setupNavigation() { 38 console.log('ð§ Configuration de la navigation...'); 39 40 // Utiliser setTimeout pour s'assurer que le DOM est prêt 41 setTimeout(() => { 42 const menuToggle = document.querySelector('.menu-toggle'); 43 const navMenu = document.querySelector('.nav-menu'); 44 const searchToggle = document.querySelector('.search-toggle'); 45 const searchBar = document.querySelector('.search-bar'); 46 const searchClose = document.querySelector('.search-close'); 47 48 console.log('Menu toggle trouvé:', menuToggle); 49 console.log('Nav menu trouvé:', navMenu); 50 51 if (!menuToggle || !navMenu) { 52 console.error('â Ãléments de navigation non trouvés!'); 53 this.createEmergencyMenu(); 54 return; 55 } 56 57 // Créer l'overlay 58 let navOverlay = document.querySelector('.nav-overlay'); 59 if (!navOverlay) { 60 navOverlay = document.createElement('div'); 61 navOverlay.className = 'nav-overlay'; 62 document.body.appendChild(navOverlay); 63 } 64 65 // Fonction pour fermer le menu mobile 66 const closeMobileMenu = () => { 67 navMenu.classList.remove('active'); 68 menuToggle.classList.remove('active'); 69 navOverlay.classList.remove('active'); 70 document.body.style.overflow = ''; 71 72 // Fermer tous les sous-menus 73 document.querySelectorAll('.nav-menu li.active').forEach(item => { 74 item.classList.remove('active'); 75 }); 76 }; 77 78 // Toggle menu mobile 79 menuToggle.addEventListener('click', (e) => { 80 e.stopPropagation(); 81 const isActive = navMenu.classList.contains('active'); 82 83 if (!isActive) { 84 navMenu.classList.add('active'); 85 menuToggle.classList.add('active'); 86 navOverlay.classList.add('active'); 87 document.body.style.overflow = 'hidden'; 88 } else { 89 closeMobileMenu(); 90 } 91 }); 92 93 // Fermer le menu en cliquant sur l'overlay 94 navOverlay.addEventListener('click', closeMobileMenu); 95 96 // Gestion des sous-menus mobiles 97 const navItems = navMenu.querySelectorAll('li'); 98 navItems.forEach(item => { 99 const submenu = item.querySelector('.submenu'); 100 if (submenu) { 101 const link = item.querySelector('a'); 102 link.addEventListener('click', (e) => { 103 if (window.innerWidth <= 768) { 104 e.preventDefault(); 105 e.stopPropagation(); 106 item.classList.toggle('active'); 107 } 108 }); 109 } 110 }); 111 112 // Fermer le menu en cliquant sur un lien 113 navMenu.addEventListener('click', (e) => { 114 if (e.target.tagName === 'A' && window.innerWidth <= 768) { 115 const parentLi = e.target.closest('li'); 116 if (!parentLi || !parentLi.querySelector('.submenu')) { 117 setTimeout(closeMobileMenu, 100); 118 } 119 } 120 }); 121 122 // Recherche 123 if (searchToggle) { 124 searchToggle.addEventListener('click', () => { 125 searchBar.classList.add('active'); 126 const input = searchBar.querySelector('input'); 127 if (input) input.focus(); 128 closeMobileMenu(); 129 }); 130 } 131 132 if (searchClose) { 133 searchClose.addEventListener('click', () => { 134 searchBar.classList.remove('active'); 135 }); 136 } 137 138 // Fermer avec Ãchap 139 document.addEventListener('keydown', (e) => { 140 if (e.key === 'Escape') { 141 closeMobileMenu(); 142 if (searchBar) searchBar.classList.remove('active'); 143 } 144 }); 145 146 // Redimensionnement 147 window.addEventListener('resize', () => { 148 if (window.innerWidth > 768) { 149 closeMobileMenu(); 150 } 151 }); 152 153 console.log('â Navigation configurée avec succès!'); 154 155 }, 100); // Délai pour s'assurer que le DOM est prêt 156 } 157 158 // Créer un menu d'urgence si le menu principal est cassé 159 createEmergencyMenu() { 160 console.log('ð Création du menu d\'urgence...'); 161 162 const existingEmergencyMenu = document.querySelector('.emergency-mobile-menu'); 163 if (existingEmergencyMenu) return; 164 165 const emergencyHTML = ` 166 <div class="emergency-mobile-menu" style="display: none;"> 167 <button class="emergency-menu-toggle" style=" 168 position: fixed; 169 top: 15px; 170 right: 15px; 171 z-index: 10001; 172 background: #007a3d; 173 border: 2px solid white; 174 border-radius: 5px; 175 padding: 10px; 176 cursor: pointer; 177 display: flex; 178 flex-direction: column; 179 gap: 4px; 180 ">
181 <span style="display: block; width: 25px; height: 3px; background: white;"></span> 182 <span style="display: block; width: 25px; height: 3px; background: white;"></span> 183 <span style="display: block; width: 25px; height: 3px; background: white;"></span> 184 </button> 185 186 <div class="emergency-nav-menu" style=" 187 position: fixed; 188 top: 0; 189 right: -300px; 190 width: 280px; 191 height: 100vh; 192 background: white; 193 box-shadow: -2px 0 10px rgba(0,0,0,0.3); 194 padding: 80px 20px 20px; 195 transition: 0.3s; 196 z-index: 10000; 197 overflow-y: auto; 198 "> 199 <a href="/" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">Accueil</a> 200 <a href="/societe.html" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">Actualités</a> 201 <a href="/le-cameroun.html" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">Le Cameroun</a> 202 <a href="/photos.html" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">Photos</a> 203 <a href="/medias/videos.html" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">Médias</a> 204 <a href="/selection-tv.html" style="display: block; padding: 15px; color: #333; border-bottom: 1px solid #eee;">TV en Direct</a> 205 </div> 206 207 <div class="emergency-overlay" style=" 208 display: none; 209 position: fixed; 210 top: 0; 211 left: 0; 212 width: 100%; 213 height: 100%; 214 background: rgba(0,0,0,0.5); 215 z-index: 9999; 216 "></div> 217 </div> 218 `; 219 220 document.body.insertAdjacentHTML('beforeend', emergencyHTML); 221 222 const emergencyMenu = document.querySelector('.emergency-mobile-menu'); 223 const emergencyToggle = document.querySelector('.emergency-menu-toggle'); 224 const emergencyNav = document.querySelector('.emergency-nav-menu'); 225 const emergencyOverlay = document.querySelector('.emergency-overlay'); 226 227 // Afficher seulement sur mobile 228 if (window.innerWidth <= 768) { 229 emergencyMenu.style.display = 'block'; 230 } 231 232 // Gestion du toggle 233 emergencyToggle.addEventListener('click', () => { 234 const isActive = emergencyNav.style.right === '0px'; 235 236 if (!isActive) { 237 emergencyNav.style.right = '0px'; 238 emergencyOverlay.style.display = 'block'; 239 document.body.style.overflow = 'hidden'; 240 } else { 241 emergencyNav.style.right = '-300px'; 242 emergencyOverlay.style.display = 'none'; 243 document.body.style.overflow = ''; 244 } 245 }); 246 247 // Fermer avec l'overlay 248 emergencyOverlay.addEventListener('click', () => { 249 emergencyNav.style.right = '-300px'; 250 emergencyOverlay.style.display = 'none'; 251 document.body.style.overflow = ''; 252 }); 253 254 // Fermer en cliquant sur les liens 255 emergencyNav.addEventListener('click', (e) => { 256 if (e.target.tagName === 'A') { 257 setTimeout(() => { 258 emergencyNav.style.right = '-300px'; 259 emergencyOverlay.style.display = 'none'; 260 document.body.style.overflow = ''; 261 }, 100); 262 } 263 }); 264 265 // Adaptation au redimensionnement 266 window.addEventListener('resize', () => { 267 if (window.innerWidth > 768) { 268 emergencyMenu.style.display = 'none'; 269 emergencyNav.style.right = '-300px'; 270 emergencyOverlay.style.display = 'none'; 271 } else { 272 emergencyMenu.style.display = 'block'; 273 } 274 }); 275 276 console.log('â Menu d\'urgence créé!'); 277 } 278 279 // Slider hero 280 setupHeroSlider() { 281 const heroSlides = document.querySelectorAll('.hero-slide'); 282 const prevButton = document.querySelector('.hero-prev'); 283 const nextButton = document.querySelector('.hero-next'); 284 const indicators = document.querySelectorAll('.hero-indicator'); 285 286 if (!heroSlides || heroSlides.length <= 1) return; 287 288 let currentSlide = 0; 289 const totalSlides = heroSlides.length; 290 let autoSlideInterval; 291 292 const showSlide = (index) => {
293 heroSlides.forEach(slide => slide.classList.remove('active')); 294 indicators.forEach(indicator => indicator.classList.remove('active')); 295 296 heroSlides[index].classList.add('active'); 297 if (indicators[index]) { 298 indicators[index].classList.add('active'); 299 } 300 301 currentSlide = index; 302 }; 303 304 const nextSlide = () => { 305 let nextIndex = currentSlide + 1; 306 if (nextIndex >= totalSlides) nextIndex = 0; 307 showSlide(nextIndex); 308 }; 309 310 const prevSlide = () => { 311 let prevIndex = currentSlide - 1; 312 if (prevIndex < 0) prevIndex = totalSlides - 1; 313 showSlide(prevIndex); 314 }; 315 316 if (nextButton) { 317 nextButton.addEventListener('click', (e) => { 318 e.preventDefault(); 319 nextSlide(); 320 this.resetAutoSlide(); 321 }); 322 } 323 324 if (prevButton) { 325 prevButton.addEventListener('click', (e) => { 326 e.preventDefault(); 327 prevSlide(); 328 this.resetAutoSlide(); 329 }); 330 } 331 332 indicators.forEach((indicator, index) => { 333 indicator.addEventListener('click', (e) => { 334 e.preventDefault(); 335 showSlide(index); 336 this.resetAutoSlide(); 337 }); 338 }); 339 340 this.startAutoSlide = () => { 341 autoSlideInterval = setInterval(nextSlide, 5000); 342 }; 343 344 this.stopAutoSlide = () => { 345 if (autoSlideInterval) { 346 clearInterval(autoSlideInterval); 347 } 348 }; 349 350 this.resetAutoSlide = () => { 351 this.stopAutoSlide(); 352 this.startAutoSlide(); 353 }; 354 355 this.startAutoSlide(); 356 357 const heroSlider = document.querySelector('.hero-slider'); 358 if (heroSlider) { 359 heroSlider.addEventListener('mouseenter', this.stopAutoSlide); 360 heroSlider.addEventListener('mouseleave', this.startAutoSlide); 361 } 362 363 document.addEventListener('keydown', (e) => { 364 if (e.key === 'ArrowLeft') { 365 prevSlide(); 366 this.resetAutoSlide(); 367 } else if (e.key === 'ArrowRight') { 368 nextSlide(); 369 this.resetAutoSlide(); 370 } 371 }); 372 373 document.addEventListener('visibilitychange', () => { 374 if (document.hidden) { 375 this.stopAutoSlide(); 376 } else { 377 this.startAutoSlide(); 378 } 379 }); 380 381 showSlide(0); 382 } 383 384 // Recherche 385 setupSearch() { 386 const searchForm = document.querySelector('.search-form'); 387 if (!searchForm) return; 388 389 searchForm.addEventListener('submit', (e) => { 390 e.preventDefault(); 391 const query = searchForm.querySelector('input').value.trim(); 392 if (query) { 393 window.location.href = `/recherche.html?q=${encodeURIComponent(query)}`; 394 } 395 }); 396 } 397 398 // Compteur de visiteurs 399 setupVisitorsCounter() { 400 this.updateVisitorsCount(); 401 setInterval(() => this.updateVisitorsCount(), 30000); 402 } 403 404 async updateVisitorsCount() { 405 try { 406 const response = await fetch('/api/visitors.php'); 407 const data = await response.json(); 408 409 const counters = document.querySelectorAll('#visitors-count'); 410 counters.forEach(counter => { 411 counter.textContent = data.count; 412 }); 413 } catch (error) { 414 console.error('Erreur mise à jour visiteurs:', error); 415 } 416 } 417 418 // NEWSLETTER - Fonction corrigée 419 setupNewsletter() { 420 console.log('ð§ Initialisation de la newsletter...'); 421 422 const newsletterForms = document.querySelectorAll('.newsletter-form, .sidebar-newsletter'); 423
424 newsletterForms.forEach(form => { 425 form.addEventListener('submit', async (e) => { 426 e.preventDefault(); 427 console.log('ð¯ Formulaire newsletter soumis'); 428 429 const emailInput = form.querySelector('input[type="email"]'); 430 const submitButton = form.querySelector('button[type="submit"]'); 431 const messageContainer = document.getElementById('newsletter-message'); 432 433 if (!emailInput || !submitButton) return; 434 435 const email = emailInput.value.trim(); 436 437 // Validation email 438 if (!this.isValidEmail(email)) { 439 this.showNewsletterMessage('â Veuillez entrer une adresse email valide.', 'error', messageContainer); 440 return; 441 } 442 443 // Ãtat de chargement 444 this.setButtonLoading(submitButton, true); 445 446 try { 447 const response = await fetch('/newsletter.php', { 448 method: 'POST', 449 headers: { 450 'Content-Type': 'application/x-www-form-urlencoded', 451 }, 452 body: `email=${encodeURIComponent(email)}&action=subscribe` 453 }); 454 455 const data = await response.json(); 456 console.log('ð¥ Réponse newsletter:', data); 457 458 // CORRECTION : Décoder les caractères Unicode 459 let displayMessage = data.message; 460 461 // Remplacer les séquences Unicode 462 displayMessage = displayMessage 463 .replace(/\\u00e9/g, 'é') 464 .replace(/\\u00e0/g, 'à ') 465 .replace(/\\u00e8/g, 'è') 466 .replace(/\\u00ea/g, 'ê') 467 .replace(/\\u00eb/g, 'ë') 468 .replace(/\\u00ee/g, 'î') 469 .replace(/\\u00ef/g, 'ï') 470 .replace(/\\u00f4/g, 'ô') 471 .replace(/\\u00f6/g, 'ö') 472 .replace(/\\u00f9/g, 'ù') 473 .replace(/\\u00fb/g, 'û') 474 .replace(/\\u00fc/g, 'ü') 475 .replace(/\\u00e7/g, 'ç') 476 .replace(/\\\\/g, ''); 477 478 if (data.success) { 479 this.showNewsletterMessage('â ' + displayMessage, 'success', messageContainer); 480 emailInput.value = ''; // Vider le champ 481 } else { 482 this.showNewsletterMessage('â ' + displayMessage, 'error', messageContainer); 483 } 484 485 } catch (error) { 486 console.error('ð¥ Erreur newsletter:', error); 487 this.showNewsletterMessage('â Une erreur est survenue. Veuillez réessayer.', 'error', messageContainer); 488 } finally { 489 this.setButtonLoading(submitButton, false); 490 } 491 }); 492 }); 493 } 494 495 // Validation email 496 isValidEmail(email) { 497 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 498 return emailRegex.test(email); 499 } 500 501 // Affichage des messages newsletter 502 showNewsletterMessage(message, type, container) { 503 if (!container) { 504 console.error('â Container message newsletter non trouvé'); 505 // Créer un container si il n'existe pas 506 const forms = document.querySelectorAll('.newsletter-form, .sidebar-newsletter'); 507 forms.forEach(form => { 508 if (!form.nextElementSibling || !form.nextElementSibling.id.includes('newsletter-message')) { 509 const newContainer = document.createElement('div'); 510 newContainer.id = 'newsletter-message'; 511 form.parentNode.insertBefore(newContainer, form.nextSibling); 512 container = newContainer; 513 } 514 }); 515 } 516 517 if (!container) return; 518 519 container.innerHTML = ` 520 <div class="newsletter-message newsletter-${type}"> 521 ${message} 522 </div> 523 `; 524 525 console.log('ð Message newsletter affiché:', message); 526 527 // Auto-suppression après 8 secondes 528 setTimeout(() => { 529 if (container && container.innerHTML.includes('newsletter-message')) { 530 container.innerHTML = ''; 531 console.log('ð§¹ Message newsletter nettoyé'); 532 } 533 }, 8000); 534 } 535 536 // Gestion de l'état du bouton newsletter 537 setButtonLoading(button, isLoading) { 538 const originalHTML = button.innerHTML; 539 540 if (isLoading) { 541 button.disabled = true; 542 button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Envoi...'; 543 button.style.opacity = '0.7'; 544 } else { 545 button.disabled = false;
546 button.innerHTML = originalHTML; 547 button.style.opacity = '1'; 548 } 549 } 550} 551 552// Initialisation 553console.log('ð Script main.js chargé, initialisation...'); 554window.siteManager = new SiteManager(); 555 556// Animation pour les brèves 557document.addEventListener('DOMContentLoaded', function() { 558 setTimeout(() => { 559 const breveItems = document.querySelectorAll('.breve-item'); 560 if (!breveItems || breveItems.length <= 1) return; 561 562 let currentBreve = 0; 563 const totalBreves = breveItems.length; 564 565 function showBreve(index) { 566 breveItems.forEach(item => item.classList.remove('active')); 567 breveItems[index].classList.add('active'); 568 currentBreve = index; 569 } 570 571 function nextBreve() { 572 let nextIndex = currentBreve + 1; 573 if (nextIndex >= totalBreves) nextIndex = 0; 574 showBreve(nextIndex); 575 } 576 577 setInterval(nextBreve, 8000); 578 }, 500); 579}); 580 581// Micro-interactions 582class MicroInteractions { 583 static init() { 584 // Hover sur les cartes 585 document.querySelectorAll('.news-card, .media-card').forEach(card => { 586 card.addEventListener('mouseenter', () => { 587 card.style.transform = 'translateY(-8px) scale(1.02)'; 588 }); 589 card.addEventListener('mouseleave', () => { 590 card.style.transform = 'translateY(0) scale(1)'; 591 }); 592 }); 593 } 594} 595 596// Analytics utilisateur 597class UserAnalytics { 598 static trackEvent(category, action, label) { 599 if (typeof gtag !== 'undefined') { 600 gtag('event', action, { 601 'event_category': category, 602 'event_label': label 603 }); 604 } 605 } 606 607 static trackReadTime(articleId) { 608 const startTime = Date.now(); 609 window.addEventListener('beforeunload', () => { 610 const readTime = Math.round((Date.now() - startTime) / 1000); 611 this.trackEvent('Engagement', 'article_read_time', articleId); 612 }); 613 } 614} 615 616// Initialiser les micro-interactions 617document.addEventListener('DOMContentLoaded', () => { 618 MicroInteractions.init(); 619});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.