Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ethereal Workshop Battle Royale</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: #0a0a1a; | |
| color: #fff; | |
| overflow-x: hidden; | |
| } | |
| .game-container { | |
| width: 100%; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| font-size: 2.5rem; | |
| color: #c9a0ff; | |
| text-shadow: 0 0 10px rgba(201, 160, 255, 0.6); | |
| } | |
| .subtitle { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| font-size: 1.2rem; | |
| color: #8a7aa9; | |
| } | |
| .battle-arena { | |
| position: relative; | |
| width: 100%; | |
| height: 600px; | |
| background-image: url('https://static.wikia.nocookie.net/mysingingmonsters/images/4/4b/Ethereal_Workshop_Empty.png'); | |
| background-size: cover; | |
| background-position: center; | |
| border-radius: 12px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 5px 25px rgba(201, 160, 255, 0.3); | |
| overflow: hidden; | |
| } | |
| .monster { | |
| position: absolute; | |
| width: 100px; | |
| height: 100px; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| transition: all 0.3s ease; | |
| filter: drop-shadow(0 0 8px rgba(201, 160, 255, 0.7)); | |
| z-index: 5; | |
| cursor: pointer; | |
| } | |
| .monster.attacking { | |
| filter: drop-shadow(0 0 15px rgba(255, 86, 86, 0.9)); | |
| transform: scale(1.2); | |
| } | |
| .monster.damaged { | |
| filter: drop-shadow(0 0 15px rgba(255, 0, 0, 0.9)); | |
| opacity: 0.7; | |
| } | |
| .monster.eliminated { | |
| opacity: 0.3; | |
| filter: grayscale(100%); | |
| pointer-events: none; | |
| } | |
| .sword { | |
| position: absolute; | |
| width: 40px; | |
| height: 40px; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23c9a0ff'%3E%3Cpath d='M14.5,15.88L19.64,21L21,19.64L15.89,14.5L16.24,14.16L21.7,9.5L19.84,7.64L14.28,13.19L13.93,13.54L13.59,13.19L8.7,8.3L11.24,5.76L7.45,2L2,7.45L5.82,11.27L8.3,8.79L13.54,14.04L13.19,14.39L7.64,19.93L9.5,21.8L14.16,17.13L14.5,16.79V15.88Z'/%3E%3C/svg%3E"); | |
| transform: rotate(45deg); | |
| opacity: 0; | |
| z-index: 4; | |
| transition: opacity 0.2s; | |
| } | |
| .health-bar-container { | |
| position: absolute; | |
| bottom: -15px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 80px; | |
| height: 10px; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| border-radius: 5px; | |
| overflow: hidden; | |
| } | |
| .health-bar { | |
| height: 100%; | |
| width: 100%; | |
| background-color: #2ecc71; | |
| transition: width 0.3s ease; | |
| } | |
| .battle-log { | |
| background-color: rgba(20, 20, 40, 0.8); | |
| border-radius: 8px; | |
| padding: 15px; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| border: 1px solid rgba(201, 160, 255, 0.3); | |
| } | |
| .log-entry { | |
| margin-bottom: 8px; | |
| padding-bottom: 8px; | |
| border-bottom: 1px solid rgba(201, 160, 255, 0.2); | |
| font-size: 14px; | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| margin-top: 20px; | |
| gap: 15px; | |
| } | |
| button { | |
| background-color: #7851a9; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| button:hover { | |
| background-color: #9d71cf; | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(157, 113, 207, 0.4); | |
| } | |
| button:disabled { | |
| background-color: #4a3863; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| .winner-display { | |
| display: none; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(20, 20, 40, 0.9); | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| z-index: 100; | |
| box-shadow: 0 0 30px rgba(201, 160, 255, 0.8); | |
| animation: pulse 2s infinite; | |
| } | |
| .winner-display h2 { | |
| color: #c9a0ff; | |
| margin-bottom: 10px; | |
| } | |
| .winner-display .winner-img { | |
| width: 150px; | |
| height: 150px; | |
| margin: 0 auto; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| filter: drop-shadow(0 0 15px rgba(201, 160, 255, 0.9)); | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 30px rgba(201, 160, 255, 0.6); } | |
| 50% { box-shadow: 0 0 30px rgba(201, 160, 255, 1); } | |
| 100% { box-shadow: 0 0 30px rgba(201, 160, 255, 0.6); } | |
| } | |
| .sparks { | |
| position: absolute; | |
| width: 5px; | |
| height: 5px; | |
| background-color: #ffeb3b; | |
| border-radius: 50%; | |
| z-index: 10; | |
| box-shadow: 0 0 10px #ff9800; | |
| opacity: 0; | |
| } | |
| .immunity-badge { | |
| position: absolute; | |
| top: -25px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: gold; | |
| color: #333; | |
| font-size: 10px; | |
| font-weight: bold; | |
| padding: 3px 6px; | |
| border-radius: 10px; | |
| display: none; | |
| } | |
| @media (max-width: 768px) { | |
| .battle-arena { | |
| height: 400px; | |
| } | |
| .monster { | |
| width: 80px; | |
| height: 80px; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| h1 { | |
| font-size: 1.8rem; | |
| } | |
| .battle-arena { | |
| height: 300px; | |
| } | |
| .monster { | |
| width: 60px; | |
| height: 60px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="game-container"> | |
| <h1>Ethereal Workshop Battle Royale</h1> | |
| <p class="subtitle">Last ethereal standing wins immunity! Click Start Battle to begin.</p> | |
| <div class="battle-arena" id="arena"> | |
| <!-- Monsters will be added here by JavaScript --> | |
| <div class="winner-display" id="winnerDisplay"> | |
| <h2>WINNER!</h2> | |
| <div class="winner-img" id="winnerImg"></div> | |
| <p>has won immunity!</p> | |
| </div> | |
| </div> | |
| <div class="battle-log" id="battleLog"> | |
| <div class="log-entry">Welcome to the Ethereal Workshop Battle Royale! Click the Start Battle button to begin.</div> | |
| </div> | |
| <div class="controls"> | |
| <button id="startButton">Start Battle</button> | |
| <button id="resetButton" disabled>Reset Battle</button> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Define monsters | |
| const monsters = [ | |
| { | |
| name: "Yooreek", | |
| image: "https://static.wikia.nocookie.net/mysingingmonsters/images/1/13/Yooreek.png", | |
| health: 100, | |
| power: 25, | |
| speed: 4, | |
| eliminated: false, | |
| position: { x: 10, y: 50 } | |
| }, | |
| { | |
| name: "Meebkin", | |
| image: "https://static.wikia.nocookie.net/mysingingmonsters/images/f/f1/Meebkin.png", | |
| health: 100, | |
| power: 20, | |
| speed: 5, | |
| eliminated: false, | |
| position: { x: 30, y: 60 } | |
| }, | |
| { | |
| name: "Blarret", | |
| image: "https://static.wikia.nocookie.net/mysingingmonsters/images/9/93/Blarret.png", | |
| health: 100, | |
| power: 30, | |
| speed: 3, | |
| eliminated: false, | |
| position: { x: 50, y: 30 } | |
| }, | |
| { | |
| name: "Gaddzooks", | |
| image: "https://static.wikia.nocookie.net/mysingingmonsters/images/f/f3/Gaddzooks.png", | |
| health: 100, | |
| power: 15, | |
| speed: 6, | |
| eliminated: false, | |
| position: { x: 70, y: 40 } | |
| }, | |
| { | |
| name: "Auglur", | |
| image: "https://static.wikia.nocookie.net/mysingingmonsters/images/d/db/Auglur.png", | |
| health: 100, | |
| power: 35, | |
| speed: 2, | |
| eliminated: false, | |
| position: { x: 85, y: 70 } | |
| } | |
| ]; | |
| const arena = document.getElementById('arena'); | |
| const battleLog = document.getElementById('battleLog'); | |
| const startButton = document.getElementById('startButton'); | |
| const resetButton = document.getElementById('resetButton'); | |
| const winnerDisplay = document.getElementById('winnerDisplay'); | |
| const winnerImg = document.getElementById('winnerImg'); | |
| let battleInProgress = false; | |
| let monsterElements = []; | |
| let battleInterval; | |
| let animationFrameId; | |
| // Initialize the battle arena | |
| function initializeBattle() { | |
| // Reset monsters | |
| monsters.forEach(monster => { | |
| monster.health = 100; | |
| monster.eliminated = false; | |
| }); | |
| // Clear arena | |
| arena.innerHTML = ''; | |
| monsterElements = []; | |
| // Hide winner display | |
| winnerDisplay.style.display = 'none'; | |
| // Add monster elements to arena | |
| monsters.forEach((monster, index) => { | |
| const monsterElement = document.createElement('div'); | |
| monsterElement.className = 'monster'; | |
| monsterElement.id = `monster-${index}`; | |
| monsterElement.style.backgroundImage = `url(${monster.image})`; | |
| monsterElement.style.left = `${monster.position.x}%`; | |
| monsterElement.style.top = `${monster.position.y}%`; | |
| // Add health bar | |
| const healthBarContainer = document.createElement('div'); | |
| healthBarContainer.className = 'health-bar-container'; | |
| const healthBar = document.createElement('div'); | |
| healthBar.className = 'health-bar'; | |
| healthBarContainer.appendChild(healthBar); | |
| // Add immunity badge | |
| const immunityBadge = document.createElement('div'); | |
| immunityBadge.className = 'immunity-badge'; | |
| immunityBadge.textContent = 'IMMUNE'; | |
| monsterElement.appendChild(healthBarContainer); | |
| monsterElement.appendChild(immunityBadge); | |
| arena.appendChild(monsterElement); | |
| monsterElements.push(monsterElement); | |
| }); | |
| // Add winner display back to arena | |
| arena.appendChild(winnerDisplay); | |
| // Reset battle log | |
| battleLog.innerHTML = '<div class="log-entry">The battle is about to begin! Ethereal monsters take their positions.</div>'; | |
| } | |
| // Update monster health bars | |
| function updateHealthBars() { | |
| monsters.forEach((monster, index) => { | |
| const healthBar = monsterElements[index].querySelector('.health-bar'); | |
| healthBar.style.width = `${monster.health}%`; | |
| // Change color based on health | |
| if (monster.health > 60) { | |
| healthBar.style.backgroundColor = '#2ecc71'; | |
| } else if (monster.health > 30) { | |
| healthBar.style.backgroundColor = '#f39c12'; | |
| } else { | |
| healthBar.style.backgroundColor = '#e74c3c'; | |
| } | |
| }); | |
| } | |
| // Add log entry | |
| function addLogEntry(text) { | |
| const entry = document.createElement('div'); | |
| entry.className = 'log-entry'; | |
| entry.textContent = text; | |
| battleLog.appendChild(entry); | |
| battleLog.scrollTop = battleLog.scrollHeight; | |
| } | |
| // Create sword attack animation | |
| function swordAttack(attackerIndex, targetIndex) { | |
| const attacker = monsterElements[attackerIndex]; | |
| const target = monsterElements[targetIndex]; | |
| // Get positions | |
| const attackerRect = attacker.getBoundingClientRect(); | |
| const targetRect = target.getBoundingClientRect(); | |
| const arenaRect = arena.getBoundingClientRect(); | |
| // Create sword | |
| const sword = document.createElement('div'); | |
| sword.className = 'sword'; | |
| sword.style.left = `${(attackerRect.left + attackerRect.width/2) - arenaRect.left}px`; | |
| sword.style.top = `${(attackerRect.top + attackerRect.height/2) - arenaRect.top}px`; | |
| arena.appendChild(sword); | |
| // Show sword | |
| setTimeout(() => { sword.style.opacity = '1'; }, 10); | |
| // Animate sword to target | |
| const targetX = (targetRect.left + targetRect.width/2) - arenaRect.left; | |
| const targetY = (targetRect.top + targetRect.height/2) - arenaRect.top; | |
| let progress = 0; | |
| const startX = parseFloat(sword.style.left); | |
| const startY = parseFloat(sword.style.top); | |
| function animateSword() { | |
| progress += 0.05; | |
| if (progress >= 1) { | |
| sword.style.opacity = '0'; | |
| // Create sparks at impact | |
| createSparks(targetX, targetY); | |
| // Add damaged class to target | |
| target.classList.add('damaged'); | |
| setTimeout(() => { | |
| target.classList.remove('damaged'); | |
| }, 300); | |
| // Remove sword after animation | |
| setTimeout(() => { | |
| sword.remove(); | |
| }, 200); | |
| return; | |
| } | |
| const currentX = startX + (targetX - startX) * progress; | |
| const currentY = startY + (targetY - startY) * progress; | |
| sword.style.left = `${currentX}px`; | |
| sword.style.top = `${currentY}px`; | |
| requestAnimationFrame(animateSword); | |
| } | |
| requestAnimationFrame(animateSword); | |
| } | |
| // Create spark effects | |
| function createSparks(x, y) { | |
| for (let i = 0; i < 15; i++) { | |
| const spark = document.createElement('div'); | |
| spark.className = 'sparks'; | |
| spark.style.left = `${x}px`; | |
| spark.style.top = `${y}px`; | |
| arena.appendChild(spark); | |
| // Random direction and speed | |
| const angle = Math.random() * Math.PI * 2; | |
| const speed = 2 + Math.random() * 3; | |
| const distance = 30 + Math.random() * 40; | |
| // Set initial properties | |
| spark.style.opacity = '1'; | |
| // Animate the spark | |
| let progress = 0; | |
| const startX = x; | |
| const startY = y; | |
| function animateSpark() { | |
| progress += 0.03; | |
| if (progress >= 1) { | |
| spark.remove(); | |
| return; | |
| } | |
| const currentX = startX + Math.cos(angle) * distance * progress; | |
| const currentY = startY + Math.sin(angle) * distance * progress; | |
| spark.style.left = `${currentX}px`; | |
| spark.style.top = `${currentY}px`; | |
| spark.style.opacity = 1 - progress; | |
| requestAnimationFrame(animateSpark); | |
| } | |
| requestAnimationFrame(animateSpark); | |
| } | |
| } | |
| // Find nearest target | |
| function findNearestTarget(attacker) { | |
| let nearestTarget = -1; | |
| let minDistance = Infinity; | |
| for (let i = 0; i < monsters.length; i++) { | |
| if (i !== attacker && !monsters[i].eliminated) { | |
| const distance = Math.sqrt( | |
| Math.pow(monsters[attacker].position.x - monsters[i].position.x, 2) + | |
| Math.pow(monsters[attacker].position.y - monsters[i].position.y, 2) | |
| ); | |
| if (distance < minDistance) { | |
| minDistance = distance; | |
| nearestTarget = i; | |
| } | |
| } | |
| } | |
| return nearestTarget; | |
| } | |
| // Move towards target | |
| function moveTowardsTarget(attackerIndex, targetIndex) { | |
| const attacker = monsters[attackerIndex]; | |
| const target = monsters[targetIndex]; | |
| // Calculate direction | |
| const dx = target.position.x - attacker.position.x; | |
| const dy = target.position.y - attacker.position.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance > 10) { // Only move if not close enough | |
| // Normalize direction and scale by speed | |
| const moveX = (dx / distance) * (attacker.speed * 0.5); | |
| const moveY = (dy / distance) * (attacker.speed * 0.5); | |
| // Update position | |
| attacker.position.x += moveX; | |
| attacker.position.y += moveY; | |
| // Keep within bounds | |
| attacker.position.x = Math.max(0, Math.min(90, attacker.position.x)); | |
| attacker.position.y = Math.max(0, Math.min(85, attacker.position.y)); | |
| // Update element position | |
| monsterElements[attackerIndex].style.left = `${attacker.position.x}%`; | |
| monsterElements[attackerIndex].style.top = `${attacker.position.y}%`; | |
| } | |
| return distance <= 15; // Return true if close enough to attack | |
| } | |
| // Perform battle round | |
| function battleRound() { | |
| // Check for battle end | |
| let activeMonstersCount = 0; | |
| let lastStanding = -1; | |
| monsters.forEach((monster, index) => { | |
| if (!monster.eliminated) { | |
| activeMonstersCount++; | |
| lastStanding = index; | |
| } | |
| }); | |
| if (activeMonstersCount <= 1) { | |
| endBattle(lastStanding); | |
| return; | |
| } | |
| // Process each monster's turn | |
| monsters.forEach((monster, attackerIndex) => { | |
| if (monster.eliminated) return; | |
| // Find target | |
| const targetIndex = findNearestTarget(attackerIndex); | |
| if (targetIndex === -1) return; | |
| // Try to move closer to target | |
| const canAttack = moveTowardsTarget(attackerIndex, targetIndex); | |
| if (canAttack) { | |
| // Chance to attack based on speed | |
| if (Math.random() < monster.speed / 10) { | |
| // Add attacking class | |
| monsterElements[attackerIndex].classList.add('attacking'); | |
| // Calculate damage with some randomness | |
| const baseDamage = monster.power; | |
| const randomFactor = 0.8 + Math.random() * 0.4; // 0.8 to 1.2 | |
| const damage = Math.round(baseDamage * randomFactor); | |
| // Apply damage | |
| monsters[targetIndex].health -= damage; | |
| monsters[targetIndex].health = Math.max(0, monsters[targetIndex].health); | |
| // Update health bars | |
| updateHealthBars(); | |
| // Create sword attack animation | |
| swordAttack(attackerIndex, targetIndex); | |
| // Log the attack | |
| addLogEntry(`${monster.name} attacks ${monsters[targetIndex].name} for ${damage} damage!`); | |
| // Check if target is eliminated | |
| if (monsters[targetIndex].health <= 0 && !monsters[targetIndex].eliminated) { | |
| monsters[targetIndex].eliminated = true; | |
| monsterElements[targetIndex].classList.add('eliminated'); | |
| addLogEntry(`${monsters[targetIndex].name} has been eliminated from the battle!`); | |
| } | |
| // Remove attacking class after a delay | |
| setTimeout(() => { | |
| monsterElements[attackerIndex].classList.remove('attacking'); | |
| }, 300); | |
| } | |
| } | |
| }); | |
| } | |
| // Start the battle | |
| function startBattle() { | |
| if (battleInProgress) return; | |
| battleInProgress = true; | |
| startButton.disabled = true; | |
| resetButton.disabled = false; | |
| addLogEntry("The battle has begun! Ethereal monsters are fighting for immunity!"); | |
| // Run battle rounds at intervals | |
| battleInterval = setInterval(() => { | |
| battleRound(); | |
| }, 1000); | |
| // Start animation loop | |
| function animationLoop() { | |
| if (!battleInProgress) return; | |
| // Any continuous animations can go here | |
| animationFrameId = requestAnimationFrame(animationLoop); | |
| } | |
| animationLoop(); | |
| } | |
| // End the battle | |
| function endBattle(winnerIndex) { | |
| battleInProgress = false; | |
| clearInterval(battleInterval); | |
| cancelAnimationFrame(animationFrameId); | |
| // Display winner | |
| if (winnerIndex >= 0) { | |
| const winner = monsters[winnerIndex]; | |
| addLogEntry(`${winner.name} is the last ethereal standing and has won immunity!`); | |
| // Show winner banner | |
| winnerImg.style.backgroundImage = `url(${winner.image})`; | |
| winnerDisplay.style.display = 'block'; | |
| // Show immunity badge | |
| const immunityBadge = monsterElements[winnerIndex].querySelector('.immunity-badge'); | |
| immunityBadge.style.display = 'block'; | |
| } else { | |
| addLogEntry("The battle has ended in a draw!"); | |
| } | |
| resetButton.disabled = false; | |
| } | |
| // Reset the battle | |
| function resetBattle() { | |
| battleInProgress = false; | |
| clearInterval(battleInterval); | |
| cancelAnimationFrame(animationFrameId); | |
| startButton.disabled = false; | |
| resetButton.disabled = true; | |
| initializeBattle(); | |
| } | |
| // Event listeners | |
| startButton.addEventListener('click', startBattle); | |
| resetButton.addEventListener('click', resetBattle); | |
| // Initialize the battle arena on load | |
| initializeBattle(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |