Compare commits

...

5 Commits

3 changed files with 1010 additions and 55 deletions

644
game.js
View File

@ -13,6 +13,9 @@ const finalScoreElement = document.getElementById('finalScore');
const finalLevelElement = document.getElementById('finalLevel'); const finalLevelElement = document.getElementById('finalLevel');
const overlayRestartBtn = document.getElementById('overlayRestartBtn'); const overlayRestartBtn = document.getElementById('overlayRestartBtn');
const confirmUsernameBtn = document.getElementById('confirmUsernameBtn'); const confirmUsernameBtn = document.getElementById('confirmUsernameBtn');
const gameOverUsernameInput = document.getElementById('gameOverUsername');
const saveScoreBtn = document.getElementById('saveScoreBtn');
const scoreSavedMsg = document.getElementById('scoreSavedMsg');
// === GESTION DU MENU === // === GESTION DU MENU ===
const mainMenu = document.getElementById('mainMenu'); const mainMenu = document.getElementById('mainMenu');
@ -28,10 +31,29 @@ const closeRulesBtn = document.getElementById('closeRulesBtn');
const menuLeaderboard = document.getElementById('menuLeaderboard'); const menuLeaderboard = document.getElementById('menuLeaderboard');
const leaderboardContainer = document.getElementById('leaderboardContainer'); const leaderboardContainer = document.getElementById('leaderboardContainer');
const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn'); const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const customizeBtn = document.getElementById('customizeBtn');
const customizeModal = document.getElementById('customizeModal');
const closeCustomizeBtn = document.getElementById('closeCustomizeBtn');
const saveCustomizeBtn = document.getElementById('saveCustomizeBtn');
const playerPreviewCanvas = document.getElementById('playerPreviewCanvas');
const colorOptions = document.querySelectorAll('.color-option');
const shapeOptions = document.querySelectorAll('.shape-option');
// Fonction pour vérifier si on est en mode plein écran
function isFullscreen() {
return !!(document.fullscreenElement || document.webkitFullscreenElement ||
document.mozFullScreenElement || document.msFullscreenElement);
}
let usernameConfirmed = false; let usernameConfirmed = false;
let gameOverUsername = null;
const CELL_SIZE = 20; // Personnalisation du joueur
let playerColor = localStorage.getItem('playerColor') || 'rainbow';
let playerShape = localStorage.getItem('playerShape') || 'round';
const CELL_SIZE = 25;
const COLS = 30; const COLS = 30;
const ROWS = 30; const ROWS = 30;
@ -233,7 +255,7 @@ let traps = [];
let specialZones = []; let specialZones = [];
class Pacman { class Pacman {
constructor() { constructor(color = null, shape = null) {
this.x = 14; this.x = 14;
this.y = 23; this.y = 23;
this.direction = 0; this.direction = 0;
@ -245,6 +267,8 @@ class Pacman {
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2; this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2; this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
this.colorAnimation = 0; this.colorAnimation = 0;
this.color = color || playerColor;
this.shape = shape || playerShape;
} }
update() { update() {
@ -354,14 +378,14 @@ class Pacman {
cherryEatenTimer = Math.max(150, 300 - (level - 1) * 20); cherryEatenTimer = Math.max(150, 300 - (level - 1) * 20);
// Rendre tous les fantômes vulnérables // Rendre tous les fantômes vulnérables
const vulnerableTime = Math.max(180, 360 - (level - 1) * 30); const vulnerableTime = Math.max(60, 180 - (level - 1) * 15);
for (let ghost of ghosts) { for (let ghost of ghosts) {
ghost.isVulnerable = true; ghost.isVulnerable = true;
ghost.vulnerableTimer = vulnerableTime; ghost.vulnerableTimer = vulnerableTime;
} }
// Afficher l'indicateur de poursuite // Afficher l'indicateur de poursuite (sauf en mode plein écran)
if (pursuitIndicator) { if (pursuitIndicator && !isFullscreen()) {
pursuitIndicator.style.display = 'block'; pursuitIndicator.style.display = 'block';
} }
@ -449,19 +473,93 @@ class Pacman {
const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI]; const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI];
ctx.rotate(rotation[this.direction]); ctx.rotate(rotation[this.direction]);
const hue = (this.colorAnimation * 180 / Math.PI) % 360; // Utiliser la couleur personnalisée
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`; let baseFillColor;
if (this.color === 'rainbow') {
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
baseFillColor = `hsl(${hue}, 100%, 50%)`;
ctx.fillStyle = baseFillColor;
} else {
const colorMap = {
'yellow': '#ffd700',
'red': '#ff4444',
'blue': '#4444ff',
'green': '#44ff44',
'purple': '#aa44ff',
'orange': '#ff8844',
'pink': '#ff44aa'
};
baseFillColor = colorMap[this.color] || '#ffd700';
ctx.fillStyle = baseFillColor;
}
const size = CELL_SIZE * 0.4;
ctx.beginPath(); ctx.beginPath();
if (this.mouthOpen) { if (this.shape === 'triangle') {
ctx.arc(0, 0, CELL_SIZE * 0.4, 0.2, Math.PI * 2 - 0.2); const triangleSize = size * 1.5; // Plus grand pour une meilleure visibilité
} else {
ctx.arc(0, 0, CELL_SIZE * 0.4, 0, Math.PI * 2);
}
ctx.lineTo(0, 0); // Dessiner le triangle avec contour et yeux
ctx.fill(); ctx.beginPath();
if (this.mouthOpen) {
// Triangle avec bouche ouverte - ouverture plus grande et visible
ctx.moveTo(0, -triangleSize * 0.9);
ctx.lineTo(-triangleSize * 0.85, triangleSize * 0.7);
ctx.lineTo(-triangleSize * 0.4, triangleSize * 0.4);
ctx.lineTo(0, triangleSize * 0.2);
ctx.lineTo(triangleSize * 0.4, triangleSize * 0.4);
ctx.lineTo(triangleSize * 0.85, triangleSize * 0.7);
ctx.closePath();
} else {
// Triangle équilatéral complet
ctx.moveTo(0, -triangleSize);
ctx.lineTo(-triangleSize * 0.866, triangleSize * 0.5); // cos(30°) ≈ 0.866
ctx.lineTo(triangleSize * 0.866, triangleSize * 0.5);
ctx.closePath();
}
// Remplir le triangle
ctx.fill();
// Contour noir épais pour plus de définition
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2.5;
ctx.stroke();
// Dessiner les yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
ctx.fill();
// Pupilles
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
ctx.fill();
// Reflets dans les yeux pour plus de vie
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-triangleSize * 0.22, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.18, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
ctx.fill();
// Restaurer la couleur originale
ctx.fillStyle = baseFillColor;
} else {
// Forme ronde (par défaut)
if (this.mouthOpen) {
ctx.arc(0, 0, size, 0.2, Math.PI * 2 - 0.2);
} else {
ctx.arc(0, 0, size, 0, Math.PI * 2);
}
ctx.lineTo(0, 0);
ctx.fill();
}
ctx.restore(); ctx.restore();
} }
@ -538,7 +636,7 @@ class Ghost {
} }
} }
this.moveInterval = Math.max(8, 20 - (level - 1) * 2); this.moveInterval = Math.max(5, 15 - (level - 1) * 1.5);
this.moveCounter++; this.moveCounter++;
@ -555,15 +653,20 @@ class Ghost {
// Fuir le joueur quand vulnérable // Fuir le joueur quand vulnérable
this.direction = this.getDirectionAwayFromPacman(possibleDirections); this.direction = this.getDirectionAwayFromPacman(possibleDirections);
} else { } else {
// Comportement selon le type // Tous les fantômes chassent le joueur (sauf patrouilleurs qui patrouillent si très loin)
if (this.type === GHOST_HUNTER) { if (this.type === GHOST_PATROL) {
this.direction = this.getDirectionToPacman(possibleDirections); const distance = Math.sqrt(
} else if (this.type === GHOST_PATROL) { Math.pow(pacman.x - this.x, 2) +
this.direction = this.getPatrolDirection(possibleDirections); Math.pow(pacman.y - this.y, 2)
} else if (this.type === GHOST_FAST || this.type === GHOST_INVISIBLE) { );
this.direction = this.getDirectionToPacman(possibleDirections); // Si proche, chasser, sinon patrouiller (seulement si très loin)
if (distance < 12) {
this.direction = this.getDirectionToPacman(possibleDirections);
} else {
this.direction = this.getPatrolDirection(possibleDirections);
}
} else { } else {
// Normal : toujours poursuivre // Tous les autres chassent directement
this.direction = this.getDirectionToPacman(possibleDirections); this.direction = this.getDirectionToPacman(possibleDirections);
} }
} }
@ -595,10 +698,13 @@ class Ghost {
let targetX = pacman.x; let targetX = pacman.x;
let targetY = pacman.y; let targetY = pacman.y;
// Prédiction améliorée pour tous les niveaux
const predictionSteps = level >= 3 ? 4 : 3;
// Si Pacman bouge, prédire où il sera // Si Pacman bouge, prédire où il sera
if (pacman.direction !== undefined) { if (pacman.direction !== undefined) {
const futureX = pacman.x + dx[pacman.direction] * 2; const futureX = pacman.x + dx[pacman.direction] * predictionSteps;
const futureY = pacman.y + dy[pacman.direction] * 2; const futureY = pacman.y + dy[pacman.direction] * predictionSteps;
if (futureX >= 0 && futureX < COLS && futureY >= 0 && futureY < ROWS) { if (futureX >= 0 && futureX < COLS && futureY >= 0 && futureY < ROWS) {
targetX = futureX; targetX = futureX;
targetY = futureY; targetY = futureY;
@ -612,10 +718,8 @@ class Ghost {
const oppositeDirection = (this.direction + 2) % 4; const oppositeDirection = (this.direction + 2) % 4;
for (let dir of possibleDirections) { for (let dir of possibleDirections) {
// Éviter la direction opposée sauf si c'est la seule option // Permettre le retour en arrière si c'est la meilleure direction
if (possibleDirections.length > 1 && dir === oppositeDirection) { // (pas de restriction pour une poursuite plus agressive)
continue;
}
const nextX = this.x + dx[dir]; const nextX = this.x + dx[dir];
const nextY = this.y + dy[dir]; const nextY = this.y + dy[dir];
@ -1224,7 +1328,7 @@ function checkCollisions() {
gameRunning = false; gameRunning = false;
statusElement.textContent = 'Game Over !'; statusElement.textContent = 'Game Over !';
showGameOver(); showGameOver();
saveScore(); // Le score sera sauvegardé quand l'utilisateur clique sur "Sauvegarder"
} else { } else {
restartCurrentLevel(); restartCurrentLevel();
} }
@ -1308,7 +1412,7 @@ function gameLoop() {
ghost.vulnerableTimer = FRENZY_DURATION; ghost.vulnerableTimer = FRENZY_DURATION;
} }
if (frenzyIndicator) frenzyIndicator.style.display = 'block'; if (frenzyIndicator) frenzyIndicator.style.display = 'block';
if (pursuitIndicator) pursuitIndicator.style.display = 'block'; if (pursuitIndicator && !isFullscreen()) pursuitIndicator.style.display = 'block';
} }
} else { } else {
frenzyTimer--; frenzyTimer--;
@ -1333,13 +1437,16 @@ function gameLoop() {
pacman.update(); pacman.update();
pacman.draw(); pacman.draw();
// Mettre à jour l'indicateur de poursuite // Mettre à jour l'indicateur de poursuite (masquer en mode plein écran)
if (pursuitIndicator && (cherryEatenTimer > 0 || frenzyModeActive)) { if (isFullscreen() && pursuitIndicator) {
pursuitIndicator.style.display = 'none';
} else if (pursuitIndicator && (cherryEatenTimer > 0 || frenzyModeActive) && !isFullscreen()) {
const timer = frenzyModeActive ? frenzyTimer : cherryEatenTimer; const timer = frenzyModeActive ? frenzyTimer : cherryEatenTimer;
const seconds = Math.ceil(timer / 60); const seconds = Math.ceil(timer / 60);
if (pursuitTimerElement) { if (pursuitTimerElement) {
pursuitTimerElement.textContent = seconds; pursuitTimerElement.textContent = seconds;
} }
pursuitIndicator.style.display = 'block';
} else if (pursuitIndicator && !frenzyModeActive) { } else if (pursuitIndicator && !frenzyModeActive) {
pursuitIndicator.style.display = 'none'; pursuitIndicator.style.display = 'none';
} }
@ -1751,6 +1858,77 @@ function placeBonuses() {
} }
} }
// === REDIMENSIONNEMENT ADAPTATIF DU CANVAS ===
function resizeCanvas() {
if (!gameWrapper || gameWrapper.style.display === 'none') {
return; // Ne pas redimensionner si le jeu n'est pas visible
}
const container = document.querySelector('.container');
const gameHeader = document.querySelector('.game-header');
const gameInfo = document.querySelector('.game-info');
const userInput = document.querySelector('.user-input-section');
const powerUps = document.querySelector('.power-ups-display');
const frenzyIndicator = document.querySelector('.frenzy-indicator');
const pursuitIndicator = document.querySelector('.pursuit-indicator');
const instructions = document.querySelector('.instructions');
const leaderboard = document.querySelector('.game-leaderboard');
if (!container || !canvas) return;
// Calculer l'espace disponible en hauteur
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
// Hauteur des éléments autour du canvas
let usedHeight = 0;
if (gameHeader) usedHeight += gameHeader.offsetHeight + 20;
if (userInput && userInput.offsetHeight > 0) usedHeight += userInput.offsetHeight + 10;
if (gameInfo) usedHeight += gameInfo.offsetHeight + 20;
if (powerUps && powerUps.offsetHeight > 0) usedHeight += powerUps.offsetHeight + 10;
if (frenzyIndicator && frenzyIndicator.offsetHeight > 0 && frenzyIndicator.style.display !== 'none') {
usedHeight += frenzyIndicator.offsetHeight + 10;
}
if (pursuitIndicator && pursuitIndicator.offsetHeight > 0 && pursuitIndicator.style.display !== 'none') {
usedHeight += pursuitIndicator.offsetHeight + 10;
}
if (instructions) usedHeight += instructions.offsetHeight + 20;
// Padding du container (30px top + 30px bottom)
usedHeight += 60;
// Marges et espacement
usedHeight += 40;
// Largeur disponible (en tenant compte du leaderboard à droite si visible)
let usedWidth = 0;
const containerPadding = 60; // 30px left + 30px right
const gaps = 60; // Espacement entre éléments
if (leaderboard && window.innerWidth > 1200) {
const leaderboardRect = leaderboard.getBoundingClientRect();
if (leaderboardRect.width > 0) {
usedWidth += leaderboardRect.width + 30; // + gap
}
}
usedWidth += containerPadding + gaps;
const availableHeight = windowHeight - usedHeight;
const availableWidth = windowWidth - usedWidth;
// La taille du canvas sera le minimum entre hauteur et largeur disponible
// Utilise 95% de l'espace disponible avec une taille minimale de 400px
const maxSize = Math.min(availableHeight, availableWidth);
const minSize = 400;
const canvasDisplaySize = Math.max(minSize, Math.min(maxSize * 0.95, 1200));
// Appliquer la taille au canvas via CSS
canvas.style.width = canvasDisplaySize + 'px';
canvas.style.height = canvasDisplaySize + 'px';
canvas.style.maxWidth = '100%';
canvas.style.maxHeight = 'calc(100vh - ' + usedHeight + 'px)';
}
function initGame() { function initGame() {
resetUsernameInput(); // Réinitialiser le champ nom resetUsernameInput(); // Réinitialiser le champ nom
@ -1816,6 +1994,10 @@ function initGame() {
placeBonuses(); placeBonuses();
countDots(); countDots();
updateGameLeaderboard(); updateGameLeaderboard();
// Redimensionner le canvas après l'initialisation
setTimeout(resizeCanvas, 100);
gameLoop(); gameLoop();
} }
@ -1863,9 +2045,15 @@ function getScores() {
return scoresJson ? JSON.parse(scoresJson) : []; return scoresJson ? JSON.parse(scoresJson) : [];
} }
function saveScore() { function saveScore(usernameForScore = null) {
let username; let username;
if (!usernameConfirmed) {
// Priorité au nom passé en paramètre, puis au nom du Game Over, puis au nom confirmé, sinon "Anonyme"
if (usernameForScore) {
username = usernameForScore.trim() || 'Anonyme';
} else if (gameOverUsername) {
username = gameOverUsername.trim() || 'Anonyme';
} else if (!usernameConfirmed) {
username = 'Anonyme'; username = 'Anonyme';
} else { } else {
username = usernameInput.value.trim() || 'Anonyme'; username = usernameInput.value.trim() || 'Anonyme';
@ -2036,6 +2224,21 @@ usernameInput.addEventListener('keypress', (e) => {
function showGameOver() { function showGameOver() {
finalScoreElement.textContent = score; finalScoreElement.textContent = score;
finalLevelElement.textContent = level; finalLevelElement.textContent = level;
// Réinitialiser le champ de nom et cacher le message
if (gameOverUsernameInput) {
gameOverUsernameInput.value = usernameInput.value.trim() || '';
gameOverUsernameInput.disabled = false;
gameOverUsernameInput.focus();
}
if (saveScoreBtn) {
saveScoreBtn.disabled = false;
}
if (scoreSavedMsg) {
scoreSavedMsg.style.display = 'none';
}
gameOverUsername = null; // Réinitialiser
gameOverlay.classList.add('active'); gameOverlay.classList.add('active');
restartBtn.style.display = 'block'; restartBtn.style.display = 'block';
} }
@ -2044,7 +2247,39 @@ function hideGameOver() {
gameOverlay.classList.remove('active'); gameOverlay.classList.remove('active');
} }
// Sauvegarder le score avec le nom du Game Over
if (saveScoreBtn && gameOverUsernameInput) {
saveScoreBtn.addEventListener('click', () => {
const username = gameOverUsernameInput.value.trim();
if (username.length === 0) {
alert('Veuillez entrer un nom d\'utilisateur !');
gameOverUsernameInput.focus();
return;
}
gameOverUsername = username;
saveScore(username);
// Afficher le message de confirmation
if (scoreSavedMsg) {
scoreSavedMsg.style.display = 'block';
}
// Désactiver le champ et le bouton
gameOverUsernameInput.disabled = true;
saveScoreBtn.disabled = true;
});
// Permettre de sauvegarder avec la touche Entrée
gameOverUsernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !saveScoreBtn.disabled) {
saveScoreBtn.click();
}
});
}
overlayRestartBtn.addEventListener('click', () => { overlayRestartBtn.addEventListener('click', () => {
hideGameOver();
initGame(); initGame();
}); });
@ -2261,7 +2496,11 @@ playBtn.addEventListener('click', () => {
resetUsernameInput(); resetUsernameInput();
// Toujours initialiser le jeu quand on clique sur JOUER // Toujours initialiser le jeu quand on clique sur JOUER
gameRunning = false; // S'assurer que le jeu est arrêté gameRunning = false; // S'assurer que le jeu est arrêté
initGame(); // Attendre un peu pour que le DOM soit rendu avant de redimensionner
setTimeout(() => {
initGame();
resizeCanvas();
}, 50);
}); });
// Retour au menu depuis le jeu // Retour au menu depuis le jeu
@ -2309,6 +2548,262 @@ if (rulesModal) {
}); });
} }
// === GESTION DU MODAL DE PERSONNALISATION ===
let selectedColor = playerColor;
let selectedShape = playerShape;
// Ouvrir le modal de personnalisation
if (customizeBtn) {
customizeBtn.addEventListener('click', () => {
if (customizeModal) {
customizeModal.style.display = 'flex';
selectedColor = playerColor;
selectedShape = playerShape;
updateColorSelection();
updateShapeSelection();
updatePlayerPreview();
startPreviewAnimation();
}
});
}
// Fermer le modal de personnalisation
if (closeCustomizeBtn) {
closeCustomizeBtn.addEventListener('click', () => {
if (customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
});
}
// Fermer en cliquant en dehors du modal
if (customizeModal) {
customizeModal.addEventListener('click', (e) => {
if (e.target === customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
});
}
// Gérer la sélection de couleur
if (colorOptions) {
colorOptions.forEach(option => {
option.addEventListener('click', () => {
selectedColor = option.getAttribute('data-color');
updateColorSelection();
updatePlayerPreview();
});
});
}
// Gérer la sélection de forme
if (shapeOptions) {
shapeOptions.forEach(option => {
option.addEventListener('click', () => {
selectedShape = option.getAttribute('data-shape');
updateShapeSelection();
updatePlayerPreview();
});
});
}
// Mettre à jour la sélection visuelle
function updateColorSelection() {
if (colorOptions) {
colorOptions.forEach(option => {
if (option.getAttribute('data-color') === selectedColor) {
option.classList.add('active');
} else {
option.classList.remove('active');
}
});
}
}
function updateShapeSelection() {
if (shapeOptions) {
shapeOptions.forEach(option => {
if (option.getAttribute('data-shape') === selectedShape) {
option.classList.add('active');
} else {
option.classList.remove('active');
}
});
}
}
// Prévisualisation du joueur
let previewAnimationId = null;
function startPreviewAnimation() {
if (!playerPreviewCanvas) return;
let angle = 0;
let mouthOpen = true;
function animate() {
if (!playerPreviewCanvas) return;
const ctx = playerPreviewCanvas.getContext('2d');
ctx.clearRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
// Fond
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
// Dessiner le joueur au centre
ctx.save();
ctx.translate(playerPreviewCanvas.width / 2, playerPreviewCanvas.height / 2);
// Couleur
if (selectedColor === 'rainbow') {
angle += 0.05;
const hue = (angle * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
} else {
const colorMap = {
'yellow': '#ffd700',
'red': '#ff4444',
'blue': '#4444ff',
'green': '#44ff44',
'purple': '#aa44ff',
'orange': '#ff8844',
'pink': '#ff44aa'
};
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
}
// Animation de la bouche
if (Math.floor(angle * 5) % 2 === 0) {
mouthOpen = !mouthOpen;
}
const size = 40;
ctx.beginPath();
if (selectedShape === 'triangle') {
const triangleSize = size * 1.5; // Plus grand pour une meilleure visibilité
// Dessiner le triangle avec contour et yeux
ctx.beginPath();
if (mouthOpen) {
// Triangle avec bouche ouverte
ctx.moveTo(0, -triangleSize * 0.9);
ctx.lineTo(-triangleSize * 0.85, triangleSize * 0.7);
ctx.lineTo(-triangleSize * 0.4, triangleSize * 0.4);
ctx.lineTo(0, triangleSize * 0.2);
ctx.lineTo(triangleSize * 0.4, triangleSize * 0.4);
ctx.lineTo(triangleSize * 0.85, triangleSize * 0.7);
ctx.closePath();
} else {
// Triangle équilatéral complet
ctx.moveTo(0, -triangleSize);
ctx.lineTo(-triangleSize * 0.866, triangleSize * 0.5);
ctx.lineTo(triangleSize * 0.866, triangleSize * 0.5);
ctx.closePath();
}
// Remplir le triangle
ctx.fill();
// Contour noir épais
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2.5;
ctx.stroke();
// Dessiner les yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
ctx.fill();
// Pupilles
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
ctx.fill();
// Reflets dans les yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-triangleSize * 0.22, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
ctx.arc(triangleSize * 0.18, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
ctx.fill();
// Restaurer la couleur originale
if (selectedColor === 'rainbow') {
const hue = (angle * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
} else {
const colorMap = {
'yellow': '#ffd700',
'red': '#ff4444',
'blue': '#4444ff',
'green': '#44ff44',
'purple': '#aa44ff',
'orange': '#ff8844',
'pink': '#ff44aa'
};
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
}
} else {
// Dessiner un cercle
if (mouthOpen) {
ctx.arc(0, 0, size, 0.2, Math.PI * 2 - 0.2);
} else {
ctx.arc(0, 0, size, 0, Math.PI * 2);
}
ctx.lineTo(0, 0);
}
ctx.fill();
ctx.restore();
previewAnimationId = requestAnimationFrame(animate);
}
animate();
}
function stopPreviewAnimation() {
if (previewAnimationId) {
cancelAnimationFrame(previewAnimationId);
previewAnimationId = null;
}
}
function updatePlayerPreview() {
// L'animation se mettra à jour automatiquement car elle utilise selectedColor
}
// Sauvegarder la personnalisation
if (saveCustomizeBtn) {
saveCustomizeBtn.addEventListener('click', () => {
playerColor = selectedColor;
playerShape = selectedShape;
localStorage.setItem('playerColor', playerColor);
localStorage.setItem('playerShape', playerShape);
// Mettre à jour le joueur actuel si le jeu est en cours
if (pacman) {
pacman.color = playerColor;
pacman.shape = playerShape;
}
// Fermer le modal
if (customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
// Message de confirmation
alert('Personnalisation sauvegardée !');
});
}
// Fermer le classement dans le jeu // Fermer le classement dans le jeu
if (closeLeaderboardBtn) { if (closeLeaderboardBtn) {
closeLeaderboardBtn.addEventListener('click', () => { closeLeaderboardBtn.addEventListener('click', () => {
@ -2379,3 +2874,80 @@ function updateBestScore() {
updateMenuLeaderboard(); updateMenuLeaderboard();
updateBestScore(); updateBestScore();
// Appeler au chargement, au redimensionnement et quand le jeu devient visible
window.addEventListener('resize', () => {
if (gameWrapper && gameWrapper.style.display !== 'none') {
resizeCanvas();
}
});
// Redimensionner quand le jeu est affiché
if (gameWrapper) {
const observer = new MutationObserver(() => {
if (gameWrapper && gameWrapper.style.display !== 'none') {
// Attendre un peu pour que le DOM soit complètement rendu
setTimeout(resizeCanvas, 100);
}
});
observer.observe(gameWrapper, { attributes: true, attributeFilter: ['style'] });
}
// === GESTION DU PLEIN ÉCRAN ===
function toggleFullscreen() {
const gameWrapper = document.getElementById('gameWrapper');
if (!document.fullscreenElement && !document.webkitFullscreenElement &&
!document.mozFullScreenElement && !document.msFullscreenElement) {
// Entrer en plein écran
if (gameWrapper.requestFullscreen) {
gameWrapper.requestFullscreen();
} else if (gameWrapper.webkitRequestFullscreen) {
gameWrapper.webkitRequestFullscreen();
} else if (gameWrapper.mozRequestFullScreen) {
gameWrapper.mozRequestFullScreen();
} else if (gameWrapper.msRequestFullscreen) {
gameWrapper.msRequestFullscreen();
}
} else {
// Quitter le plein écran
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
function updateFullscreenButton() {
const isFullscreenMode = document.fullscreenElement || document.webkitFullscreenElement ||
document.mozFullScreenElement || document.msFullscreenElement;
if (fullscreenBtn) {
fullscreenBtn.textContent = isFullscreenMode ? '✕ Sortir' : '⛶ Plein écran';
fullscreenBtn.title = isFullscreenMode ? 'Quitter le plein écran' : 'Plein écran';
}
// Cacher l'indicateur de poursuite en mode plein écran
if (isFullscreenMode && pursuitIndicator) {
pursuitIndicator.style.display = 'none';
}
// Redimensionner le canvas après changement de mode plein écran
setTimeout(resizeCanvas, 100);
}
// Ajouter l'event listener au bouton
if (fullscreenBtn) {
fullscreenBtn.addEventListener('click', toggleFullscreen);
}
// Écouter les changements de mode plein écran
document.addEventListener('fullscreenchange', updateFullscreenButton);
document.addEventListener('webkitfullscreenchange', updateFullscreenButton);
document.addEventListener('mozfullscreenchange', updateFullscreenButton);
document.addEventListener('MSFullscreenChange', updateFullscreenButton);

View File

@ -21,6 +21,9 @@
<button class="menu-btn" id="playBtn"> <button class="menu-btn" id="playBtn">
<span class="btn-icon"></span> JOUER <span class="btn-icon"></span> JOUER
</button> </button>
<button class="menu-btn" id="customizeBtn">
<span class="btn-icon">🎨</span> PERSONNALISER
</button>
<button class="menu-btn" id="scoresBtn"> <button class="menu-btn" id="scoresBtn">
<span class="btn-icon">🏆</span> CLASSEMENT <span class="btn-icon">🏆</span> CLASSEMENT
</button> </button>
@ -38,20 +41,21 @@
<div class="game-header"> <div class="game-header">
<button class="back-btn" id="backToMenuBtn">← Menu</button> <button class="back-btn" id="backToMenuBtn">← Menu</button>
<h2>OULVIC</h2> <h2>OULVIC</h2>
<button class="fullscreen-btn" id="fullscreenBtn" title="Plein écran">⛶ Plein écran</button>
</div> </div>
<div class="user-input-section"> <div class="user-input-section">
<label for="username">Nom d'utilisateur:</label> <label for="username">Nom d'utilisateur:</label>
<input type="text" id="username" placeholder="Entrez votre nom" maxlength="15"> <input type="text" id="username" placeholder="Entrez votre nom" maxlength="15">
<button id="confirmUsernameBtn" class="confirm-btn">✓ Confirmer</button> <button id="confirmUsernameBtn" class="confirm-btn">✓ Confirmer</button>
</div> </div>
<div class="game-info"> <div class="game-info">
<div class="score">Score: <span id="score">0</span></div> <div class="score">Score: <span id="score">0</span></div>
<div class="level">Niveau: <span id="level">1</span></div> <div class="level">Niveau: <span id="level">1</span></div>
<div class="lives">Vies: <span id="lives"><span class="heart"></span><span class="heart"></span><span class="heart"></span></span></div> <div class="lives">Vies: <span id="lives"><span class="heart"></span><span class="heart"></span><span class="heart"></span></span></div>
<div class="dots-remaining">Pastilles: <span id="dotsRemaining">0</span></div> <div class="dots-remaining">Pastilles: <span id="dotsRemaining">0</span></div>
<div class="combo-display" id="comboDisplay" style="display: none;">Combo x<span id="comboValue">1</span></div> <div class="combo-display" id="comboDisplay" style="display: none;">Combo x<span id="comboValue">1</span></div>
<div class="status" id="status">Prêt à jouer</div> <div class="status" id="status">Prêt à jouer</div>
</div> </div>
<div class="power-ups-display" id="powerUpsDisplay"> <div class="power-ups-display" id="powerUpsDisplay">
<div class="power-up-item" id="speedBoostIndicator" style="display: none;">⚡ Vitesse</div> <div class="power-up-item" id="speedBoostIndicator" style="display: none;">⚡ Vitesse</div>
<div class="power-up-item" id="shieldIndicator" style="display: none;">🛡 Bouclier</div> <div class="power-up-item" id="shieldIndicator" style="display: none;">🛡 Bouclier</div>
@ -63,7 +67,7 @@
<div class="pursuit-indicator" id="pursuitIndicator" style="display: none;"> <div class="pursuit-indicator" id="pursuitIndicator" style="display: none;">
<span class="pursuit-text">👻 Poursuite active: <span id="pursuitTimer">0</span>s</span> <span class="pursuit-text">👻 Poursuite active: <span id="pursuitTimer">0</span>s</span>
</div> </div>
<canvas id="gameCanvas" width="600" height="600"></canvas> <canvas id="gameCanvas" width="750" height="750"></canvas>
<!-- Contrôles tactiles mobile --> <!-- Contrôles tactiles mobile -->
<div class="mobile-controls" id="mobileControls"> <div class="mobile-controls" id="mobileControls">
@ -75,8 +79,8 @@
<button class="ctrl-btn" data-dir="2"></button> <button class="ctrl-btn" data-dir="2"></button>
</div> </div>
<div class="instructions"> <div class="instructions">
<p>Utilisez les flèches directionnelles pour déplacer Oulvic</p> <p>Utilisez les flèches directionnelles pour déplacer Oulvic</p>
<button id="restartBtn" style="display: none;">REJOUER</button> <button id="restartBtn" style="display: none;">REJOUER</button>
</div> </div>
@ -86,6 +90,12 @@
<h2 class="game-over-title">GAME OVER</h2> <h2 class="game-over-title">GAME OVER</h2>
<p class="final-score">Score Final: <span id="finalScore">0</span></p> <p class="final-score">Score Final: <span id="finalScore">0</span></p>
<p class="final-level">Niveau Atteint: <span id="finalLevel">1</span></p> <p class="final-level">Niveau Atteint: <span id="finalLevel">1</span></p>
<div class="game-over-username-section">
<label for="gameOverUsername">Nom pour le classement:</label>
<input type="text" id="gameOverUsername" placeholder="Entrez votre nom" maxlength="15">
<button id="saveScoreBtn" class="save-score-btn">💾 Sauvegarder</button>
</div>
<p class="score-saved-msg" id="scoreSavedMsg" style="display: none; color: #00ff00; font-size: 0.7em; margin-top: 10px;">Score sauvegardé !</p>
<button id="overlayRestartBtn">REJOUER</button> <button id="overlayRestartBtn">REJOUER</button>
</div> </div>
</div> </div>
@ -97,20 +107,20 @@
<h3>🏆 Classement</h3> <h3>🏆 Classement</h3>
</div> </div>
<div id="gameLeaderboardContent" class="game-leaderboard-content"></div> <div id="gameLeaderboardContent" class="game-leaderboard-content"></div>
</div> </div>
<!-- CLASSEMENT (caché par défaut) --> <!-- CLASSEMENT (caché par défaut) -->
<div class="leaderboard-container" id="leaderboardContainer" style="display: none;"> <div class="leaderboard-container" id="leaderboardContainer" style="display: none;">
<div class="leaderboard-header"> <div class="leaderboard-header">
<h2>Classement</h2> <h2>Classement</h2>
<button class="close-leaderboard-btn" id="closeLeaderboardBtn"></button> <button class="close-leaderboard-btn" id="closeLeaderboardBtn"></button>
</div> </div>
<div id="leaderboard"></div> <div id="leaderboard"></div>
</div>
</div> </div>
<footer> </div>
<p>By Ludo and Syoul</p> <footer>
</footer> <p>By Ludo and Syoul</p>
</footer>
</div> </div>
<!-- MODAL CLASSEMENT (pour le menu) --> <!-- MODAL CLASSEMENT (pour le menu) -->
@ -221,6 +231,73 @@
</div> </div>
</div> </div>
<!-- MODAL PERSONNALISATION -->
<div class="customize-modal" id="customizeModal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h2>🎨 PERSONNALISER VOTRE JOUEUR</h2>
<button class="close-modal-btn" id="closeCustomizeBtn">×</button>
</div>
<div class="customize-content">
<div class="customize-section">
<h3>Couleur du joueur</h3>
<div class="color-options">
<div class="color-option" data-color="yellow" data-name="Jaune">
<div class="color-preview" style="background: #ffd700;"></div>
<span>Jaune (Classique)</span>
</div>
<div class="color-option" data-color="red" data-name="Rouge">
<div class="color-preview" style="background: #ff4444;"></div>
<span>Rouge</span>
</div>
<div class="color-option" data-color="blue" data-name="Bleu">
<div class="color-preview" style="background: #4444ff;"></div>
<span>Bleu</span>
</div>
<div class="color-option" data-color="green" data-name="Vert">
<div class="color-preview" style="background: #44ff44;"></div>
<span>Vert</span>
</div>
<div class="color-option" data-color="purple" data-name="Violet">
<div class="color-preview" style="background: #aa44ff;"></div>
<span>Violet</span>
</div>
<div class="color-option" data-color="orange" data-name="Orange">
<div class="color-preview" style="background: #ff8844;"></div>
<span>Orange</span>
</div>
<div class="color-option active" data-color="rainbow" data-name="Arc-en-ciel">
<div class="color-preview rainbow-preview"></div>
<span>Arc-en-ciel (Animé)</span>
</div>
<div class="color-option" data-color="pink" data-name="Rose">
<div class="color-preview" style="background: #ff44aa;"></div>
<span>Rose</span>
</div>
</div>
</div>
<div class="customize-section">
<h3>Forme du joueur</h3>
<div class="shape-options">
<div class="shape-option active" data-shape="round" data-name="Rond">
<div class="shape-preview round-preview"></div>
<span>Rond (Classique)</span>
</div>
<div class="shape-option" data-shape="triangle" data-name="Triangle">
<div class="shape-preview triangle-preview"></div>
<span>Triangle</span>
</div>
</div>
</div>
<div class="customize-preview">
<h3>Aperçu</h3>
<canvas id="playerPreviewCanvas" width="200" height="200"></canvas>
</div>
<button class="save-customize-btn" id="saveCustomizeBtn">💾 Sauvegarder</button>
</div>
</div>
</div>
<script src="game.js"></script> <script src="game.js"></script>
</body> </body>
</html> </html>

306
style.css
View File

@ -19,6 +19,7 @@ body {
padding: 20px; padding: 20px;
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='32' viewBox='0 0 36 32'%3E%3Cdefs%3E%3Cfilter id='glow'%3E%3CfeGaussianBlur stdDeviation='1.5' result='coloredBlur'/%3E%3CfeMerge%3E%3CfeMergeNode in='coloredBlur'/%3E%3CfeMergeNode in='SourceGraphic'/%3E%3C/feMerge%3E%3C/filter%3E%3ClinearGradient id='ghostGrad' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23ff4444;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23cc0000;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M18 4 Q22 2 26 4 Q30 6 30 10 L30 20 Q30 24 28 26 Q26 28 24 28 Q22 30 20 28 Q18 30 16 28 Q14 30 12 28 Q10 30 8 28 Q6 30 4 28 Q2 26 2 22 L2 10 Q2 6 6 4 Q10 2 14 4 Q16 2 18 4 Z' fill='url(%23ghostGrad)' filter='url(%23glow)' stroke='%23ffffff' stroke-width='0.3'/%3E%3Cpath d='M8 22 Q6 24 8 26 Q9 24 8 22 M14 22 Q12 24 14 26 Q15 24 14 22 M20 22 Q18 24 20 26 Q21 24 20 22 M26 22 Q24 24 26 26 Q27 24 26 22' fill='url(%23ghostGrad)' filter='url(%23glow)'/%3E%3Ccircle cx='12' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='24' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='12' cy='10' r='1.8' fill='%23000000'/%3E%3Ccircle cx='24' cy='10' r='1.8' fill='%23000000'/%3E%3Cellipse cx='12.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3Cellipse cx='24.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3C/svg%3E") 18 16, auto;
} }
body::before { body::before {
@ -143,6 +144,7 @@ body::after {
height: 100%; height: 100%;
z-index: 1000; z-index: 1000;
overflow: hidden; overflow: hidden;
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='32' viewBox='0 0 36 32'%3E%3Cdefs%3E%3Cfilter id='glow'%3E%3CfeGaussianBlur stdDeviation='1.5' result='coloredBlur'/%3E%3CfeMerge%3E%3CfeMergeNode in='coloredBlur'/%3E%3CfeMergeNode in='SourceGraphic'/%3E%3C/feMerge%3E%3C/filter%3E%3ClinearGradient id='ghostGrad' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23ff4444;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23cc0000;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M18 4 Q22 2 26 4 Q30 6 30 10 L30 20 Q30 24 28 26 Q26 28 24 28 Q22 30 20 28 Q18 30 16 28 Q14 30 12 28 Q10 30 8 28 Q6 30 4 28 Q2 26 2 22 L2 10 Q2 6 6 4 Q10 2 14 4 Q16 2 18 4 Z' fill='url(%23ghostGrad)' filter='url(%23glow)' stroke='%23ffffff' stroke-width='0.3'/%3E%3Cpath d='M8 22 Q6 24 8 26 Q9 24 8 22 M14 22 Q12 24 14 26 Q15 24 14 22 M20 22 Q18 24 20 26 Q21 24 20 22 M26 22 Q24 24 26 26 Q27 24 26 22' fill='url(%23ghostGrad)' filter='url(%23glow)'/%3E%3Ccircle cx='12' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='24' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='12' cy='10' r='1.8' fill='%23000000'/%3E%3Ccircle cx='24' cy='10' r='1.8' fill='%23000000'/%3E%3Cellipse cx='12.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3Cellipse cx='24.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3C/svg%3E") 18 16, auto;
} }
/* === CANVAS ARRIERE-PLAN MENU === */ /* === CANVAS ARRIERE-PLAN MENU === */
@ -321,6 +323,26 @@ body::after {
transform: translateX(-3px); transform: translateX(-3px);
} }
.fullscreen-btn {
padding: 10px 20px;
font-size: 0.7em;
font-family: 'Press Start 2P', cursive;
background: rgba(255, 255, 255, 0.1);
color: #ffd700;
border: 2px solid #ffd700;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
line-height: 1.2;
white-space: nowrap;
}
.fullscreen-btn:hover {
background: rgba(255, 215, 0, 0.2);
transform: scale(1.1);
box-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}
/* === MODAL CLASSEMENT === */ /* === MODAL CLASSEMENT === */
.leaderboard-modal { .leaderboard-modal {
position: fixed; position: fixed;
@ -693,6 +715,14 @@ h1 {
0 0 40px rgba(255, 215, 0, 0.3), 0 0 40px rgba(255, 215, 0, 0.3),
inset 0 0 60px rgba(0, 0, 255, 0.1); inset 0 0 60px rgba(0, 0, 255, 0.1);
animation: canvasGlow 3s ease-in-out infinite alternate; animation: canvasGlow 3s ease-in-out infinite alternate;
max-width: 100%;
max-height: 90vh;
width: auto;
height: auto;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
} }
/* === INPUT UTILISATEUR === */ /* === INPUT UTILISATEUR === */
@ -1093,6 +1123,71 @@ h1 {
transform: translateY(0) scale(0.98); transform: translateY(0) scale(0.98);
} }
.game-over-username-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin: 20px 0;
}
.game-over-username-section label {
font-size: 0.7em;
color: #ffd700;
font-weight: bold;
}
#gameOverUsername {
padding: 10px 15px;
font-size: 0.8em;
font-family: 'Press Start 2P', cursive;
border: 2px solid #ffd700;
border-radius: 10px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
outline: none;
max-width: 250px;
width: 100%;
text-align: center;
transition: all 0.3s;
}
#gameOverUsername:focus {
border-color: #ffed4e;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.6);
background: rgba(0, 0, 0, 0.9);
}
.save-score-btn {
padding: 10px 20px;
font-size: 0.7em;
font-family: 'Press Start 2P', cursive;
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
color: #000;
border: 2px solid #00ff00;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
}
.save-score-btn:hover {
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
}
.save-score-btn:active {
transform: translateY(0) scale(0.98);
}
.save-score-btn:disabled {
background: rgba(100, 100, 100, 0.5);
border-color: #666;
cursor: not-allowed;
opacity: 0.5;
}
/* === CONTROLES TACTILES MOBILE === */ /* === CONTROLES TACTILES MOBILE === */
.mobile-controls { .mobile-controls {
display: none; display: none;
@ -1238,3 +1333,214 @@ footer {
font-size: 0.9em; font-size: 0.9em;
} }
} }
/* === MODAL PERSONNALISATION === */
.customize-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
animation: fadeIn 0.3s ease-out;
}
.customize-modal.active {
display: flex;
}
.customize-content {
display: flex;
flex-direction: column;
gap: 30px;
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.customize-section {
background: rgba(0, 0, 0, 0.6);
padding: 20px;
border-radius: 15px;
border: 2px solid #ffd700;
}
.customize-section h3 {
color: #ffd700;
font-size: 1em;
margin-bottom: 15px;
text-align: center;
}
.color-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-top: 15px;
}
.color-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 215, 0, 0.3);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.color-option:hover {
background: rgba(255, 215, 0, 0.1);
border-color: #ffd700;
transform: scale(1.05);
}
.color-option.active {
background: rgba(255, 215, 0, 0.2);
border-color: #ffd700;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
}
.color-preview {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #fff;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.rainbow-preview {
background: linear-gradient(45deg, #ff0000, #ff8800, #ffff00, #00ff00, #0088ff, #0000ff, #8800ff);
animation: rainbowRotate 3s linear infinite;
}
@keyframes rainbowRotate {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
.color-option span {
font-size: 0.7em;
color: #fff;
text-align: center;
}
.customize-preview {
background: rgba(0, 0, 0, 0.6);
padding: 20px;
border-radius: 15px;
border: 2px solid #ffd700;
display: flex;
flex-direction: column;
align-items: center;
}
.customize-preview h3 {
color: #ffd700;
font-size: 1em;
margin-bottom: 15px;
}
#playerPreviewCanvas {
border: 3px solid #ffd700;
border-radius: 10px;
background: #000;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
}
.save-customize-btn {
padding: 15px 30px;
font-size: 0.9em;
font-family: 'Press Start 2P', cursive;
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
color: #000;
border: 2px solid #00ff00;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
align-self: center;
}
.save-customize-btn:hover {
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
}
.save-customize-btn:active {
transform: translateY(0) scale(0.98);
}
.shape-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-top: 15px;
}
.shape-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 215, 0, 0.3);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.shape-option:hover {
background: rgba(255, 215, 0, 0.1);
border-color: #ffd700;
transform: scale(1.05);
}
.shape-option.active {
background: rgba(255, 215, 0, 0.2);
border-color: #ffd700;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
}
.shape-preview {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.round-preview {
width: 50px;
height: 50px;
border-radius: 50%;
background: #ffd700;
border: 3px solid #fff;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.triangle-preview {
width: 0;
height: 0;
border-left: 25px solid transparent;
border-right: 25px solid transparent;
border-bottom: 45px solid #ffd700;
border-top: none;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
}
.shape-option span {
font-size: 0.7em;
color: #fff;
text-align: center;
}