I'm pasting a simple HTML script for an image overlay effect and it works smooth when I check in the preview. However, when I save my edits to the page, the effect is killed and it does not show properly. Surprisingly, when I go to edit again and check preview it is working fine again. Just not when I save changes.
[EDIT] is there perhaps any other simple method to have image overlay on hover? This one may not work when due to security reasons in my organisation.
This is the code:
<style>
.container {
position: relative;
width: 50%;
}
.image {
display: block;
width: 100%;
height: auto;
}
.overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #008CBA;
overflow: hidden;
width: 100%;
height: 0;
transition: .5s ease;
}
.container:hover .overlay {
height: 100%;
}
.text {
color: white;
font-size: 20px;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
}
</style>
</head>
<body>
<h2>Slide in Overlay from the Bottom</h2>
<p>Hover over the image to see the effect.</p>
<div class="container">
<img src="https://alm-confluence.systems.uk.hsbc/confluence/download/attachments/401548207/image-2024-6-13_18-54-20.png" alt="Avatar" class="image">
<div class="overlay">
<div class="text">Hello World</div>
</div>
</div>
</body>
</html>
|
I suggest you create a bug for this.
If the form does not sanitize the input in preview mode, then you are executing code in the platform and that can be a security issue. While they probably are sanitizing some code, this is still not ok because it is a way to execute code and that should not be possible.
It also causes confusion if the output in preview is different from the end result, as you have experienced.
Most likely the following happens:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Z-Sector: Protocolo Sobrevivência</title>
<style>
/* ESTILOS GERAIS E UI */
body { margin: 0; overflow: hidden; background: #111; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; }
canvas { display: block; cursor: crosshair; }
/* HUD */
#ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
.hud-panel { position: absolute; padding: 10px 20px; background: rgba(0, 0, 0, 0.7); border: 1px solid #444; color: #fff; border-radius: 5px; font-weight: bold; }
#hud-top-left { top: 20px; left: 20px; }
#hud-top-right { top: 20px; right: 20px; text-align: right; }
#hud-bottom-left { bottom: 20px; left: 20px; }
#hud-bottom-right { bottom: 20px; right: 20px; text-align: right; }
.stat-label { font-size: 12px; color: #aaa; text-transform: uppercase; }
.stat-value { font-size: 24px; color: #fff; }
.bar-container { width: 200px; height: 10px; background: #333; margin-top: 5px; }
.bar-fill { height: 100%; transition: width 0.2s; }
#hp-bar { background: #e74c3c; width: 100%; }
#ammo-bar { background: #f1c40f; width: 100%; }
/* MENUS */
.overlay-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85); display: flex; flex-direction: column;
justify-content: center; align-items: center; color: white; pointer-events: all;
z-index: 100;
}
h1 { font-size: 48px; margin-bottom: 10px; color: #e74c3c; text-transform: uppercase; letter-spacing: 5px; }
button {
margin: 10px; padding: 15px 40px; background: #e74c3c; border: none;
color: white; font-size: 18px; cursor: pointer; border-radius: 3px;
transition: 0.2s; text-transform: uppercase; font-weight: bold;
}
button:hover { background: #c0392b; transform: scale(1.05); }
button:disabled { background: #555; cursor: not-allowed; transform: none; }
/* LOJA */
#shop-menu { display: none; background: rgba(10, 20, 30, 0.95); border: 2px solid #3498db; padding: 40px; border-radius: 10px; }
.shop-item { display: flex; justify-content: space-between; align-items: center; width: 400px; margin-bottom: 20px; padding: 15px; background: rgba(255,255,255,0.05); border: 1px solid #444; }
.item-info h3 { margin: 0; color: #3498db; }
.item-info p { margin: 5px 0 0 0; font-size: 12px; color: #ccc; }
.price-tag { color: #f1c40f; font-weight: bold; margin-right: 20px; }
/* NOTIFICAÇÕES */
#notification-area { position: absolute; top: 20%; left: 50%; transform: translateX(-50%); text-align: center; pointer-events: none; }
.notif { font-size: 24px; color: #fff; text-shadow: 0 2px 4px #000; animation: fadeUp 2s forwards; }
@keyframes fadeUp { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-50px); } }
</style>
</head>
<body>
<div id="ui-layer">
<div id="hud-top-left" class="hud-panel">
<div class="stat-label">Saúde</div>
<div class="bar-container"><div id="hp-bar" class="bar-fill"></div></div>
<div style="margin-top:10px;" class="stat-label">Onda Atual</div>
<div class="stat-value" id="wave-val">1</div>
</div>
<div id="hud-top-right" class="hud-panel">
<div class="stat-label">Créditos</div>
<div class="stat-value" style="color: #f1c40f">$ <span id="score-val">0</span></div>
<div style="margin-top:5px; font-size:12px; color:#888;">[B] para abrir Loja</div>
</div>
<div id="hud-bottom-right" class="hud-panel">
<div class="stat-label">Arma: <span id="weapon-name" style="color:#fff;">Pistola Padrão</span></div>
<div class="stat-value"><span id="ammo-current">12</span> / <span id="ammo-reserve">∞</span></div>
<div class="bar-container"><div id="ammo-bar" class="bar-fill"></div></div>
<div style="font-size:12px; color:#888; margin-top:5px;">[R] Recarregar</div>
</div>
</div>
<div id="start-screen" class="overlay-screen">
<h1>Z-Sector</h1>
<p>Sobreviva às ondas. Compre armas. Não pare de se mover.</p>
<button onclick="Game.start()">Iniciar Missão</button>
</div>
<div id="game-over-screen" class="overlay-screen" style="display: none;">
<h1 style="color: #c0392b">MISSÃO FALHOU</h1>
<p>Você foi sobrecarregado pela horda.</p>
<p>Ondas Sobrevividas: <span id="final-wave">0</span></p>
<button onclick="location.reload()">Tentar Novamente</button>
</div>
<div id="shop-screen" class="overlay-screen" style="display: none;">
<div id="shop-menu">
<h2 style="color: #3498db; text-align: center; margin-bottom: 30px;">SUPRIMENTOS TÁTICOS</h2>
<div class="shop-item">
<div class="item-info">
<h3>Rifle de Assalto AR-50</h3>
<p>Dano: 50 | Cadência: Alta | Automático</p>
</div>
<span class="price-tag">$ 1500</span>
<button id="btn-buy-rifle" onclick="Shop.buyWeapon('rifle')">Comprar</button>
</div>
<div class="shop-item">
<div class="item-info">
<h3>Escopeta 'Devastadora'</h3>
<p>Dano: 20x8 | Curto Alcance | Lenta</p>
</div>
<span class="price-tag">$ 2500</span>
<button id="btn-buy-shotgun" onclick="Shop.buyWeapon('shotgun')">Comprar</button>
</div>
<div class="shop-item">
<div class="item-info">
<h3>Caixa de Munição</h3>
<p>Restaura munição de todas as armas</p>
</div>
<span class="price-tag">$ 250</span>
<button onclick="Shop.buyAmmo()">Comprar</button>
</div>
<div class="shop-item">
<div class="item-info">
<h3>Kit Médico</h3>
<p>Restaura 100% da saúde</p>
</div>
<span class="price-tag">$ 500</span>
<button onclick="Shop.buyHealth()">Comprar</button>
</div>
<button onclick="Shop.close()" style="width: 100%; background: #555; margin: 20px 0 0 0;">Fechar Loja [B]</button>
</div>
</div>
<div id="notification-area"></div>
<canvas id="gameCanvas"></canvas>
<script>
/**
* Z-SECTOR ENGINE
* Desenvolvido para estabilidade, performance e jogabilidade viciante.
*/
// --- CONFIGURAÇÕES E CONSTANTES ---
const CONSTANTS = {
PLAYER_SPEED: 4,
ZOMBIE_BASE_SPEED: 1.5,
TILE_SIZE: 50,
COLORS: {
wall: '#333',
floor: '#1a1a1a',
player: '#3498db',
zombie: '#2ecc71',
bullet: '#f1c40f'
}
};
// Dados das Armas
const WEAPONS = {
pistol: {
name: "Pistola M9",
damage: 25,
fireRate: 500, // ms
magSize: 12,
reloadTime: 1500,
automatic: false,
spread: 0.05,
color: '#f1c40f'
},
rifle: {
name: "Rifle AR-50",
damage: 50,
fireRate: 150, // ms (Rápido)
magSize: 30,
reloadTime: 2000,
automatic: true,
spread: 0.1,
color: '#e67e22'
},
shotgun: {
name: "Devastadora",
damage: 20, // Por pelada
pellets: 6,
fireRate: 900,
magSize: 6,
reloadTime: 2500,
automatic: false,
spread: 0.4,
color: '#e74c3c'
}
};
// --- GERENCIAMENTO DE INPUT ---
const Input = {
keys: {},
mouse: { x: 0, y: 0, leftDown: false },
init() {
window.addEventListener('keydown', e => {
this.keys[e.key.toLowerCase()] = true;
if (e.key.toLowerCase() === 'r') Player.reload();
if (e.key.toLowerCase() === 'b') Shop.toggle();
if (e.key === 'Escape') Shop.close();
});
window.addEventListener('keyup', e => this.keys[e.key.toLowerCase()] = false);
window.addEventListener('mousemove', e => {
this.mouse.x = e.clientX;
this.mouse.y = e.clientY;
});
window.addEventListener('mousedown', e => {
if(e.button === 0) {
this.mouse.leftDown = true;
if (!Player.currentWeapon.automatic) Player.shoot();
}
});
window.addEventListener('mouseup', e => { if(e.button === 0) this.mouse.leftDown = false; });
}
};
// --- CLASSE DE ENTIDADES ---
class Entity {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.markedForDeletion = false;
}
}
// --- JOGADOR ---
const Player = {
x: 0, y: 0, radius: 15,
hp: 100, maxHp: 100,
score: 0,
inventory: ['pistol'],
equippedIndex: 0,
ammo: { pistol: 12, rifle: 0, shotgun: 0 }, // Munição no pente
reserve: { pistol: Infinity, rifle: 120, shotgun: 30 }, // Munição reserva
lastShotTime: 0,
isReloading: false,
get currentWeapon() { return WEAPONS[this.inventory[this.equippedIndex]]; },
init(x, y) {
this.x = x; this.y = y;
this.hp = 100;
this.score = 0;
this.inventory = ['pistol'];
this.resetAmmo();
},
resetAmmo() {
this.ammo.pistol = WEAPONS.pistol.magSize;
this.ammo.rifle = WEAPONS.rifle.magSize;
this.ammo.shotgun = WEAPONS.shotgun.magSize;
},
update() {
if (this.hp <= 0) return;
// Movimento
let dx = 0, dy = 0;
if (Input.keys['w']) dy = -1;
if (Input.keys['s']) dy = 1;
if (Input.keys['a']) dx = -1;
if (Input.keys['d']) dx = 1;
// Normalização de Vetor (Evitar andar mais rápido na diagonal)
if (dx !== 0 || dy !== 0) {
const length = Math.sqrt(dx*dx + dy*dy);
dx /= length;
dy /= length;
// Colisão com Paredes (Previsão)
if (!Map.checkCollision(this.x + dx * CONSTANTS.PLAYER_SPEED, this.y, this.radius)) {
this.x += dx * CONSTANTS.PLAYER_SPEED;
}
if (!Map.checkCollision(this.x, this.y + dy * CONSTANTS.PLAYER_SPEED, this.radius)) {
this.y += dy * CONSTANTS.PLAYER_SPEED;
}
}
// Tiro Automático
if (Input.mouse.leftDown && this.currentWeapon.automatic) {
this.shoot();
}
// Troca de Arma (Teclas 1, 2, 3)
if (Input.keys['1'] && this.inventory.length >= 1) this.switchWeapon(0);
if (Input.keys['2'] && this.inventory.length >= 2) this.switchWeapon(1);
if (Input.keys['3'] && this.inventory.length >= 3) this.switchWeapon(2);
},
switchWeapon(index) {
if (this.isReloading || index === this.equippedIndex) return;
this.equippedIndex = index;
HUD.updateWeapon();
},
shoot() {
if (this.isReloading || Game.isPaused) return;
const now = Date.now();
const weapon = this.currentWeapon;
const weaponKey = this.inventory[this.equippedIndex];
if (now - this.lastShotTime < weapon.fireRate) return;
if (this.ammo[weaponKey] <= 0) {
this.reload();
return;
}
// Criar Projétil
this.lastShotTime = now;
this.ammo[weaponKey]--;
// Efeito de Recuo Visual
Game.cameraShake = 5;
// Lógica de Disparo (Shotgun vs Normal)
const angle = Math.atan2(Input.mouse.y - Game.camera.y - this.y, Input.mouse.x - Game.camera.x - this.x);
if (weaponKey === 'shotgun') {
for(let i=0; i<weapon.pellets; i++) {
const spread = (Math.random() - 0.5) * weapon.spread;
Projectiles.spawn(this.x, this.y, angle + spread, weapon.damage, weapon.color);
}
} else {
const spread = (Math.random() - 0.5) * weapon.spread;
Projectiles.spawn(this.x, this.y, angle + spread, weapon.damage, weapon.color);
}
HUD.updateAmmo();
},
reload() {
const weaponKey = this.inventory[this.equippedIndex];
const weapon = WEAPONS[weaponKey];
if (this.isReloading || this.ammo[weaponKey] === weapon.magSize) return;
if (this.reserve[weaponKey] <= 0 && this.reserve[weaponKey] !== Infinity) {
HUD.notify("SEM MUNIÇÃO EXTRA!");
return;
}
this.isReloading = true;
HUD.notify("RECARREGANDO...");
setTimeout(() => {
const needed = weapon.magSize - this.ammo[weaponKey];
const available = this.reserve[weaponKey];
const toLoad = Math.min(needed, available);
this.ammo[weaponKey] += toLoad;
if (this.reserve[weaponKey] !== Infinity) {
this.reserve[weaponKey] -= toLoad;
}
this.isReloading = false;
HUD.updateAmmo();
}, weapon.reloadTime);
},
takeDamage(amount) {
this.hp -= amount;
Game.cameraShake = 10;
HUD.updateHealth();
if (this.hp <= 0) Game.over();
},
addScore(amount) {
this.score += amount;
HUD.updateScore();
}
};
// --- SISTEMA DE MAPA E COLISÃO ---
const Map = {
width: 2000,
height: 2000,
walls: [], // Retângulos {x, y, w, h}
init() {
this.walls = [];
// Criar Bordas
this.addWall(0, 0, this.width, 50); // Top
this.addWall(0, this.height-50, this.width, 50); // Bottom
this.addWall(0, 0, 50, this.height); // Left
this.addWall(this.width-50, 0, 50, this.height); // Right
// Criar Obstáculos (Sala central segura e corredores)
// Centro (Loja)
this.addWall(800, 800, 400, 20);
this.addWall(800, 1180, 400, 20);
this.addWall(800, 800, 20, 150);
this.addWall(1180, 800, 20, 150);
// Caixas aleatórias
for(let i=0; i<20; i++) {
const wx = 100 + Math.random() * (this.width - 200);
const wy = 100 + Math.random() * (this.height - 200);
// Evitar centro
if (Math.abs(wx - 1000) > 300 || Math.abs(wy - 1000) > 300) {
this.addWall(wx, wy, 80, 80);
}
}
},
addWall(x, y, w, h) {
this.walls.push({x, y, w, h});
},
checkCollision(x, y, radius) {
// Colisão Circulo-Retângulo AABB
for (let w of this.walls) {
let closestX = Math.max(w.x, Math.min(x, w.x + w.w));
let closestY = Math.max(w.y, Math.min(y, w.y + w.h));
let dx = x - closestX;
let dy = y - closestY;
if ((dx * dx + dy * dy) < (radius * radius)) return true;
}
return false;
},
draw(ctx) {
// Desenha Chão (Grid simples para performance)
ctx.strokeStyle = '#222';
ctx.lineWidth = 2;
const gridSize = 100;
// Desenha apenas o que está na tela (Culling simples)
const startX = Math.floor(-Game.camera.x / gridSize) * gridSize;
const startY = Math.floor(-Game.camera.y / gridSize) * gridSize;
for (let x = startX; x < startX + window.innerWidth + 100; x += gridSize) {
for (let y = startY; y < startY + window.innerHeight + 100; y += gridSize) {
ctx.strokeRect(x, y, gridSize, gridSize);
}
}
// Desenha Paredes
ctx.fillStyle = '#444';
ctx.shadowColor = '#000';
ctx.shadowBlur = 10;
for (let w of this.walls) {
ctx.fillRect(w.x, w.y, w.w, w.h);
// Detalhe visual (topo da parede)
ctx.fillStyle = '#555';
ctx.fillRect(w.x, w.y, w.w, 10);
ctx.fillStyle = '#444';
}
ctx.shadowBlur = 0;
}
};
// --- ZUMBIS ---
class Zombie extends Entity {
constructor(waveMultiplier) {
// Spawn longe do jogador
let sx, sy, dist;
do {
sx = Math.random() * Map.width;
sy = Math.random() * Map.height;
const dx = sx - Player.x;
const dy = sy - Player.y;
dist = Math.sqrt(dx*dx + dy*dy);
} while (dist < 600 || Map.checkCollision(sx, sy, 20)); // Longe e não dentro da parede
super(sx, sy, 18, CONSTANTS.COLORS.zombie);
// Progressão de Dificuldade
this.hp = 75 + (waveMultiplier * 25);
this.maxHp = this.hp;
this.speed = CONSTANTS.ZOMBIE_BASE_SPEED + (waveMultiplier * 0.1);
this.damage = 10 + Math.floor(waveMultiplier * 2);
}
update() {
// IA de perseguição simples
const angle = Math.atan2(Player.y - this.y, Player.x - this.x);
const dx = Math.cos(angle) * this.speed;
const dy = Math.sin(angle) * this.speed;
// Verificação de colisão futura
if (!Map.checkCollision(this.x + dx, this.y, this.radius)) this.x += dx;
if (!Map.checkCollision(this.x, this.y + dy, this.radius)) this.y += dy;
// Ataque
const distToPlayer = Math.hypot(Player.x - this.x, Player.y - this.y);
if (distToPlayer < this.radius + Player.radius) {
Player.takeDamage(0.5); // Dano contínuo se encostar
}
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
// Barra de vida do Zumbi
if (this.hp < this.maxHp) {
ctx.fillStyle = 'red';
ctx.fillRect(this.x - 15, this.y - 25, 30, 5);
ctx.fillStyle = '#0f0';
ctx.fillRect(this.x - 15, this.y - 25, 30 * (this.hp / this.maxHp), 5);
}
}
takeHit(dmg) {
this.hp -= dmg;
Particles.spawn(this.x, this.y, '#2ecc71', 3); // Sangue
if (this.hp <= 0) {
this.markedForDeletion = true;
Player.addScore(10 + Game.wave * 2);
Particles.spawn(this.x, this.y, '#2ecc71', 10);
}
}
}
// --- PROJÉTEIS ---
const Projectiles = {
list: [],
spawn(x, y, angle, damage, color) {
this.list.push({
x, y,
vx: Math.cos(angle) * 15, // Velocidade da bala
vy: Math.sin(angle) * 15,
damage, color,
life: 100 // Frames de vida
});
},
update() {
for (let i = this.list.length - 1; i >= 0; i--) {
let p = this.list[i];
p.x += p.vx;
p.y += p.vy;
p.life--;
// Colisão com Paredes
if (Map.checkCollision(p.x, p.y, 2)) p.life = 0;
// Colisão com Zumbis
if (p.life > 0) {
for (let z of Zombies.list) {
const dist = Math.hypot(z.x - p.x, z.y - p.y);
if (dist < z.radius + 5) {
z.takeHit(p.damage);
p.life = 0;
break;
}
}
}
if (p.life <= 0) this.list.splice(i, 1);
}
},
draw(ctx) {
for (let p of this.list) {
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
ctx.fill();
}
}
};
// --- PARTÍCULAS (Otimização visual) ---
const Particles = {
list: [],
spawn(x, y, color, count) {
for(let i=0; i<count; i++) {
this.list.push({
x, y,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
life: 1.0,
color
});
}
},
updateAndDraw(ctx) {
for (let i = this.list.length - 1; i >= 0; i--) {
let p = this.list[i];
p.x += p.vx;
p.y += p.vy;
p.life -= 0.05;
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, 3, 3);
ctx.globalAlpha = 1.0;
if (p.life <= 0) this.list.splice(i, 1);
}
}
}
// --- GERENCIADOR DE ONDAS ---
const Zombies = {
list: [],
spawnQueue: 0,
spawnTimer: 0,
startWave() {
Game.wave++;
// Fórmula: 5 zumbis iniciais + 5 por onda
this.spawnQueue = 5 + (Game.wave * 5);
HUD.updateWave();
HUD.notify(`ONDA ${Game.wave} INICIADA`);
},
update() {
// Spawn progressivo para não travar
if (this.spawnQueue > 0) {
this.spawnTimer++;
if (this.spawnTimer > 30) { // Spawna a cada 0.5s aprox
this.list.push(new Zombie(Game.wave));
this.spawnQueue--;
this.spawnTimer = 0;
}
}
// Atualiza zumbis
for (let i = this.list.length - 1; i >= 0; i--) {
let z = this.list[i];
z.update();
if (z.markedForDeletion) this.list.splice(i, 1);
}
// Checa fim da onda
if (this.spawnQueue === 0 && this.list.length === 0 && Game.isRunning) {
this.startWave();
}
},
draw(ctx) {
this.list.forEach(z => z.draw(ctx));
}
};
// --- LOJA ---
const Shop = {
isOpen: false,
toggle() {
if (Game.hp <= 0) return;
this.isOpen ? this.close() : this.open();
},
open() {
this.isOpen = true;
Game.isPaused = true;
document.getElementById('shop-screen').style.display = 'flex';
this.updateButtons();
},
close() {
this.isOpen = false;
Game.isPaused = false;
document.getElementById('shop-screen').style.display = 'none';
},
updateButtons() {
// Habilita/Desabilita botões baseado no dinheiro
document.getElementById('btn-buy-rifle').disabled = Player.score < 1500 || Player.inventory.includes('rifle');
document.getElementById('btn-buy-shotgun').disabled = Player.score < 2500 || Player.inventory.includes('shotgun');
},
buyWeapon(type) {
let cost = type === 'rifle' ? 1500 : 2500;
if (Player.score >= cost && !Player.inventory.includes(type)) {
Player.score -= cost;
Player.inventory.push(type);
Player.reserve[type] += type === 'rifle' ? 120 : 30; // Dá munição inicial
this.updateButtons();
HUD.updateScore();
HUD.notify(`ARMA ADQUIRIDA: ${type.toUpperCase()}`);
}
},
buyAmmo() {
if (Player.score >= 250) {
Player.score -= 250;
// Enche reserva
Player.reserve.rifle += 90;
Player.reserve.shotgun += 12;
HUD.updateScore();
HUD.updateAmmo();
this.updateButtons();
HUD.notify("MUNIÇÃO REPOSTA");
}
},
buyHealth() {
if (Player.score >= 500 && Player.hp < 100) {
Player.score -= 500;
Player.hp = 100;
HUD.updateScore();
HUD.updateHealth();
this.updateButtons();
HUD.notify("SAÚDE RESTAURADA");
}
}
};
// --- GAME LOOP PRINCIPAL ---
const Game = {
canvas: document.getElementById('gameCanvas'),
ctx: document.getElementById('gameCanvas').getContext('2d'),
isRunning: false,
isPaused: false,
wave: 0,
camera: { x: 0, y: 0 },
cameraShake: 0,
init() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
Input.init();
Map.init();
window.addEventListener('resize', () => {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
});
},
start() {
document.getElementById('start-screen').style.display = 'none';
document.getElementById('game-over-screen').style.display = 'none';
Player.init(1000, 1000); // Centro do mapa
Zombies.list = [];
Projectiles.list = [];
this.wave = 0;
this.isRunning = true;
this.isPaused = false;
HUD.updateAll();
Zombies.startWave();
this.loop();
},
updateCamera() {
// Camera segue o jogador
let targetX = Player.x - this.canvas.width / 2;
let targetY = Player.y - this.canvas.height / 2;
// Clamp no mapa
targetX = Math.max(0, Math.min(targetX, Map.width - this.canvas.width));
targetY = Math.max(0, Math.min(targetY, Map.height - this.canvas.height));
// Screen Shake
if (this.cameraShake > 0) {
targetX += (Math.random() - 0.5) * this.cameraShake;
targetY += (Math.random() - 0.5) * this.cameraShake;
this.cameraShake *= 0.9;
if(this.cameraShake < 0.5) this.cameraShake = 0;
}
this.camera.x = targetX;
this.camera.y = targetY;
},
loop() {
if (!this.isRunning) return;
requestAnimationFrame(() => this.loop());
if (this.isPaused) return;
// Updates
Player.update();
Zombies.update();
Projectiles.update();
this.updateCamera();
// Render
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.save();
this.ctx.translate(-this.camera.x, -this.camera.y);
Map.draw(this.ctx);
Projectiles.draw(this.ctx);
Particles.updateAndDraw(this.ctx);
// Desenha Jogador
this.ctx.fillStyle = Player.isReloading ? '#fff' : CONSTANTS.COLORS.player;
this.ctx.beginPath();
this.ctx.arc(Player.x, Player.y, Player.radius, 0, Math.PI * 2);
this.ctx.fill();
// Linha de mira
this.ctx.strokeStyle = 'rgba(255,255,255,0.3)';
this.ctx.beginPath();
this.ctx.moveTo(Player.x, Player.y);
const aimX = Input.mouse.x + this.camera.x;
const aimY = Input.mouse.y + this.camera.y;
this.ctx.lineTo(aimX, aimY);
this.ctx.stroke();
Zombies.draw(this.ctx);
this.ctx.restore();
},
over() {
this.isRunning = false;
document.getElementById('final-wave').innerText = this.wave;
document.getElementById('game-over-screen').style.display = 'flex';
}
};
// --- HUD MANAGER ---
const HUD = {
notify(msg) {
const el = document.createElement('div');
el.className = 'notif';
el.innerText = msg;
document.getElementById('notification-area').appendChild(el);
setTimeout(() => el.remove(), 2000);
},
updateScore() { document.getElementById('score-val').innerText = Player.score; },
updateWave() { document.getElementById('wave-val').innerText = Game.wave; },
updateHealth() { document.getElementById('hp-bar').style.width = Math.max(0, Player.hp) + '%'; },
updateWeapon() { document.getElementById('weapon-name').innerText = Player.currentWeapon.name; this.updateAmmo(); },
updateAmmo() {
const weapon = Player.inventory[Player.equippedIndex];
const current = Player.ammo[weapon];
const max = WEAPONS[weapon].magSize;
const reserve = Player.reserve[weapon] === Infinity ? '∞' : Player.reserve[weapon];
document.getElementById('ammo-current').innerText = current;
document.getElementById('ammo-reserve').innerText = reserve;
document.getElementById('ammo-bar').style.width = (current / max * 100) + '%';
},
updateAll() {
this.updateScore();
this.updateWave();
this.updateHealth();
this.updateWeapon();
}
};
// --- INICIALIZAÇÃO ---
Game.init();
</script>
</body>
</html>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.