/* * Page d'accueil - gestion capitaine + leaderboard */ (function () { 'use strict'; const MAX_SCORES_DISPLAY = 5; // refs DOM (on les cache au début) const DOM = { captainSection: null, captainInput: null, saveCaptainBtn: null, difficultySection: null, captainGreeting: null, subTitle: null, resultsTableBody: null, audioToggle: null, audioSliderContainer: null, masterVolumeSlider: null, volumeValue: null, snowContainer: null }; // textes selon si le joueur est nouveau ou pas const TEXTS = { new: { subtitle: "Traversez les eaux glacées arctiques et livrez votre précieuse cargaison.", greeting: "Prêt pour votre première traversée, Capitaine ?", noScores: "Votre journal de bord est vide. Partez en mer pour écrire votre légende !" }, returning: { subtitle: "Content de vous revoir ! Prêt à battre vos records ?", greetings: [ "De retour aux commandes, Capitaine {name} ! La mer vous attend.", "Les icebergs n'attendent que vous, Capitaine {name}.", "Capitaine {name}, votre équipage est prêt à lever l'ancre !", "Ahoy Capitaine {name} ! Une nouvelle tempête se profile.", "Bienvenue à bord, Capitaine {name}. Le cargo est chargé." ], noScores: "Hmm, votre journal semble vide. Reprenez la mer !" }, veteran: { subtitle: "Légende des mers glacées ! Votre réputation vous précède.", greetings: [ "La légende revient ! Capitaine {name}, montrez-leur de quel bois vous êtes fait.", "Capitaine {name}, vos exploits sont chantés dans tous les ports arctiques !", "Les icebergs tremblent à votre approche, Capitaine {name} !" ] } }; // --- Init --- document.addEventListener('DOMContentLoaded', init); function init() { cacheDOMElements(); initSnowEffect(); initAudioControls(); // Vérifier et sauvegarder un nouveau score depuis l'URL checkAndSaveNewScore(); // Configurer l'interface selon le statut du joueur setupPlayerExperience(); // Charger le leaderboard loadLeaderboard(); // Attacher les événements bindEvents(); } function cacheDOMElements() { DOM.captainSection = document.getElementById('captain-section'); DOM.captainInput = document.getElementById('captain-name-input'); DOM.saveCaptainBtn = document.getElementById('save-captain-btn'); DOM.difficultySection = document.getElementById('difficulty-selection'); DOM.captainGreeting = document.getElementById('captain-greeting'); DOM.subTitle = document.getElementById('sub-title'); DOM.resultsTableBody = document.getElementById('results-table-body'); DOM.audioToggle = document.getElementById('audio-toggle'); DOM.audioSliderContainer = document.getElementById('audio-slider-container'); DOM.masterVolumeSlider = document.getElementById('master-volume'); DOM.volumeValue = document.getElementById('volume-value'); DOM.snowContainer = document.getElementById('snow-container'); } // flocons de neige function initSnowEffect() { if (!DOM.snowContainer) return; const snowflakeCount = 50; const fragment = document.createDocumentFragment(); for (let i = 0; i < snowflakeCount; i++) { const flake = document.createElement('div'); flake.className = 'snowflake'; flake.style.cssText = ` left: ${Math.random() * 100}%; animation-delay: ${Math.random() * 10}s; animation-duration: ${8 + Math.random() * 7}s; opacity: ${0.3 + Math.random() * 0.7}; font-size: ${8 + Math.random() * 12}px; `; flake.textContent = '❄'; fragment.appendChild(flake); } DOM.snowContainer.appendChild(fragment); } // --- Audio --- function initAudioControls() { if (!DOM.audioToggle) return; // Initialiser le gestionnaire audio if (typeof AudioManager !== 'undefined') { AudioManager.init(); } // Charger le volume sauvegardé const settings = typeof IceBreakerStorage !== 'undefined' ? IceBreakerStorage.getSettings() : { masterVolume: 0.8 }; if (DOM.masterVolumeSlider) { DOM.masterVolumeSlider.value = Math.round(settings.masterVolume * 100); updateVolumeDisplay(settings.masterVolume * 100); } // Initialiser la musique d'ambiance initBackgroundMusic(settings.masterVolume); // Toggle du panneau audio DOM.audioToggle.addEventListener('click', () => { DOM.audioSliderContainer.classList.toggle('hidden'); }); // Changement de volume if (DOM.masterVolumeSlider) { DOM.masterVolumeSlider.addEventListener('input', (e) => { const value = parseInt(e.target.value); updateVolumeDisplay(value); if (typeof AudioManager !== 'undefined') { AudioManager.setMasterVolume(value / 100); } }); } // Fermer le panneau en cliquant ailleurs document.addEventListener('click', (e) => { if (!e.target.closest('#audio-controls')) { DOM.audioSliderContainer?.classList.add('hidden'); } }); } function initBackgroundMusic(masterVolume) { const bgm = document.getElementById('background-audio'); if (!bgm) return; bgm.volume = masterVolume * 0.5; // Démarrer la musique au premier clic utilisateur (politique autoplay) const startMusic = () => { bgm.play().catch(() => { }); document.removeEventListener('click', startMusic); document.removeEventListener('keydown', startMusic); }; document.addEventListener('click', startMusic); document.addEventListener('keydown', startMusic); // Mettre à jour le volume quand le slider change if (DOM.masterVolumeSlider) { DOM.masterVolumeSlider.addEventListener('input', (e) => { bgm.volume = (parseInt(e.target.value) / 100) * 0.5; }); } } function updateVolumeDisplay(value) { if (DOM.volumeValue) { DOM.volumeValue.textContent = `${Math.round(value)}%`; } // Mettre à jour l'icône const icon = DOM.audioToggle?.querySelector('.audio-icon'); if (icon) { if (value === 0) { icon.textContent = '🔇'; } else if (value < 50) { icon.textContent = '🔉'; } else { icon.textContent = '🔊'; } } } // config interface selon le joueur function setupPlayerExperience() { const captainName = IceBreakerStorage.getCaptainName(); const playCount = IceBreakerStorage.getPlayCount(); const isReturning = IceBreakerStorage.isReturningPlayer(); if (!captainName) { // Nouveau joueur : afficher le formulaire showCaptainForm(); } else { // Joueur existant : personnaliser l'interface hideCaptainForm(); personalizeInterface(captainName, playCount); } } function showCaptainForm() { DOM.captainSection?.classList.remove('hidden'); DOM.subTitle.textContent = TEXTS.new.subtitle; // Focus sur l'input après un délai pour l'animation setTimeout(() => { DOM.captainInput?.focus(); }, 500); } function hideCaptainForm() { DOM.captainSection?.classList.add('hidden'); } function personalizeInterface(captainName, playCount) { let textSet; if (playCount >= 10) { textSet = TEXTS.veteran; } else if (playCount > 0) { textSet = TEXTS.returning; } else { textSet = TEXTS.new; } // Subtitle DOM.subTitle.textContent = textSet.subtitle; // Greeting personnalisé if (textSet.greetings) { const greeting = textSet.greetings[Math.floor(Math.random() * textSet.greetings.length)]; DOM.captainGreeting.textContent = greeting.replace('{name}', captainName); } else { DOM.captainGreeting.textContent = textSet.greeting || ''; } } // events function bindEvents() { // Sauvegarder le nom du capitaine DOM.saveCaptainBtn?.addEventListener('click', saveCaptainName); DOM.captainInput?.addEventListener('keypress', (e) => { if (e.key === 'Enter') saveCaptainName(); }); // Boutons de difficulté document.querySelectorAll('.diff-btn').forEach(btn => { btn.addEventListener('click', (e) => { const difficulty = e.currentTarget.getAttribute('data-difficulty'); startGame(difficulty); }); }); } function saveCaptainName() { const name = DOM.captainInput?.value.trim(); const MIN_LEN = 3; const MAX_LEN = 20; const validRe = /^[A-Za-z0-9 _-]+$/; if (!name) { showInputError('Veuillez entrer un pseudo.'); return; } // Validation du pseudo comme demandé if (name.length < MIN_LEN || name.length > MAX_LEN || !validRe.test(name)) { showInputError(`Pseudo invalide — ${MIN_LEN}-${MAX_LEN} caractères, lettres/chiffres/_/- seulement.`); return; } IceBreakerStorage.saveCaptainName(name); hideCaptainForm(); personalizeInterface(name, 0); DOM.difficultySection?.classList.add('fade-in'); } // démarrer le jeu function startGame(difficulty) { // Incrémenter le compteur de parties IceBreakerStorage.incrementPlayCount(); // Rediriger vers le jeu window.location.href = `cruise.html?score=0&difficulty=${difficulty}`; } // vérifier si on revient avec un score function checkAndSaveNewScore() { const params = new URLSearchParams(window.location.search); // Format unifié pour tous les scores (checkpoint et game over) if (params.get('finalScore')) { const totalDelivered = parseInt(params.get('totalDelivered') || 0); const totalPossible = parseInt(params.get('totalPossible') || 1); const cargoDeliveryRate = Math.round((totalDelivered / totalPossible) * 100); const scoreData = { score: parseInt(params.get('finalScore')), difficulty: parseFloat(params.get('finalDifficulty') || 1), cargoDeliveryRate: cargoDeliveryRate, checkpoints: parseInt(params.get('checkpoints') || 0), totalDelivered: totalDelivered, totalPossible: totalPossible }; if (!isNaN(scoreData.score)) { IceBreakerStorage.saveScore(scoreData); // Nettoyer l'URL window.history.replaceState({}, document.title, window.location.pathname); } } } function loadLeaderboard() { const scores = IceBreakerStorage.getLeaderboard(); if (!DOM.resultsTableBody) return; DOM.resultsTableBody.innerHTML = ''; if (scores.length === 0) { const isReturning = IceBreakerStorage.isReturningPlayer(); const noDataText = isReturning ? TEXTS.returning.noScores : TEXTS.new.noScores; DOM.resultsTableBody.innerHTML = `${noDataText}`; return; } const fragment = document.createDocumentFragment(); scores.slice(0, MAX_SCORES_DISPLAY).forEach((result, index) => { const row = document.createElement('tr'); row.className = index === 0 ? 'top-score' : ''; const rankIcon = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}`; // Afficher le pourcentage de livraison ou fallback pour anciens scores let deliveryDisplay; if (typeof result.cargoDeliveryRate === 'number') { deliveryDisplay = `${result.cargoDeliveryRate}%`; } else if (result.containers !== undefined) { // Fallback pour anciens scores const rate = Math.round((result.containers / (result.initialContainers || 20)) * 100); deliveryDisplay = `${rate}%`; } else { deliveryDisplay = '-'; } row.innerHTML = ` ${rankIcon} ${escapeHtml(result.captain || 'Anonyme')} ${result.score.toLocaleString()} ${deliveryDisplay} ${mapDifficulty(result.difficulty)} `; fragment.appendChild(row); }); DOM.resultsTableBody.appendChild(fragment); } // helpers function mapDifficulty(diff) { if (diff <= 1.1) return '❄️ Calme'; if (diff <= 1.6) return '🌊 Normal'; if (diff <= 2.6) return '⚡ Tempête'; return `🔥 Expert`; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // messages d'erreur input function showInputMessage(msg, type = 'error') { if (!DOM.captainInput) return; let msgEl = document.getElementById('captain-input-message'); if (!msgEl) { msgEl = document.createElement('div'); msgEl.id = 'captain-input-message'; msgEl.className = 'input-message'; DOM.captainInput.parentNode?.insertBefore(msgEl, DOM.captainInput.nextSibling); } msgEl.textContent = msg; msgEl.classList.remove('error', 'success', 'visible'); msgEl.classList.add(type); // Force reflow then show for CSS transitions void msgEl.offsetWidth; msgEl.classList.add('visible'); // Animation visuelle sur l'input DOM.captainInput.classList.remove('shake'); void DOM.captainInput.offsetWidth; DOM.captainInput.classList.add('shake'); DOM.captainInput.focus(); // Nettoyage automatique clearTimeout(showInputMessage._timeout); showInputMessage._timeout = setTimeout(() => { msgEl.classList.remove('visible'); DOM.captainInput.classList.remove('shake'); }, 3000); } function showInputError(msg) { showInputMessage(msg, 'error'); } function showInputSuccess(msg) { showInputMessage(msg, 'success'); } })();