Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Simple HTML script works in preview only, stops working when the page is saved

Piotr Rajewski
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
November 25, 2024

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>

 

3 answers

1 vote
__ Jimi Wikman
Community Champion
November 25, 2024

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.

1 vote
marc -Collabello--Phase Locked-
Community Champion
November 25, 2024

Most likely the following happens:

  • after preview you save your html
  • when rendering within Confluence, the html gets sanitized and not everything gets rendered.
  • in preview everything gets rendered again
0 votes
Samuel Mendonça
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
December 25, 2025

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

 

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events