/** * Flying Book - Presentation JavaScript * Gestion de la présentation interactive */ document.addEventListener('DOMContentLoaded', function() { initializePresentation(); }); /** * Initialisation de la présentation */ function initializePresentation() { // Charger la présentation loadPresentation(); // Initialiser les contrôles initializeControls(); // Initialiser la navigation clavier initializeKeyboardNavigation(); // Initialiser le mode plein écran initializeFullscreen(); // Initialiser les paramètres initializeSettings(); // Démarrer le mode automatique si configuré initializeAutoPlay(); // Précharger les médias preloadMedia(); // Enregistrer les analytics recordSlideView(currentSlideIndex); } /** * Charger la présentation */ function loadPresentation() { const loadingScreen = document.getElementById('loadingScreen'); const loadingBar = document.getElementById('loadingBar'); // Simuler le chargement let progress = 0; const interval = setInterval(() => { progress += Math.random() * 15; if (progress >= 100) { progress = 100; clearInterval(interval); setTimeout(() => { loadingScreen.classList.add('hidden'); startPresentation(); }, 500); } loadingBar.style.width = progress + '%'; }, 100); } /** * Démarrer la présentation */ function startPresentation() { // Afficher la première slide showSlide(0); // Initialiser la barre de progression updateProgress(); // Activer la première miniature updateThumbnails(); // Démarrer l'auto-advance si configuré if (autoPlayEnabled) { startAutoAdvance(); } // Analytics if (!PRESENTATION_CONFIG.isPreview) { recordPresentationStart(); } } /** * Afficher une slide spécifique */ function showSlide(index, direction = 'next') { if (index < 0 || index >= PRESENTATION_CONFIG.totalSlides) { return; } const slides = document.querySelectorAll('.slide'); const currentSlide = slides[currentSlideIndex]; const nextSlide = slides[index]; // Arrêter l'auto-advance de la slide courante stopAutoAdvance(); // Nettoyer les classes de transition slides.forEach(slide => { slide.classList.remove('active', 'prev', 'next'); }); // Appliquer les classes de transition if (direction === 'next') { currentSlide?.classList.add('prev'); nextSlide.classList.add('next'); } else { currentSlide?.classList.add('next'); nextSlide.classList.add('prev'); } // Petite pause pour permettre la transition CSS setTimeout(() => { nextSlide.classList.remove('prev', 'next'); nextSlide.classList.add('active'); // Charger le média si nécessaire loadSlideMedia(nextSlide); // Mettre à jour l'index currentSlideIndex = index; // Mettre à jour l'interface updateProgress(); updateThumbnails(); updateNavigationButtons(); // Démarrer l'auto-advance pour cette slide if (autoPlayEnabled) { startAutoAdvance(); } // Analytics recordSlideView(index); }, 50); } /** * Charger les médias d'une slide */ function loadSlideMedia(slide) { // Charger les images avec data-src const images = slide.querySelectorAll('img[data-src]'); images.forEach(img => { img.src = img.dataset.src; img.removeAttribute('data-src'); }); // Charger les vidéos avec data-src const videos = slide.querySelectorAll('video[data-src]'); videos.forEach(video => { const source = video.querySelector('source'); if (source) { source.src = video.dataset.src; video.load(); } video.removeAttribute('data-src'); }); // Charger les audios avec data-src const audios = slide.querySelectorAll('audio[data-src]'); audios.forEach(audio => { const source = audio.querySelector('source'); if (source) { source.src = audio.dataset.src; audio.load(); } audio.removeAttribute('data-src'); }); } /** * Navigation vers la slide suivante */ function nextSlide() { if (currentSlideIndex < PRESENTATION_CONFIG.totalSlides - 1) { showSlide(currentSlideIndex + 1, 'next'); } } /** * Navigation vers la slide précédente */ function previousSlide() { if (currentSlideIndex > 0) { showSlide(currentSlideIndex - 1, 'prev'); } } /** * Aller à une slide spécifique */ function goToSlide(index) { const direction = index > currentSlideIndex ? 'next' : 'prev'; showSlide(index, direction); } /** * Mettre à jour la barre de progression */ function updateProgress() { const progressFill = document.getElementById('progressFill'); const currentSlideElement = document.getElementById('currentSlide'); const progress = ((currentSlideIndex + 1) / PRESENTATION_CONFIG.totalSlides) * 100; progressFill.style.width = progress + '%'; currentSlideElement.textContent = currentSlideIndex + 1; } /** * Mettre à jour les miniatures */ function updateThumbnails() { const thumbnails = document.querySelectorAll('.thumbnail'); thumbnails.forEach((thumb, index) => { thumb.classList.toggle('active', index === currentSlideIndex); }); // Faire défiler vers la miniature active si nécessaire const activeThumbnail = document.querySelector('.thumbnail.active'); if (activeThumbnail) { activeThumbnail.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } } /** * Mettre à jour les boutons de navigation */ function updateNavigationButtons() { const prevBtn = document.querySelector('.nav-prev'); const nextBtn = document.querySelector('.nav-next'); if (prevBtn) { prevBtn.disabled = currentSlideIndex === 0; } if (nextBtn) { nextBtn.disabled = currentSlideIndex === PRESENTATION_CONFIG.totalSlides - 1; } } /** * Initialiser les contrôles */ function initializeControls() { // Gestion des clics sur les slides pour avancer const slidesWrapper = document.getElementById('slidesWrapper'); if (slidesWrapper) { slidesWrapper.addEventListener('click', function(e) { // Éviter les clics sur les contrôles vidéo/audio if (!e.target.closest('video, audio, .slide-audio')) { nextSlide(); } }); } // Gestion des swipes sur mobile initializeTouchNavigation(); } /** * Initialiser la navigation tactile */ function initializeTouchNavigation() { let startX = null; let startY = null; const threshold = 50; // Distance minimale pour déclencher un swipe const container = document.getElementById('presentationContainer'); container.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; startY = e.touches[0].clientY; }, { passive: true }); container.addEventListener('touchend', function(e) { if (startX === null || startY === null) return; const endX = e.changedTouches[0].clientX; const endY = e.changedTouches[0].clientY; const deltaX = endX - startX; const deltaY = endY - startY; // Vérifier que c'est bien un swipe horizontal if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > threshold) { if (deltaX > 0) { // Swipe vers la droite - slide précédente previousSlide(); } else { // Swipe vers la gauche - slide suivante nextSlide(); } } startX = null; startY = null; }, { passive: true }); } /** * Initialiser la navigation clavier */ function initializeKeyboardNavigation() { document.addEventListener('keydown', function(e) { // Ignorer si on est dans un input ou textarea if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } switch (e.key) { case 'ArrowRight': case ' ': case 'PageDown': e.preventDefault(); nextSlide(); break; case 'ArrowLeft': case 'PageUp': e.preventDefault(); previousSlide(); break; case 'Home': e.preventDefault(); goToSlide(0); break; case 'End': e.preventDefault(); goToSlide(PRESENTATION_CONFIG.totalSlides - 1); break; case 'F11': case 'f': e.preventDefault(); toggleFullscreen(); break; case 'Escape': if (isFullscreen) { exitFullscreen(); } break; case 's': e.preventDefault(); toggleSettings(); break; case 'h': e.preventDefault(); toggleHeaderAndThumbnails(); break; } }); } /** * Initialiser le mode plein écran */ function initializeFullscreen() { document.addEventListener('fullscreenchange', updateFullscreenState); document.addEventListener('webkitfullscreenchange', updateFullscreenState); document.addEventListener('mozfullscreenchange', updateFullscreenState); document.addEventListener('MSFullscreenChange', updateFullscreenState); } /** * Basculer le mode plein écran */ function toggleFullscreen() { if (!isFullscreen) { enterFullscreen(); } else { exitFullscreen(); } } /** * Entrer en mode plein écran */ function enterFullscreen() { const element = document.documentElement; if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } } /** * Sortir du mode plein écran */ function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } } /** * Mettre à jour l'état du mode plein écran */ function updateFullscreenState() { isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement); document.body.classList.toggle('fullscreen', isFullscreen); // Mettre à jour l'icône du bouton const fullscreenBtn = document.querySelector('[onclick="toggleFullscreen()"]'); if (fullscreenBtn) { const icon = fullscreenBtn.querySelector('i'); icon.className = isFullscreen ? 'fas fa-compress' : 'fas fa-expand'; } } /** * Basculer l'affichage du header et des miniatures */ function toggleHeaderAndThumbnails() { const header = document.getElementById('presentationHeader'); const thumbnails = document.getElementById('slideThumbnails'); header.classList.toggle('hidden'); if (thumbnails) { thumbnails.classList.toggle('hidden'); } } /** * Initialiser l'auto-play */ function initializeAutoPlay() { // L'auto-play sera géré slide par slide dans startAutoAdvance() } /** * Démarrer l'avancement automatique pour la slide courante */ function startAutoAdvance() { stopAutoAdvance(); // Nettoyer le timer précédent const currentSlide = document.querySelector('.slide.active'); if (!currentSlide) return; const autoDuration = parseInt(currentSlide.dataset.autoDuration) || 0; if (autoDuration > 0 && autoPlayEnabled) { // Animer la barre de progression const progressBar = currentSlide.querySelector('.auto-progress-bar'); if (progressBar) { progressBar.style.transitionDuration = autoDuration + 's'; progressBar.style.width = '100%'; } // Programmer l'avancement autoAdvanceTimer = setTimeout(() => { nextSlide(); }, autoDuration * 1000); } } /** * Arrêter l'avancement automatique */ function stopAutoAdvance() { if (autoAdvanceTimer) { clearTimeout(autoAdvanceTimer); autoAdvanceTimer = null; } // Réinitialiser la barre de progression const progressBars = document.querySelectorAll('.auto-progress-bar'); progressBars.forEach(bar => { bar.style.transitionDuration = '0s'; bar.style.width = '0%'; }); } /** * Gestionnaire de fin de vidéo */ function onVideoEnded() { if (autoPlayEnabled) { setTimeout(() => { nextSlide(); }, 1000); // Petite pause avant d'avancer } } /** * Précharger les médias des slides suivantes */ function preloadMedia() { const preloadCount = 3; // Précharger 3 slides à l'avance for (let i = 1; i <= preloadCount && (currentSlideIndex + i) < PRESENTATION_CONFIG.totalSlides; i++) { const slides = document.querySelectorAll('.slide'); const slide = slides[currentSlideIndex + i]; if (slide) { // Précharger les images const images = slide.querySelectorAll('img[data-src]'); images.forEach(img => { const tempImg = new Image(); tempImg.src = img.dataset.src; }); // Précharger les vidéos (metadata seulement) const videos = slide.querySelectorAll('video[data-src]'); videos.forEach(video => { video.preload = 'metadata'; }); } } } /** * Initialiser les paramètres */ function initializeSettings() { // Charger les paramètres sauvegardés loadSettings(); // Écouter les changements const settings = document.querySelectorAll('#settingsPanel input, #settingsPanel select'); settings.forEach(setting => { setting.addEventListener('change', function() { updateSetting(this.id, this.type === 'checkbox' ? this.checked : this.value); }); }); // Mettre à jour l'affichage des valeurs de range const rangeInputs = document.querySelectorAll('#settingsPanel input[type="range"]'); rangeInputs.forEach(range => { range.addEventListener('input', function() { const valueSpan = this.nextElementSibling; if (valueSpan && valueSpan.classList.contains('range-value')) { valueSpan.textContent = this.value + 'ms'; } }); }); } /** * Basculer le panneau de paramètres */ function toggleSettings() { const panel = document.getElementById('settingsPanel'); panel.classList.toggle('open'); } /** * Mettre à jour un paramètre */ function updateSetting(settingId, value) { switch (settingId) { case 'autoPlayVideos': // Mise à jour des paramètres vidéo const videos = document.querySelectorAll('video'); videos.forEach(video => { if (value) { video.setAttribute('autoplay', ''); } else { video.removeAttribute('autoplay'); } }); break; case 'showThumbnails': const thumbnails = document.getElementById('slideThumbnails'); if (thumbnails) { thumbnails.style.display = value ? 'block' : 'none'; } break; case 'autoAdvance': autoPlayEnabled = value; if (value) { startAutoAdvance(); } else { stopAutoAdvance(); } break; case 'transitionSpeed': document.documentElement.style.setProperty('--transition-speed', value + 'ms'); break; case 'imageQuality': // Peut être implémenté pour changer la qualité des images break; } // Sauvegarder les paramètres saveSettings(); } /** * Charger les paramètres */ function loadSettings() { try { const saved = localStorage.getItem('flying_book_presentation_settings'); if (saved) { const settings = JSON.parse(saved); Object.keys(settings).forEach(key => { const element = document.getElementById(key); if (element) { if (element.type === 'checkbox') { element.checked = settings[key]; } else { element.value = settings[key]; } // Appliquer le paramètre updateSetting(key, settings[key]); } }); } } catch (e) { console.warn('Erreur lors du chargement des paramètres:', e); } } /** * Sauvegarder les paramètres */ function saveSettings() { try { const settings = {}; const elements = document.querySelectorAll('#settingsPanel input, #settingsPanel select'); elements.forEach(element => { if (element.id) { settings[element.id] = element.type === 'checkbox' ? element.checked : element.value; } }); localStorage.setItem('flying_book_presentation_settings', JSON.stringify(settings)); } catch (e) { console.warn('Erreur lors de la sauvegarde des paramètres:', e); } } /** * Réinitialiser les paramètres */ function resetSettings() { localStorage.removeItem('flying_book_presentation_settings'); location.reload(); } /** * Partager la présentation */ function sharePresentation() { const modal = document.getElementById('shareModal'); modal.classList.add('show'); } /** * Fermer la modal de partage */ function closeShareModal() { const modal = document.getElementById('shareModal'); modal.classList.remove('show'); } /** * Copier l'URL de partage */ function copyShareUrl() { const input = document.getElementById('shareUrl'); input.select(); document.execCommand('copy'); // Feedback visuel const button = event.target.closest('button'); const originalText = button.innerHTML; button.innerHTML = ''; button.style.color = 'var(--success-color)'; setTimeout(() => { button.innerHTML = originalText; button.style.color = ''; }, 2000); } /** * Partager sur Facebook */ function shareOnFacebook() { const url = encodeURIComponent(document.getElementById('shareUrl').value); const title = encodeURIComponent(document.title); window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank', 'width=600,height=400'); } /** * Partager sur Twitter */ function shareOnTwitter() { const url = encodeURIComponent(document.getElementById('shareUrl').value); const title = encodeURIComponent(document.title); window.open(`https://twitter.com/intent/tweet?url=${url}&text=${title}`, '_blank', 'width=600,height=400'); } /** * Partager sur LinkedIn */ function shareOnLinkedIn() { const url = encodeURIComponent(document.getElementById('shareUrl').value); const title = encodeURIComponent(document.title); window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${url}`, '_blank', 'width=600,height=400'); } /** * Partager par email */ function shareByEmail() { const url = document.getElementById('shareUrl').value; const title = document.title; const subject = encodeURIComponent(`Découvrez cette présentation: ${title}`); const body = encodeURIComponent(`Bonjour,\n\nJe vous invite à découvrir cette présentation interactive:\n\n${url}\n\nCordialement`); window.location.href = `mailto:?subject=${subject}&body=${body}`; } /** * Enregistrer le démarrage de la présentation */ function recordPresentationStart() { if (PRESENTATION_CONFIG.isPreview) return; fetch('../api/analytics.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'presentation_start', project_id: PRESENTATION_CONFIG.projectId, timestamp: Date.now() }) }).catch(e => console.warn('Analytics error:', e)); } /** * Enregistrer la vue d'une slide */ function recordSlideView(slideIndex) { if (PRESENTATION_CONFIG.isPreview) return; fetch('../api/analytics.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'slide_view', project_id: PRESENTATION_CONFIG.projectId, slide_index: slideIndex, timestamp: Date.now() }) }).catch(e => console.warn('Analytics error:', e)); } /** * Fermer les modales en cliquant sur l'overlay */ document.addEventListener('click', function(e) { if (e.target.classList.contains('modal')) { e.target.classList.remove('show'); } }); /** * Gestion de la visibilité de la page (pour analytics) */ document.addEventListener('visibilitychange', function() { if (document.hidden) { stopAutoAdvance(); } else if (autoPlayEnabled) { startAutoAdvance(); } }); /** * Nettoyage avant fermeture */ window.addEventListener('beforeunload', function() { stopAutoAdvance(); // Enregistrer le temps total de présentation if (!PRESENTATION_CONFIG.isPreview) { const totalTime = Date.now() - presentationStartTime; navigator.sendBeacon('../api/analytics.php', JSON.stringify({ action: 'presentation_end', project_id: PRESENTATION_CONFIG.projectId, total_time: totalTime, slides_viewed: currentSlideIndex + 1 })); } }); /** * Utilitaires */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Optimisation des performances const optimizedNextSlide = debounce(nextSlide, 300); const optimizedPrevSlide = debounce(previousSlide, 300); // Remplacer les fonctions pour éviter les appels trop rapides window.nextSlide = optimizedNextSlide; window.previousSlide = optimizedPrevSlide;