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> |