commit b5dcec2200280ae5240d7ab6a70e62e2e0048832 Author: Sam Date: Sat Nov 8 10:31:43 2025 +0100 initial commit diff --git a/index.html b/index.html new file mode 100644 index 0000000..85b18a3 --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ + + + + + + Les Jeux de Waterloo + + + + +
+
+

Jeux de Waterloo

+

🗺️ District 33 🗺️

+

+ Magali ! ton aventure est loin d'être finie...
+ Le Capitole t'a miniaturisée et plongée dans un monde virtuel.
+ Retrace tes pas dans ce District 33 que tu connais comme ta poche et replace les animaux cachés.
+ La libération de Sam et le trésor t'attendent à la fin de cette quête !
+ Bonne chance, Chevalière Badass !
+

+ +
+
+ +
+

Les Jeux de Waterloo

+

🗺️ District 33 🗺️

+
+ + + +
+ Animaux trouvés : 0 / 10 +
+ + + + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..0898ef2 --- /dev/null +++ b/script.js @@ -0,0 +1,849 @@ +const canvas = document.getElementById('map'); +const ctx = canvas.getContext('2d'); +const tileSize = 64; + +// Game started flag +let gameStarted = false; + +// Start game function +function startGame() { + const startScreen = document.getElementById('startScreen'); + startScreen.classList.add('hidden'); + + // Show welcome message + setTimeout(() => { + showMessage("Bienvenue dans l'arène ! Tu peux te déplacer en glissant ton doigt dans la direction que tu veux.", false); + gameStarted = true; + }, 300); +} + +// Charger les tiles individuelles +const tiles = { + wall: new Image(), + floor: new Image(), + stair: new Image(), + wind: new Image(), + door: new Image(), +}; + +tiles.wall.src = 'tiles/wall.png'; +tiles.floor.src = 'tiles/sol.png'; +tiles.stair.src = 'tiles/stairs.png'; +tiles.wind.src = 'tiles/wind.png'; +tiles.door.src = 'tiles/door.png'; + +// Animal sprites +const animalImages = { + lion: new Image(), + elephant: new Image(), + paon: new Image(), + koala: new Image(), + belier: new Image(), + panda: new Image(), + girafe: new Image(), + bear: new Image(), + pingouin: new Image(), + singe: new Image() +}; + +animalImages.lion.src = 'tiles/lion.png'; +animalImages.elephant.src = 'tiles/elephant.png'; +animalImages.paon.src = 'tiles/paon.png'; +animalImages.koala.src = 'tiles/koala.png'; +animalImages.belier.src = 'tiles/belier.png'; +animalImages.panda.src = 'tiles/panda.png'; +animalImages.girafe.src = 'tiles/giraffe.png'; +animalImages.bear.src = 'tiles/bear.png'; +animalImages.pingouin.src = 'tiles/pingouin.png'; +animalImages.singe.src = 'tiles/singe.png'; + +// Player sprite +const player = { + image: new Image(), + x: 3, // Starting grid position + y: 10, + currentFloor: 2 +}; +player.image.src = 'tiles/player.png'; // You'll need to add this image + +const sky = { + image: new Image(), + x: 10, + y : 5, + currentFloor: 2, + isMoving: false, + targetX: 1, + targetY: 3, + barkAnimations: [] // Store active bark animations +}; +sky.image.src = 'tiles/sky.png'; // You'll need to add this image + +// Bark animation class +class BarkAnimation { + constructor(x, y, text = 'WAOUF!') { + this.x = x; + this.y = y; + this.text = text; + this.opacity = 1; + this.offsetY = 0; + this.lifetime = 0; + this.maxLifetime = 60; // frames (about 1 second at 60fps) + } + + update() { + this.lifetime++; + this.offsetY -= 1; // Move up + this.opacity = 1 - (this.lifetime / this.maxLifetime); + return this.lifetime < this.maxLifetime; + } + + draw(ctx) { + ctx.save(); + ctx.globalAlpha = this.opacity; + ctx.font = 'bold 30px Courier New'; + ctx.fillStyle = '#fff'; + ctx.strokeStyle = '#000'; + ctx.lineWidth = 3; + ctx.textAlign = 'center'; + const text = this.text; + ctx.strokeText(text, this.x, this.y + this.offsetY); + ctx.fillText(text, this.x, this.y + this.offsetY); + ctx.restore(); + } +} + +// Game state +const gameState = { + animalPuzzles: [ + // Floor 2 - 1 animal + { x: 1, y: 8, floor: 2, animal: 'panda', solved: false }, + // Floor 1 - 8 animals + { x: 12, y: 3, floor: 1, animal: 'lion', solved: false }, // cuisine table + { x: 1, y: 5, floor: 1, animal: 'koala', solved: false }, // livre + { x: 4, y: 1, floor: 1, animal: 'elephant', solved: false }, // bureau + { x: 14, y: 10, floor: 1, animal: 'paon', solved: false }, // climatiseur + { x: 3, y: 8, floor: 1, animal: 'belier', solved: false }, // table basse + { x: 8, y: 2, floor: 1, animal: 'bear', solved: false }, // salle de bain + { x: 11, y: 1, floor: 1, animal: 'pingouin', solved: false }, // cuisine + { x: 7, y: 7, floor: 1, animal: 'singe', solved: false }, // canapé + // Floor 0 - 1 animals + { x: 2, y: 8, floor: 0, animal: 'girafe', solved: false }, // salle grimpe sous sol + + + + ], + allPuzzlesSolved: false, + skyMessageShown: false, + treasureRevealed: false +}; + +// Multiple floors +const maps = [ + // Floor 0 (Basement Floor) + [ + ['wall', 'wall', 'wall', 'wall', 'wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','wall','floor','door','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','wall','floor','wall','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','wall','wall','wall','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'wall', 'wall', 'wall', 'wall', 'door','wall','wall','wall','wall','stair','wall','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'wall', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','door','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'door', 'wall', 'floor', 'floor', 'floor','floor','floor','wall','wall','door','wall','wall','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','wall','floor','floor','floor','floor','wall','floor', 'floor','wall','door','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','wall','floor','floor','floor','floor','wall','floor', 'floor','wall','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','wall','floor','floor','floor','floor','wall','floor', 'floor','wall','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','wall','floor','floor','floor','floor','wall','floor', 'floor','wall','floor','wall'], + ['wall', 'wall', 'wall', 'wall', 'wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall'], + ], + + // Floor 1 (Ground floor) + [ + ['wall', 'wall', 'wall', 'wind', 'wall','wall','wall','wind','wall','wall','wall','wind','door','wall','wall','wind','wall','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'wall','floor','floor','floor','wall','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'wall','floor','floor','floor','wall','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'wall','floor','floor','floor','wall','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'wall','wall','door','wall','wall','stair','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'door','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'wall', 'wall', 'wall', 'wall', 'wall','floor','wall','wall','wall','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','floor','floor','floor','floor','floor', 'wall','wall','door','wall'], + ['wind', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','floor','floor','floor','floor','floor', 'floor','wind','floor','floor'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','stair','floor','floor','floor','floor', 'floor','wall','floor','floor'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','wall','floor','floor'], + ['wall', 'wall', 'wall', 'wind', 'wind','wall','wall','wall','wall','door','wall','wall','wind','wind','wall','wall','floor','floor'], + ], + // Floor 2 (Upper floor) + [ + ['wall', 'wall', 'wall', 'wall', 'wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','floor','floor','floor','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','floor','wall','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','floor','wall','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','stair','wall','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'floor', 'floor', 'floor', 'floor', 'floor','floor','floor','wall','wall','wall','floor','floor','floor', 'floor','floor','floor','wall'], + ['wall', 'wall', 'wall', 'wall', 'wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall','wall'], + ] +]; + +let map = maps[player.currentFloor]; + +// Stair connections: define which stair position leads to which floor +// Format: { floor: { 'x,y': destinationFloor } } +const stairConnections = { + 0: { // Basement floor stairs + '10,1': 1, // Stair at x=10, y=1 goes to floor 1 + '10,2': 1, + '10,3': 1, + '10,4': 1, + '9,7': 1, // Stair at x=9, y=7 goes to floor 1 + '9,8': 1, + '9,9': 1, + '16,11': 1, // Stair at x=16, y=11 goes to floor 1 + '17,11': 1 + }, + 1: { // Ground floor stairs + '10,1': 0, // These stairs go down to basement + '10,2': 0, + '10,3': 0, + '10,4': 0, + '9,7': 2, // These stairs go up to floor 2 + '9,8': 2, + '9,9': 2, + '16,11': 2, // These stairs go up to floor 2 + '17,11': 2 + }, + 2: { // Upper floor stairs + '10,1': 1, // All stairs on floor 2 go down to floor 1 + '10,2': 1, + '10,3': 1, + '10,4': 1, + '9,7': 1, + '9,8': 1, + '9,9': 1, + '16,11': 1 + } +}; + +// Set canvas size based on map dimensions +canvas.width = map[0].length * tileSize; // number of columns * tile size +canvas.height = map.length * tileSize; // number of rows * tile size + +// Check if player can move to a position +function canMoveTo(x, y) { + if (y < 0 || y >= map.length || x < 0 || x >= map[0].length) { + return false; + } + const tile = map[y][x]; + // Player can walk on floor, door, and stair tiles + return tile === 'floor' || tile === 'door' || tile === 'stair'; +} + +// Check if player is on stairs and handle floor change +function checkStairs() { + const currentTile = map[player.y][player.x]; + if (currentTile === 'stair') { + const stairKey = `${player.x},${player.y}`; + const floorConnections = stairConnections[player.currentFloor]; + + if (floorConnections && floorConnections[stairKey] !== undefined) { + const targetFloor = floorConnections[stairKey]; + player.currentFloor = targetFloor; + map = maps[player.currentFloor]; + // Update canvas size for new floor + canvas.width = map[0].length * tileSize; + canvas.height = map.length * tileSize; + render(); + } + } +} + +// Une fonction pour dessiner la carte une fois que toutes les images sont chargées +function drawMap() { + for(let y = 0; y < map.length; y++) { + for(let x = 0; x < map[y].length; x++) { + const tileKey = map[y][x]; + const tileImage = tiles[tileKey]; + if(tileImage.complete) { // vérifier que l'image est chargée + ctx.drawImage(tileImage, x * tileSize, y * tileSize, tileSize, tileSize); + } else { + // Réessayer plus tard si image pas encore chargée + tileImage.onload = () => { + ctx.drawImage(tileImage, x * tileSize, y * tileSize, tileSize, tileSize); + }; + } + } + } +} + +// Draw a single tile at position +function drawTile(x, y) { + const tileKey = map[y][x]; + const tileImage = tiles[tileKey]; + if(tileImage.complete) { + ctx.drawImage(tileImage, x * tileSize, y * tileSize, tileSize, tileSize); + } + + // Draw animal sprite if this tile has a solved puzzle + const puzzle = gameState.animalPuzzles.find( + p => p.x === x && p.y === y && p.floor === player.currentFloor && p.solved + ); + if (puzzle && animalImages[puzzle.animal].complete) { + ctx.drawImage(animalImages[puzzle.animal], x * tileSize, y * tileSize, tileSize, tileSize); + } +} + +// Draw all solved animals on current floor +function drawAnimals() { + gameState.animalPuzzles.forEach(puzzle => { + if (puzzle.solved && puzzle.floor === player.currentFloor) { + const animalImg = animalImages[puzzle.animal]; + if (animalImg.complete) { + ctx.drawImage(animalImg, puzzle.x * tileSize, puzzle.y * tileSize, tileSize, tileSize); + } + } + }); +} + +// Draw player +function drawPlayer() { + if (player.image.complete) { + ctx.drawImage(player.image, player.x * tileSize, player.y * tileSize, tileSize, tileSize); + } +} + +// Draw sky +function drawSky() { + // Only draw sky if on the same floor as player + if (sky.currentFloor === player.currentFloor && sky.image.complete) { + ctx.drawImage(sky.image, sky.x * tileSize, sky.y * tileSize, tileSize, tileSize); + } +} + +// Draw bark animations +function drawBarkAnimations() { + // Update and draw all active bark animations + sky.barkAnimations = sky.barkAnimations.filter(bark => { + const isAlive = bark.update(); + if (isAlive) { + bark.draw(ctx); + } + return isAlive; + }); +} + +// Create a bark animation at Sky's position +function createBark() { + // Pixel position centered horizontally above Sky + const barkX = (sky.x + 0.5) * tileSize; // center of tile + const barkY = sky.y * tileSize - 10; // slightly above + // Distance check (Manhattan) for heart proximity + const distance = Math.abs(player.x - sky.x) + Math.abs(player.y - sky.y); + const isPlayerNear = (player.currentFloor === sky.currentFloor) && (distance <= 2); + // Choose text + const text = isPlayerNear ? '❤️' : 'WAOUF!'; + // Push animation with pixel coordinates + sky.barkAnimations.push(new BarkAnimation(barkX, barkY, text)); +} + +// Start random barking +let barkInterval = null; + +function startRandomBarking() { + if (barkInterval) return; // Already barking + + function scheduleBark() { + createBark(); + // Schedule next bark between 1-3 seconds + const nextBarkTime = 500 + Math.random() * 1000; // 1000ms to 3000ms + barkInterval = setTimeout(scheduleBark, nextBarkTime); + } + + scheduleBark(); +} + +function stopRandomBarking() { + if (barkInterval) { + clearTimeout(barkInterval); + barkInterval = null; + } +} + +// Update animal counter display// Update animal counter display +function updateAnimalCounter() { + const foundCount = gameState.animalPuzzles.filter(p => p.solved).length; + const counterElement = document.getElementById('foundCount'); + if (counterElement) { + counterElement.textContent = foundCount; + } +} + +// Render everything (used for initial load and floor changes) +function render() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawMap(); + drawAnimals(); + drawSky(); + drawPlayer(); + drawBarkAnimations(); + updateAnimalCounter(); +} + +// Update player position (more efficient - only redraws affected tiles) +function updatePlayer(oldX, oldY) { + // Redraw the old position (just the tile, which includes any animal sprite) + drawTile(oldX, oldY); + // Redraw sky if they were on the same tile + if (sky.currentFloor === player.currentFloor && sky.x === oldX && sky.y === oldY) { + drawSky(); + } + // Draw the tile at new position first (which includes any animal sprite) + drawTile(player.x, player.y); + // Redraw sky if they're on the same tile now + if (sky.currentFloor === player.currentFloor && sky.x === player.x && sky.y === player.y) { + drawSky(); + } + // Draw player on top + drawPlayer(); + // Draw bark animations + drawBarkAnimations(); + // Update counter + updateAnimalCounter(); +} + +// Check if player is on an animal puzzle tile +function checkAnimalPuzzle() { + const puzzle = gameState.animalPuzzles.find( + p => p.x === player.x && p.y === player.y && p.floor === player.currentFloor && !p.solved + ); + + if (puzzle) { + currentPuzzle = puzzle; + showMessage("Un animal a été trouvé ici ! Lequel ?", true); + } +} + +// Check if player is next to Sky +function checkNearSky() { + if (player.currentFloor !== sky.currentFloor) return false; + + const dx = Math.abs(player.x - sky.x); + const dy = Math.abs(player.y - sky.y); + + return (dx === 1 && dy === 0) || (dx === 0 && dy === 1); +} + +// Check if all puzzles are solved +function checkAllPuzzlesSolved() { + return gameState.animalPuzzles.every(p => p.solved); +} + +// Messages personnalisés pour chaque animal +const animalMessages = { + lion: "La savane te reconnaît, ta puissance se fait entendre comme un rugissement au milieu des tempêtes. 🦁", + elephant: "Ta mémoire est ton armure, chaque pas sur ce territoire est une preuve de ta sagesse silencieuse. 🐘", + paon: "Sous la lumière, tu déploies ta beauté, chaque plume racontant ta vérité sans craindre le regard des autres. 🦚", + koala: "Même suspendue entre deux mondes, tu trouves douceur et réconfort, là où d'autres n'oseraient se poser. 🐨", + belier: "Rien ne t'arrête, tu tiens bon face au vent et défends ce qui t'est cher par ta simple présence. 🐏", + panda: "La paix t'accompagne, tu trouves le calme au cœur des forêts, là où tu peux enfin respirer. 🐼", + girafe: "Depuis les nuages, tu observes, toujours en avant-garde, cherchant ce qui se cache là où personne ne va. 🦒", + bear: "Dans la nuit profonde, tu sais où trouver refuge, seule, mais jamais abattue. 🐻", + pingouin: "Même dans le froid, tu restes debout, ta joie n'est jamais perdue même lorsque la glace s'étend partout. 🐧", + singe: "Agile et rusée, tu t'adaptes à chaque branche, n'abandonnant jamais ton sourire malgré les obstacles. 🐒" +}; + + + + +// Show message to player +let currentPuzzle = null; +function showMessage(text, showButtons = false) { + const messageBox = document.getElementById('messageBox'); + const messageText = document.getElementById('messageText'); + const animalButtons = document.getElementById('animalButtons'); + const closeButton = document.getElementById('closeButton'); + + messageText.textContent = text; + messageBox.classList.remove('hidden'); + + if (showButtons) { + animalButtons.classList.remove('hidden'); + closeButton.classList.add('hidden'); + } else { + animalButtons.classList.add('hidden'); + closeButton.classList.remove('hidden'); + } +} + +// Select animal for puzzle +function selectAnimal(animal) { + if (!currentPuzzle) return; + + if (currentPuzzle.animal === animal) { + // Correct answer + currentPuzzle.solved = true; + closeMessage(); + + // Show personalized message for the animal + const personalizedMessage = animalMessages[animal] || `Bravo ! Le ${animal} est bien placé !`; + showMessage(personalizedMessage, false); + + // Check if all puzzles solved + if (checkAllPuzzlesSolved() && !gameState.allPuzzlesSolved) { + gameState.allPuzzlesSolved = true; + setTimeout(() => { + closeMessage(); + showMessage("Bravo, tu as replacé tous les animaux ! Va maintenant voir Sky pour trouver le trésor !", false); + }, 2000); + } + } else { + // Wrong answer - restart game + closeMessage(); + showMessage("Non, tu t'es trompeé !! Retour à la case départ ! ", false); + setTimeout(() => { + restartGame(); + }, 2000); + } + + currentPuzzle = null; +} + +// Close message +function closeMessage() { + const messageBox = document.getElementById('messageBox'); + messageBox.classList.add('hidden'); +} + +// Restart game +function restartGame() { + // Reset player position + player.x = 3; + player.y = 10; + player.currentFloor = 2; + + // Reset sky position + sky.x = 1; + sky.y = 3; + sky.currentFloor = 2; + sky.isMoving = false; + + // Reset game state + gameState.animalPuzzles.forEach(p => p.solved = false); + gameState.allPuzzlesSolved = false; + gameState.skyMessageShown = false; + gameState.treasureRevealed = false; + + // Stop sky random movement if active + if (skyRandomMovementInterval) { + clearInterval(skyRandomMovementInterval); + skyRandomMovementInterval = null; + } + + // Stop barking + stopRandomBarking(); + + // Clear existing bark animations + sky.barkAnimations = []; + + // Reset map and render + map = maps[player.currentFloor]; + canvas.width = map[0].length * tileSize; + canvas.height = map.length * tileSize; + closeMessage(); + render(); + + // Restart Sky's random movement + startSkyRandomMovement(); + + // Restart barking + setTimeout(() => { + startRandomBarking(); + }, 2000); +} + +// Random movement for Sky +let skyRandomMovementInterval = null; + +function startSkyRandomMovement() { + if (skyRandomMovementInterval) return; // Already moving + + skyRandomMovementInterval = setInterval(() => { + if (sky.isMoving || gameState.treasureRevealed) return; + + // Random direction + const directions = ['up', 'down', 'left', 'right']; + const randomDir = directions[Math.floor(Math.random() * directions.length)]; + + let newX = sky.x; + let newY = sky.y; + + switch(randomDir) { + case 'up': + newY--; + break; + case 'down': + newY++; + break; + case 'left': + newX--; + break; + case 'right': + newX++; + break; + } + + // Check if Sky can move to that position (same rules as player) + const currentMap = maps[sky.currentFloor]; + if (newY >= 0 && newY < currentMap.length && newX >= 0 && newX < currentMap[0].length) { + const tile = currentMap[newY][newX]; + if (tile === 'floor' || tile === 'door' || tile === 'stair') { + const oldX = sky.x; + const oldY = sky.y; + sky.x = newX; + sky.y = newY; + + // Render if player is on same floor + if (player.currentFloor === sky.currentFloor) { + // Redraw old position + drawTile(oldX, oldY); + // Redraw new position with sky + drawTile(sky.x, sky.y); + drawSky(); + drawPlayer(); + } + } + } + }, 500); // Move every 500ms +} + +function stopSkyRandomMovement() { + if (skyRandomMovementInterval) { + clearInterval(skyRandomMovementInterval); + skyRandomMovementInterval = null; + } +} + +// Move Sky automatically +function moveSkyToTreasure() { + sky.isMoving = true; + + // Path: go to stairs at (9, 7), then to final position + const path = [ + { x: 9, y: 7, floor: 2 }, // Stairs on floor 2 + { x: 9, y: 7, floor: 1 }, // After taking stairs to floor 1 + { x: 13, y: 3, floor: 1 }, // After taking stairs to floor 1 + { x: 10, y: 4, floor: 1 }, // After taking stairs to floor 1 + { x: 10, y: 4, floor: 0 }, // After taking stairs to floor 1 + { x: 10, y: 5, floor: 0 }, // After taking stairs to floor 1 + { x: 12, y: 4, floor: 0 } // Final treasure position + ]; + + let pathIndex = 0; + + const moveInterval = setInterval(() => { + if (pathIndex >= path.length) { + clearInterval(moveInterval); + sky.isMoving = false; + gameState.treasureRevealed = true; + render(); + return; + } + + const target = path[pathIndex]; + + // Change floor if needed + if (target.floor !== sky.currentFloor) { + sky.currentFloor = target.floor; + map = maps[player.currentFloor]; + } + + // Move towards target + if (sky.x < target.x) sky.x++; + else if (sky.x > target.x) sky.x--; + else if (sky.y < target.y) sky.y++; + else if (sky.y > target.y) sky.y--; + + // Check if reached target + if (sky.x === target.x && sky.y === target.y) { + pathIndex++; + } + + render(); + }, 500); +} + +// Move player function (used by both keyboard and touch) +function movePlayer(direction) { + // Don't allow movement until game has started + if (!gameStarted) return; + + let newX = player.x; + let newY = player.y; + + switch(direction) { + case 'up': + newY--; + break; + case 'down': + newY++; + break; + case 'left': + newX--; + break; + case 'right': + newX++; + break; + } + + // Check if move is valid + if (canMoveTo(newX, newY)) { + const oldX = player.x; + const oldY = player.y; + player.x = newX; + player.y = newY; + updatePlayer(oldX, oldY); + checkStairs(); + + // Check for animal puzzles + checkAnimalPuzzle(); + + // Check if near Sky after all puzzles solved + if (gameState.allPuzzlesSolved && !gameState.skyMessageShown && checkNearSky()) { + gameState.skyMessageShown = true; + stopSkyRandomMovement(); // Stop Sky from moving randomly + showMessage("Wouaf Wouaf ! Suis moi pour trouver le trésor final !", false); + setTimeout(() => { + closeMessage(); + moveSkyToTreasure(); + }, 2000); + } + + // Check if found Sky at treasure location + if (gameState.treasureRevealed && checkNearSky()) { + showMessage("Sky est au pied du trésor mais ces petites papattes sont trop courtes, va donc l'aider à le retrouver dans la vraie arène !", false); + } + } +} + +// Handle keyboard input +document.addEventListener('keydown', (e) => { + switch(e.key) { + case 'ArrowUp': + movePlayer('up'); + e.preventDefault(); + break; + case 'ArrowDown': + movePlayer('down'); + e.preventDefault(); + break; + case 'ArrowLeft': + movePlayer('left'); + e.preventDefault(); + break; + case 'ArrowRight': + movePlayer('right'); + e.preventDefault(); + break; + } +}); + +// Touch controls for mobile +let touchStartX = 0; +let touchStartY = 0; +let touchEndX = 0; +let touchEndY = 0; + +canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + touchStartX = e.changedTouches[0].screenX; + touchStartY = e.changedTouches[0].screenY; +}, { passive: false }); + +canvas.addEventListener('touchend', (e) => { + e.preventDefault(); + touchEndX = e.changedTouches[0].screenX; + touchEndY = e.changedTouches[0].screenY; + handleSwipe(); +}, { passive: false }); + +function handleSwipe() { + const deltaX = touchEndX - touchStartX; + const deltaY = touchEndY - touchStartY; + const minSwipeDistance = 30; // Minimum distance for a swipe + + // Determine if horizontal or vertical swipe + if (Math.abs(deltaX) > Math.abs(deltaY)) { + // Horizontal swipe + if (Math.abs(deltaX) > minSwipeDistance) { + if (deltaX > 0) { + movePlayer('right'); + } else { + movePlayer('left'); + } + } + } else { + // Vertical swipe + if (Math.abs(deltaY) > minSwipeDistance) { + if (deltaY > 0) { + movePlayer('down'); + } else { + movePlayer('up'); + } + } + } +} + +// Attendre que toutes les images soient chargées avant de dessiner +let imagesLoaded = 0; +const totalImages = Object.keys(tiles).length + Object.keys(animalImages).length + 2; // tiles + animals + player + sky + +function checkAllImagesLoaded() { + imagesLoaded++; + if(imagesLoaded === totalImages) { + render(); + // Start Sky's random movement when game loads + setTimeout(() => { + startSkyRandomMovement(); + }, 100); + // Start animation loop for bark animations + startAnimationLoop(); + // Start random barking + setTimeout(() => { + startRandomBarking(); + }, 2000); // Start barking after 2 seconds + } +} + +// Animation loop for continuous bark animations +function startAnimationLoop() { + function animate() { + // Redraw everything if there are active animations + if (sky.barkAnimations.length > 0) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawMap(); + drawAnimals(); + drawSky(); + drawPlayer(); + drawBarkAnimations(); + } + requestAnimationFrame(animate); + } + animate(); +} + +for (const key in tiles) { + tiles[key].onload = checkAllImagesLoaded; +} + +for (const key in animalImages) { + animalImages[key].onload = checkAllImagesLoaded; +} + +player.image.onload = checkAllImagesLoaded; +sky.image.onload = checkAllImagesLoaded; + +// Make functions globally accessible for HTML onclick +window.selectAnimal = selectAnimal; +window.closeMessage = closeMessage; +window.startGame = startGame; diff --git a/style.css b/style.css new file mode 100644 index 0000000..8afa0eb --- /dev/null +++ b/style.css @@ -0,0 +1,387 @@ +body { + font-family: 'Courier New', monospace; + text-align: center; + margin: 0; + padding: 0; + overflow: hidden; + background: linear-gradient(180deg, #2c1810 0%, #4a2c1a 100%); + color: #f4e4c1; + height: 100vh; + display: flex; + flex-direction: column; +} + +/* Start Screen */ +.start-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, #1a0f08 0%, #2c1810 50%, #4a2c1a 100%); + display: flex; + justify-content: center; + align-items: center; + z-index: 2000; + animation: fadeIn 0.5s ease-in; +} + +.start-screen.hidden { + display: none; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.start-content { + text-align: center; + padding: 30px; + max-width: 600px; +} + +.start-content h1 { + font-size: clamp(1.3em, 5vw, 2.3em); + color: #d4af37; + text-shadow: + 3px 3px 0px #000, + 0 0 20px rgba(212, 175, 55, 0.8); + margin-bottom: 20px; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.start-content h2 { + font-size: clamp(1.2em, 4vw, 1.8em); + color: #ffd700; + text-shadow: 2px 2px 0px #000; + margin: 20px 0; +} + +.start-description { + font-size: clamp(1em, 3vw, 1.2em); + color: #e8d4a0; + text-shadow: 1px 1px 0px #000; + line-height: 1.1; + margin: 30px 0; +} + +.start-btn { + padding: 15px 20px; + font-size: clamp(1.1em, 3vw, 1.5em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 4px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: + 0 6px 0 #5a4010, + 0 8px 15px rgba(0, 0, 0, 0.5), + 0 0 30px rgba(212, 175, 55, 0.5); + letter-spacing: 2px; + margin-top: 20px; + transition: all 0.2s; + animation: glow 2s ease-in-out infinite; +} + +@keyframes glow { + 0%, 100% { box-shadow: 0 6px 0 #5a4010, 0 8px 15px rgba(0, 0, 0, 0.5), 0 0 30px rgba(212, 175, 55, 0.5); } + 50% { box-shadow: 0 6px 0 #5a4010, 0 8px 15px rgba(0, 0, 0, 0.5), 0 0 40px rgba(255, 215, 0, 0.8); } +} + +.start-btn:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(3px); + box-shadow: + 0 3px 0 #5a4010, + 0 5px 10px rgba(0, 0, 0, 0.5), + 0 0 40px rgba(255, 215, 0, 0.8); +} + +.start-btn:active { + transform: translateY(6px); + box-shadow: + 0 0px 0 #5a4010, + 0 2px 5px rgba(0, 0, 0, 0.5), + 0 0 20px rgba(212, 175, 55, 0.5); +} + +@media (max-width: 480px) { + .start-content { + padding: 15px; + } + + .start-btn { + padding: 12px 30px; + } +} +.header-content { + margin: 10px 0; +} + +.header-content h1 { + font-size: clamp(1.5em, 5vw, 2.5em); + color: #d4af37; + text-shadow: + 3px 3px 0px #000, + 0 0 20px rgba(212, 175, 55, 0.8); + margin-bottom: 20px; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.header-content h2 { + font-size: clamp(1.2em, 4vw, 1.8em); + color: #ffd700; + text-shadow: 2px 2px 0px #000; + margin: 20px 0; +} + +h1 { + margin: 5px 0; + font-size: clamp(1em, 3vw, 1.4em); + font-weight: bold; + text-shadow: 3px 3px 0px #000; + color: #d4af37; + letter-spacing: 2px; + text-transform: uppercase; + padding: 5px; +} + +h3 { + color: #e8d4a0; + text-shadow: 2px 2px 0px #000; + font-size: clamp(0.9em, 2.5vw, 1.1em); + margin: 5px 0; + padding: 0 10px; +} + +p { + color: #d4c5a0; + text-shadow: 1px 1px 0px #000; + font-size: clamp(0.8em, 2vw, 1em); + margin: 5px 0; + padding: 0 10px; +} + +canvas { + border: 4px solid #8b6914; + box-shadow: + 0 0 0 2px #d4af37, + 0 0 20px rgba(0, 0, 0, 0.8), + inset 0 0 10px rgba(0, 0, 0, 0.5); + background-color: #2a1a0a; + max-width: 100vw; + max-height: calc(100vh - 180px); + width: auto; + height: auto; + display: block; + margin: 5px auto; + image-rendering: pixelated; + image-rendering: crisp-edges; + flex-shrink: 1; +} + +@media (max-width: 768px) { + canvas { + max-height: calc(100vh - 200px); + border: 3px solid #8b6914; + } + + h1 { + margin: 3px 0; + } +} + +@media (max-width: 480px) { + canvas { + max-height: calc(100vh - 220px); + border: 2px solid #8b6914; + } +} + +/* Animal counter */ +.animal-counter { + font-family: 'Courier New', monospace; + font-size: clamp(1em, 3vw, 1.3em); + font-weight: bold; + margin: 5px 0; + padding: 8px 15px; + background: linear-gradient(180deg, #8b6914 0%, #6b5010 100%); + color: #f4e4c1; + border: 3px solid #d4af37; + box-shadow: + 0 4px 0 #5a4010, + 0 6px 10px rgba(0, 0, 0, 0.5); + display: inline-block; + text-shadow: 2px 2px 0px #000; + letter-spacing: 1px; +} + +@media (max-width: 480px) { + .animal-counter { + padding: 6px 12px; + border: 2px solid #d4af37; + margin: 3px 0; + } +} + +#foundCount { + font-size: 1.3em; + color: #ffd700; + text-shadow: 0 0 5px #ff0, 2px 2px 0px #000; +} + +/* Message box styles */ +.message-box { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(2px); +} + +.message-box.hidden { + display: none; +} + +.message-content { + background: linear-gradient(180deg, #3d2a1a 0%, #2c1810 100%); + padding: 20px; + border: 4px solid #d4af37; + box-shadow: + 0 0 0 2px #8b6914, + 0 0 30px rgba(212, 175, 55, 0.5), + inset 0 0 20px rgba(0, 0, 0, 0.5); + max-width: 90vw; + width: 600px; + max-height: 90vh; + overflow-y: auto; + text-align: center; + font-family: 'Courier New', monospace; +} + +@media (max-width: 768px) { + .message-content { + padding: 15px; + border: 3px solid #d4af37; + } +} + +.message-content p { + font-size: clamp(1em, 3vw, 1.2em); + margin-bottom: 15px; + color: #f4e4c1; + text-shadow: 2px 2px 0px #000; + line-height: 1.5; +} + +.animal-buttons { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px; + margin-bottom: 20px; +} + +@media (max-width: 768px) { + .animal-buttons { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 480px) { + .animal-buttons { + grid-template-columns: repeat(2, 1fr); + } +} + +.animal-buttons.hidden { + display: none; +} + +.animal-buttons button { + padding: 8px 4px; + font-size: clamp(0.75em, 2vw, 0.9em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 3px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: 0 3px 0 #5a4010, 0 4px 8px rgba(0, 0, 0, 0.5); + position: relative; +} + +@media (max-width: 480px) { + .animal-buttons button { + padding: 6px 3px; + border: 2px solid #8b6914; + } +} + +.animal-buttons button:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(2px); + box-shadow: 0 1px 0 #5a4010, 0 2px 4px rgba(0, 0, 0, 0.5); +} + +.animal-buttons button:active { + transform: translateY(3px); + box-shadow: 0 0px 0 #5a4010, 0 1px 2px rgba(0, 0, 0, 0.5); +} + +.close-btn { + padding: 10px 25px; + font-size: clamp(1em, 3vw, 1.2em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 3px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: 0 4px 0 #5a4010, 0 6px 10px rgba(0, 0, 0, 0.5); + letter-spacing: 1px; +} + +@media (max-width: 480px) { + .close-btn { + padding: 8px 20px; + border: 2px solid #8b6914; + } +} + +.close-btn.hidden { + display: none; +} + +.close-btn:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(2px); + box-shadow: 0 2px 0 #5a4010, 0 4px 8px rgba(0, 0, 0, 0.5); +} + +.close-btn:active { + transform: translateY(4px); + box-shadow: 0 0px 0 #5a4010, 0 2px 4px rgba(0, 0, 0, 0.5); +} diff --git a/style.css.backup b/style.css.backup new file mode 100644 index 0000000..8afa0eb --- /dev/null +++ b/style.css.backup @@ -0,0 +1,387 @@ +body { + font-family: 'Courier New', monospace; + text-align: center; + margin: 0; + padding: 0; + overflow: hidden; + background: linear-gradient(180deg, #2c1810 0%, #4a2c1a 100%); + color: #f4e4c1; + height: 100vh; + display: flex; + flex-direction: column; +} + +/* Start Screen */ +.start-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, #1a0f08 0%, #2c1810 50%, #4a2c1a 100%); + display: flex; + justify-content: center; + align-items: center; + z-index: 2000; + animation: fadeIn 0.5s ease-in; +} + +.start-screen.hidden { + display: none; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.start-content { + text-align: center; + padding: 30px; + max-width: 600px; +} + +.start-content h1 { + font-size: clamp(1.3em, 5vw, 2.3em); + color: #d4af37; + text-shadow: + 3px 3px 0px #000, + 0 0 20px rgba(212, 175, 55, 0.8); + margin-bottom: 20px; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.start-content h2 { + font-size: clamp(1.2em, 4vw, 1.8em); + color: #ffd700; + text-shadow: 2px 2px 0px #000; + margin: 20px 0; +} + +.start-description { + font-size: clamp(1em, 3vw, 1.2em); + color: #e8d4a0; + text-shadow: 1px 1px 0px #000; + line-height: 1.1; + margin: 30px 0; +} + +.start-btn { + padding: 15px 20px; + font-size: clamp(1.1em, 3vw, 1.5em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 4px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: + 0 6px 0 #5a4010, + 0 8px 15px rgba(0, 0, 0, 0.5), + 0 0 30px rgba(212, 175, 55, 0.5); + letter-spacing: 2px; + margin-top: 20px; + transition: all 0.2s; + animation: glow 2s ease-in-out infinite; +} + +@keyframes glow { + 0%, 100% { box-shadow: 0 6px 0 #5a4010, 0 8px 15px rgba(0, 0, 0, 0.5), 0 0 30px rgba(212, 175, 55, 0.5); } + 50% { box-shadow: 0 6px 0 #5a4010, 0 8px 15px rgba(0, 0, 0, 0.5), 0 0 40px rgba(255, 215, 0, 0.8); } +} + +.start-btn:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(3px); + box-shadow: + 0 3px 0 #5a4010, + 0 5px 10px rgba(0, 0, 0, 0.5), + 0 0 40px rgba(255, 215, 0, 0.8); +} + +.start-btn:active { + transform: translateY(6px); + box-shadow: + 0 0px 0 #5a4010, + 0 2px 5px rgba(0, 0, 0, 0.5), + 0 0 20px rgba(212, 175, 55, 0.5); +} + +@media (max-width: 480px) { + .start-content { + padding: 15px; + } + + .start-btn { + padding: 12px 30px; + } +} +.header-content { + margin: 10px 0; +} + +.header-content h1 { + font-size: clamp(1.5em, 5vw, 2.5em); + color: #d4af37; + text-shadow: + 3px 3px 0px #000, + 0 0 20px rgba(212, 175, 55, 0.8); + margin-bottom: 20px; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.header-content h2 { + font-size: clamp(1.2em, 4vw, 1.8em); + color: #ffd700; + text-shadow: 2px 2px 0px #000; + margin: 20px 0; +} + +h1 { + margin: 5px 0; + font-size: clamp(1em, 3vw, 1.4em); + font-weight: bold; + text-shadow: 3px 3px 0px #000; + color: #d4af37; + letter-spacing: 2px; + text-transform: uppercase; + padding: 5px; +} + +h3 { + color: #e8d4a0; + text-shadow: 2px 2px 0px #000; + font-size: clamp(0.9em, 2.5vw, 1.1em); + margin: 5px 0; + padding: 0 10px; +} + +p { + color: #d4c5a0; + text-shadow: 1px 1px 0px #000; + font-size: clamp(0.8em, 2vw, 1em); + margin: 5px 0; + padding: 0 10px; +} + +canvas { + border: 4px solid #8b6914; + box-shadow: + 0 0 0 2px #d4af37, + 0 0 20px rgba(0, 0, 0, 0.8), + inset 0 0 10px rgba(0, 0, 0, 0.5); + background-color: #2a1a0a; + max-width: 100vw; + max-height: calc(100vh - 180px); + width: auto; + height: auto; + display: block; + margin: 5px auto; + image-rendering: pixelated; + image-rendering: crisp-edges; + flex-shrink: 1; +} + +@media (max-width: 768px) { + canvas { + max-height: calc(100vh - 200px); + border: 3px solid #8b6914; + } + + h1 { + margin: 3px 0; + } +} + +@media (max-width: 480px) { + canvas { + max-height: calc(100vh - 220px); + border: 2px solid #8b6914; + } +} + +/* Animal counter */ +.animal-counter { + font-family: 'Courier New', monospace; + font-size: clamp(1em, 3vw, 1.3em); + font-weight: bold; + margin: 5px 0; + padding: 8px 15px; + background: linear-gradient(180deg, #8b6914 0%, #6b5010 100%); + color: #f4e4c1; + border: 3px solid #d4af37; + box-shadow: + 0 4px 0 #5a4010, + 0 6px 10px rgba(0, 0, 0, 0.5); + display: inline-block; + text-shadow: 2px 2px 0px #000; + letter-spacing: 1px; +} + +@media (max-width: 480px) { + .animal-counter { + padding: 6px 12px; + border: 2px solid #d4af37; + margin: 3px 0; + } +} + +#foundCount { + font-size: 1.3em; + color: #ffd700; + text-shadow: 0 0 5px #ff0, 2px 2px 0px #000; +} + +/* Message box styles */ +.message-box { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(2px); +} + +.message-box.hidden { + display: none; +} + +.message-content { + background: linear-gradient(180deg, #3d2a1a 0%, #2c1810 100%); + padding: 20px; + border: 4px solid #d4af37; + box-shadow: + 0 0 0 2px #8b6914, + 0 0 30px rgba(212, 175, 55, 0.5), + inset 0 0 20px rgba(0, 0, 0, 0.5); + max-width: 90vw; + width: 600px; + max-height: 90vh; + overflow-y: auto; + text-align: center; + font-family: 'Courier New', monospace; +} + +@media (max-width: 768px) { + .message-content { + padding: 15px; + border: 3px solid #d4af37; + } +} + +.message-content p { + font-size: clamp(1em, 3vw, 1.2em); + margin-bottom: 15px; + color: #f4e4c1; + text-shadow: 2px 2px 0px #000; + line-height: 1.5; +} + +.animal-buttons { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px; + margin-bottom: 20px; +} + +@media (max-width: 768px) { + .animal-buttons { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 480px) { + .animal-buttons { + grid-template-columns: repeat(2, 1fr); + } +} + +.animal-buttons.hidden { + display: none; +} + +.animal-buttons button { + padding: 8px 4px; + font-size: clamp(0.75em, 2vw, 0.9em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 3px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: 0 3px 0 #5a4010, 0 4px 8px rgba(0, 0, 0, 0.5); + position: relative; +} + +@media (max-width: 480px) { + .animal-buttons button { + padding: 6px 3px; + border: 2px solid #8b6914; + } +} + +.animal-buttons button:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(2px); + box-shadow: 0 1px 0 #5a4010, 0 2px 4px rgba(0, 0, 0, 0.5); +} + +.animal-buttons button:active { + transform: translateY(3px); + box-shadow: 0 0px 0 #5a4010, 0 1px 2px rgba(0, 0, 0, 0.5); +} + +.close-btn { + padding: 10px 25px; + font-size: clamp(1em, 3vw, 1.2em); + font-family: 'Courier New', monospace; + font-weight: bold; + border: 3px solid #8b6914; + background: linear-gradient(180deg, #d4af37 0%, #b8941f 100%); + color: #2c1810; + cursor: pointer; + text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.5); + box-shadow: 0 4px 0 #5a4010, 0 6px 10px rgba(0, 0, 0, 0.5); + letter-spacing: 1px; +} + +@media (max-width: 480px) { + .close-btn { + padding: 8px 20px; + border: 2px solid #8b6914; + } +} + +.close-btn.hidden { + display: none; +} + +.close-btn:hover { + background: linear-gradient(180deg, #ffd700 0%, #d4af37 100%); + transform: translateY(2px); + box-shadow: 0 2px 0 #5a4010, 0 4px 8px rgba(0, 0, 0, 0.5); +} + +.close-btn:active { + transform: translateY(4px); + box-shadow: 0 0px 0 #5a4010, 0 2px 4px rgba(0, 0, 0, 0.5); +} diff --git a/tiles/bear.png b/tiles/bear.png new file mode 100644 index 0000000..4b88f96 Binary files /dev/null and b/tiles/bear.png differ diff --git a/tiles/belier.png b/tiles/belier.png new file mode 100644 index 0000000..25b5b27 Binary files /dev/null and b/tiles/belier.png differ diff --git a/tiles/door.png b/tiles/door.png new file mode 100644 index 0000000..fad8bef Binary files /dev/null and b/tiles/door.png differ diff --git a/tiles/elephant.png b/tiles/elephant.png new file mode 100644 index 0000000..b5e3b51 Binary files /dev/null and b/tiles/elephant.png differ diff --git a/tiles/giraffe.png b/tiles/giraffe.png new file mode 100644 index 0000000..8ffca01 Binary files /dev/null and b/tiles/giraffe.png differ diff --git a/tiles/koala.png b/tiles/koala.png new file mode 100644 index 0000000..a86d3d3 Binary files /dev/null and b/tiles/koala.png differ diff --git a/tiles/lion.png b/tiles/lion.png new file mode 100644 index 0000000..0db743e Binary files /dev/null and b/tiles/lion.png differ diff --git a/tiles/panda.png b/tiles/panda.png new file mode 100644 index 0000000..65fb8f0 Binary files /dev/null and b/tiles/panda.png differ diff --git a/tiles/paon.png b/tiles/paon.png new file mode 100644 index 0000000..843e0fa Binary files /dev/null and b/tiles/paon.png differ diff --git a/tiles/pingouin.png b/tiles/pingouin.png new file mode 100644 index 0000000..4b3b957 Binary files /dev/null and b/tiles/pingouin.png differ diff --git a/tiles/player.png b/tiles/player.png new file mode 100644 index 0000000..75f2f73 Binary files /dev/null and b/tiles/player.png differ diff --git a/tiles/singe.png b/tiles/singe.png new file mode 100644 index 0000000..86d716a Binary files /dev/null and b/tiles/singe.png differ diff --git a/tiles/sky.png b/tiles/sky.png new file mode 100644 index 0000000..f139d24 Binary files /dev/null and b/tiles/sky.png differ diff --git a/tiles/sol.png b/tiles/sol.png new file mode 100644 index 0000000..c1f3014 Binary files /dev/null and b/tiles/sol.png differ diff --git a/tiles/stairs.png b/tiles/stairs.png new file mode 100644 index 0000000..c7fa572 Binary files /dev/null and b/tiles/stairs.png differ diff --git a/tiles/tileset.png b/tiles/tileset.png new file mode 100644 index 0000000..81cec58 Binary files /dev/null and b/tiles/tileset.png differ diff --git a/tiles/wall.png b/tiles/wall.png new file mode 100644 index 0000000..300fdb8 Binary files /dev/null and b/tiles/wall.png differ diff --git a/tiles/wind.png b/tiles/wind.png new file mode 100644 index 0000000..3146105 Binary files /dev/null and b/tiles/wind.png differ