| <!DOCTYPE html> |
| <html lang="pt-BR"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Sniper Elite Pro - Jogo de Tiro Avançado</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| body { |
| overflow: hidden; |
| background-color: #111; |
| font-family: 'Arial', sans-serif; |
| user-select: none; |
| } |
| |
| #game-container { |
| position: relative; |
| width: 100vw; |
| height: 100vh; |
| background-image: url('https://images.unsplash.com/photo-1519751138087-5bf79df62d5b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80'); |
| background-size: cover; |
| background-position: center; |
| transition: transform 0.3s ease-out; |
| } |
| |
| #scope { |
| position: absolute; |
| width: 100px; |
| height: 100px; |
| border: 2px solid rgba(255, 0, 0, 0.8); |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 100; |
| transform: translate(-50%, -50%); |
| box-shadow: 0 0 15px rgba(255, 0, 0, 0.5); |
| } |
| |
| #scope:before, #scope:after { |
| content: ''; |
| position: absolute; |
| background: rgba(255, 0, 0, 0.8); |
| } |
| |
| #scope:before { |
| width: 2px; |
| height: 30px; |
| left: 50%; |
| top: -30px; |
| transform: translateX(-50%); |
| } |
| |
| #scope:after { |
| width: 30px; |
| height: 2px; |
| left: -30px; |
| top: 50%; |
| transform: translateY(-50%); |
| } |
| |
| .target { |
| position: absolute; |
| width: 60px; |
| height: 60px; |
| background-size: contain; |
| background-repeat: no-repeat; |
| background-position: center; |
| transition: transform 0.1s; |
| z-index: 10; |
| filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.7)); |
| } |
| |
| .target.civilian { |
| background-image: url('https://cdn-icons-png.flaticon.com/512/4320/4320339.png'); |
| } |
| |
| .target.soldier { |
| background-image: url('https://cdn-icons-png.flaticon.com/512/102/102637.png'); |
| } |
| |
| .target.vip { |
| background-image: url('https://cdn-icons-png.flaticon.com/512/102/102642.png'); |
| } |
| |
| .target.hostage { |
| background-image: url('https://cdn-icons-png.flaticon.com/512/4320/4320344.png'); |
| } |
| |
| .bullet-hole { |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| background-image: url('https://cdn-icons-png.flaticon.com/512/102/102661.png'); |
| background-size: contain; |
| z-index: 5; |
| } |
| |
| .blood-splatter { |
| position: absolute; |
| width: 80px; |
| height: 80px; |
| background-image: url('https://cdn-icons-png.flaticon.com/512/102/102664.png'); |
| background-size: contain; |
| z-index: 15; |
| opacity: 0; |
| animation: fadeIn 0.5s forwards; |
| } |
| |
| @keyframes fadeIn { |
| to { opacity: 1; } |
| } |
| |
| #reticle { |
| position: absolute; |
| width: 10px; |
| height: 10px; |
| background-color: rgba(255, 0, 0, 0.7); |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 90; |
| transform: translate(-50%, -50%); |
| box-shadow: 0 0 10px rgba(255, 0, 0, 0.5); |
| } |
| |
| #breathing-effect { |
| position: absolute; |
| width: 120px; |
| height: 120px; |
| border: 1px solid rgba(255, 255, 255, 0.3); |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 80; |
| transform: translate(-50%, -50%); |
| animation: breathe 4s infinite ease-in-out; |
| } |
| |
| @keyframes breathe { |
| 0%, 100% { transform: translate(-50%, -50%) scale(1); } |
| 50% { transform: translate(-50%, -50%) scale(1.1); } |
| } |
| |
| #wind-indicator { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| #ammo-counter { |
| position: absolute; |
| bottom: 20px; |
| right: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| #score-display { |
| position: absolute; |
| top: 20px; |
| left: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| #distance-display { |
| position: absolute; |
| top: 70px; |
| left: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| #game-over { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.9); |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| color: white; |
| z-index: 300; |
| display: none; |
| backdrop-filter: blur(5px); |
| } |
| |
| #recoil-effect { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(255, 255, 255, 0.3); |
| pointer-events: none; |
| z-index: 95; |
| opacity: 0; |
| } |
| |
| #zoom-controls { |
| position: absolute; |
| bottom: 20px; |
| left: 20px; |
| z-index: 200; |
| display: flex; |
| gap: 10px; |
| } |
| |
| .zoom-btn { |
| width: 40px; |
| height: 40px; |
| background-color: rgba(0, 0, 0, 0.7); |
| color: white; |
| border: none; |
| border-radius: 50%; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| cursor: pointer; |
| font-size: 18px; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| transition: all 0.2s; |
| } |
| |
| .zoom-btn:hover { |
| background-color: rgba(255, 255, 255, 0.2); |
| transform: scale(1.1); |
| } |
| |
| #sound-control { |
| position: absolute; |
| bottom: 20px; |
| left: 80px; |
| z-index: 200; |
| } |
| |
| #sound-btn { |
| width: 40px; |
| height: 40px; |
| background-color: rgba(0, 0, 0, 0.7); |
| color: white; |
| border: none; |
| border-radius: 50%; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| cursor: pointer; |
| font-size: 18px; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| transition: all 0.2s; |
| } |
| |
| #sound-btn:hover { |
| background-color: rgba(255, 255, 255, 0.2); |
| transform: scale(1.1); |
| } |
| |
| #main-menu { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.8); |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| color: white; |
| z-index: 400; |
| backdrop-filter: blur(5px); |
| } |
| |
| .menu-title { |
| font-size: 4rem; |
| font-weight: bold; |
| margin-bottom: 2rem; |
| text-shadow: 0 0 10px rgba(255, 0, 0, 0.7); |
| background: linear-gradient(to right, #ff0000, #ff6600); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| .menu-btn { |
| background: linear-gradient(to right, #ff0000, #ff6600); |
| color: white; |
| border: none; |
| padding: 15px 30px; |
| margin: 10px; |
| border-radius: 50px; |
| font-size: 1.2rem; |
| cursor: pointer; |
| transition: all 0.3s; |
| box-shadow: 0 5px 15px rgba(255, 0, 0, 0.3); |
| min-width: 200px; |
| text-align: center; |
| } |
| |
| .menu-btn:hover { |
| transform: translateY(-3px); |
| box-shadow: 0 8px 20px rgba(255, 0, 0, 0.4); |
| } |
| |
| .menu-btn:active { |
| transform: translateY(1px); |
| } |
| |
| #difficulty-select { |
| margin: 20px 0; |
| display: flex; |
| gap: 10px; |
| } |
| |
| .difficulty-btn { |
| padding: 10px 20px; |
| border-radius: 50px; |
| border: 2px solid rgba(255, 255, 255, 0.3); |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .difficulty-btn.active { |
| background: linear-gradient(to right, #ff0000, #ff6600); |
| border-color: transparent; |
| transform: scale(1.05); |
| } |
| |
| #weapon-select { |
| margin: 20px 0; |
| display: flex; |
| gap: 15px; |
| } |
| |
| .weapon-card { |
| background-color: rgba(0, 0, 0, 0.5); |
| border-radius: 10px; |
| padding: 15px; |
| width: 120px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .weapon-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 5px 15px rgba(255, 0, 0, 0.3); |
| } |
| |
| .weapon-card.active { |
| border: 2px solid #ff0000; |
| transform: scale(1.05); |
| background-color: rgba(255, 0, 0, 0.1); |
| } |
| |
| .weapon-icon { |
| font-size: 2rem; |
| margin-bottom: 10px; |
| } |
| |
| .weapon-name { |
| font-weight: bold; |
| margin-bottom: 5px; |
| } |
| |
| .weapon-stats { |
| font-size: 0.8rem; |
| color: #ccc; |
| } |
| |
| #mission-briefing { |
| max-width: 600px; |
| text-align: center; |
| margin: 20px 0; |
| line-height: 1.6; |
| color: #ccc; |
| } |
| |
| #sniper-sway { |
| position: absolute; |
| width: 100px; |
| height: 100px; |
| pointer-events: none; |
| z-index: 85; |
| transform-origin: center; |
| } |
| |
| .hit-marker { |
| position: absolute; |
| width: 30px; |
| height: 30px; |
| background-image: url('https://cdn-icons-png.flaticon.com/512/102/102665.png'); |
| background-size: contain; |
| z-index: 110; |
| opacity: 0; |
| transform: translate(-50%, -50%) scale(0.5); |
| animation: hitMarker 0.5s forwards; |
| } |
| |
| @keyframes hitMarker { |
| 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); } |
| 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); } |
| 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); } |
| } |
| |
| #bullet-drop-indicator { |
| position: absolute; |
| width: 10px; |
| height: 10px; |
| background-color: rgba(255, 255, 0, 0.7); |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 86; |
| transform: translate(-50%, -50%); |
| box-shadow: 0 0 10px rgba(255, 255, 0, 0.5); |
| display: none; |
| } |
| |
| #mission-timer { |
| position: absolute; |
| top: 120px; |
| left: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| #level-display { |
| position: absolute; |
| top: 170px; |
| left: 20px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 10px; |
| border-radius: 5px; |
| z-index: 200; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .xp-bar { |
| width: 100%; |
| height: 5px; |
| background-color: rgba(255, 255, 255, 0.2); |
| border-radius: 5px; |
| margin-top: 5px; |
| overflow: hidden; |
| } |
| |
| .xp-progress { |
| height: 100%; |
| background: linear-gradient(to right, #ff0000, #ff6600); |
| width: 0%; |
| transition: width 0.3s; |
| } |
| |
| #notification { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background-color: rgba(0, 0, 0, 0.8); |
| color: white; |
| padding: 15px 30px; |
| border-radius: 5px; |
| z-index: 500; |
| opacity: 0; |
| transition: opacity 0.3s; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 0, 0, 0.5); |
| text-align: center; |
| max-width: 80%; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="main-menu"> |
| <h1 class="menu-title">SNIPER ELITE PRO</h1> |
| |
| <div id="difficulty-select"> |
| <div class="difficulty-btn active" data-difficulty="easy">Fácil</div> |
| <div class="difficulty-btn" data-difficulty="medium">Médio</div> |
| <div class="difficulty-btn" data-difficulty="hard">Difícil</div> |
| <div class="difficulty-btn" data-difficulty="expert">Especialista</div> |
| </div> |
| |
| <div id="weapon-select"> |
| <div class="weapon-card active" data-weapon="m24"> |
| <div class="weapon-icon"><i class="fas fa-gun"></i></div> |
| <div class="weapon-name">M24</div> |
| <div class="weapon-stats">Dano: Médio<br>Estabilidade: Alta<br>Munição: 10</div> |
| </div> |
| <div class="weapon-card" data-weapon="awp"> |
| <div class="weapon-icon"><i class="fas fa-gun"></i></div> |
| <div class="weapon-name">AWP</div> |
| <div class="weapon-stats">Dano: Alto<br>Estabilidade: Baixa<br>Munição: 5</div> |
| </div> |
| <div class="weapon-card" data-weapon="barrett"> |
| <div class="weapon-icon"><i class="fas fa-gun"></i></div> |
| <div class="weapon-name">Barrett</div> |
| <div class="weapon-stats">Dano: Altíssimo<br>Estabilidade: Muito Baixa<br>Munição: 4</div> |
| </div> |
| </div> |
| |
| <div id="mission-briefing"> |
| <h3 class="text-xl font-bold mb-2">MISSÃO ATUAL: OPERAÇÃO FANTASMA</h3> |
| <p>Você foi destacado como atirador de elite para neutralizar alvos hostis em zona de conflito. Cuidado com civis e reféns - atirar neles resultará em penalidades. Complete a missão no tempo determinado e avance para o próximo nível.</p> |
| </div> |
| |
| <button id="start-btn" class="menu-btn"> |
| <i class="fas fa-play mr-2"></i>INICIAR MISSÃO |
| </button> |
| <button id="how-to-play-btn" class="menu-btn"> |
| <i class="fas fa-question-circle mr-2"></i>COMO JOGAR |
| </button> |
| </div> |
| |
| <div id="game-container"> |
| <div id="scope"></div> |
| <div id="reticle"></div> |
| <div id="breathing-effect"></div> |
| <div id="recoil-effect"></div> |
| <div id="sniper-sway"></div> |
| <div id="bullet-drop-indicator"></div> |
| |
| <div id="wind-indicator"> |
| <i class="fas fa-wind mr-2"></i> |
| <span id="wind-value">0 m/s</span> |
| <span id="wind-direction">→</span> |
| </div> |
| |
| <div id="ammo-counter"> |
| <i class="fas fa-bullseye mr-2"></i> |
| <span id="ammo">10</span>/<span id="max-ammo">10</span> |
| </div> |
| |
| <div id="score-display"> |
| <i class="fas fa-star mr-2"></i> |
| Pontuação: <span id="score">0</span> |
| </div> |
| |
| <div id="distance-display"> |
| <i class="fas fa-ruler-combined mr-2"></i> |
| Distância: <span id="distance">0</span>m |
| </div> |
| |
| <div id="mission-timer"> |
| <i class="fas fa-clock mr-2"></i> |
| Tempo: <span id="timer">01:00</span> |
| </div> |
| |
| <div id="level-display"> |
| <i class="fas fa-level-up-alt mr-2"></i> |
| Nível: <span id="level">1</span> |
| <div class="xp-bar"> |
| <div class="xp-progress" id="xp-bar"></div> |
| </div> |
| </div> |
| |
| <div id="zoom-controls"> |
| <button id="zoom-in" class="zoom-btn">+</button> |
| <button id="zoom-out" class="zoom-btn">-</button> |
| </div> |
| |
| <div id="sound-control"> |
| <button id="sound-btn" class="zoom-btn"> |
| <i class="fas fa-volume-up"></i> |
| </button> |
| </div> |
| |
| <div id="game-over"> |
| <h1 class="text-4xl font-bold mb-4" id="result-title">MISSÃO CONCLUÍDA</h1> |
| <p class="text-2xl mb-2">Pontuação Final: <span id="final-score">0</span></p> |
| <p class="text-xl mb-2">Precisão: <span id="accuracy">0</span>%</p> |
| <p class="text-xl mb-2">Alvos Neutralizados: <span id="targets-hit">0</span>/<span id="total-targets">0</span></p> |
| <p class="text-xl mb-6">XP Ganho: <span id="xp-earned">0</span></p> |
| <button id="restart-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg text-xl"> |
| <i class="fas fa-redo mr-2"></i>Jogar Novamente |
| </button> |
| <button id="menu-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-6 rounded-lg text-xl mt-4"> |
| <i class="fas fa-home mr-2"></i>Menu Principal |
| </button> |
| </div> |
| |
| <div id="notification"></div> |
| </div> |
|
|
| <audio id="gunshot-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-gun-shot-1669.mp3" preload="auto"></audio> |
| <audio id="reload-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-gun-reload-2293.mp3" preload="auto"></audio> |
| <audio id="hit-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-arrow-whoosh-1491.mp3" preload="auto"></audio> |
| <audio id="miss-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-short-explosion-1694.mp3" preload="auto"></audio> |
| <audio id="bg-music" loop src="https://assets.mixkit.co/music/preview/mixkit-suspense-ambience-267.mp3" preload="auto"></audio> |
| <audio id="headshot-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-explosion-1689.mp3" preload="auto"></audio> |
| <audio id="penalty-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-negative-answer-interface-beep-220.mp3" preload="auto"></audio> |
| <audio id="level-up-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio> |
|
|
| <script> |
| |
| const config = { |
| ammo: 10, |
| maxAmmo: 10, |
| score: 0, |
| targetsHit: 0, |
| totalTargets: 0, |
| shotsFired: 0, |
| zoomLevel: 1, |
| maxZoom: 3, |
| windSpeed: 0, |
| windDirection: 0, |
| soundEnabled: true, |
| gameTime: 60000, |
| gameStarted: false, |
| gameOver: false, |
| difficulty: 'easy', |
| weapon: 'm24', |
| level: 1, |
| xp: 0, |
| xpToNextLevel: 100, |
| sniperSway: 0, |
| breathingPhase: 0, |
| showBulletDrop: false, |
| currentMission: 1, |
| totalMissions: 5, |
| missionObjectives: { |
| 1: { targets: 10, time: 60000, description: "Neutralize 10 alvos em 1 minuto" }, |
| 2: { targets: 15, time: 60000, description: "Neutralize 15 alvos em 1 minuto" }, |
| 3: { targets: 20, time: 60000, description: "Neutralize 20 alvos em 1 minuto" }, |
| 4: { targets: 25, time: 60000, description: "Neutralize 25 alvos em 1 minuto" }, |
| 5: { targets: 30, time: 60000, description: "Neutralize 30 alvos em 1 minuto" } |
| }, |
| weapons: { |
| m24: { name: "M24", damage: 2, stability: 0.8, ammo: 10, reloadTime: 2000, sway: 1.0 }, |
| awp: { name: "AWP", damage: 3, stability: 0.6, ammo: 5, reloadTime: 2500, sway: 1.5 }, |
| barrett: { name: "Barrett", damage: 4, stability: 0.4, ammo: 4, reloadTime: 3000, sway: 2.0 } |
| } |
| }; |
| |
| |
| const gameContainer = document.getElementById('game-container'); |
| const scope = document.getElementById('scope'); |
| const reticle = document.getElementById('reticle'); |
| const windValue = document.getElementById('wind-value'); |
| const windDirection = document.getElementById('wind-direction'); |
| const ammoDisplay = document.getElementById('ammo'); |
| const maxAmmoDisplay = document.getElementById('max-ammo'); |
| const scoreDisplay = document.getElementById('score'); |
| const distanceDisplay = document.getElementById('distance'); |
| const gameOverScreen = document.getElementById('game-over'); |
| const finalScore = document.getElementById('final-score'); |
| const accuracyDisplay = document.getElementById('accuracy'); |
| const restartBtn = document.getElementById('restart-btn'); |
| const zoomInBtn = document.getElementById('zoom-in'); |
| const zoomOutBtn = document.getElementById('zoom-out'); |
| const soundBtn = document.getElementById('sound-btn'); |
| const recoilEffect = document.getElementById('recoil-effect'); |
| const sniperSway = document.getElementById('sniper-sway'); |
| const bulletDropIndicator = document.getElementById('bullet-drop-indicator'); |
| const timerDisplay = document.getElementById('timer'); |
| const levelDisplay = document.getElementById('level'); |
| const xpBar = document.getElementById('xp-bar'); |
| const targetsHitDisplay = document.getElementById('targets-hit'); |
| const totalTargetsDisplay = document.getElementById('total-targets'); |
| const xpEarnedDisplay = document.getElementById('xp-earned'); |
| const resultTitle = document.getElementById('result-title'); |
| const mainMenu = document.getElementById('main-menu'); |
| const startBtn = document.getElementById('start-btn'); |
| const howToPlayBtn = document.getElementById('how-to-play-btn'); |
| const menuBtn = document.getElementById('menu-btn'); |
| const difficultyBtns = document.querySelectorAll('.difficulty-btn'); |
| const weaponCards = document.querySelectorAll('.weapon-card'); |
| const notification = document.getElementById('notification'); |
| |
| |
| const gunshotSound = document.getElementById('gunshot-sound'); |
| const reloadSound = document.getElementById('reload-sound'); |
| const hitSound = document.getElementById('hit-sound'); |
| const missSound = document.getElementById('miss-sound'); |
| const bgMusic = document.getElementById('bg-music'); |
| const headshotSound = document.getElementById('headshot-sound'); |
| const penaltySound = document.getElementById('penalty-sound'); |
| const levelUpSound = document.getElementById('level-up-sound'); |
| |
| |
| let mouseX = 0; |
| let mouseY = 0; |
| let targets = []; |
| let bulletHoles = []; |
| let gameTimer; |
| let targetSpawnInterval; |
| let windChangeInterval; |
| let countdownInterval; |
| let timeLeft = 0; |
| let swayInterval; |
| let breathingInterval; |
| |
| |
| function initGame() { |
| |
| config.ammo = config.weapons[config.weapon].ammo; |
| config.maxAmmo = config.weapons[config.weapon].ammo; |
| config.score = 0; |
| config.targetsHit = 0; |
| config.totalTargets = 0; |
| config.shotsFired = 0; |
| config.zoomLevel = 1; |
| config.windSpeed = 0; |
| config.windDirection = 0; |
| config.gameOver = false; |
| config.gameStarted = true; |
| |
| |
| const mission = config.missionObjectives[config.currentMission]; |
| timeLeft = mission.time; |
| config.totalTargets = mission.targets; |
| |
| |
| updateAmmoDisplay(); |
| updateScoreDisplay(); |
| updateWindDisplay(); |
| updateTimerDisplay(); |
| updateLevelDisplay(); |
| totalTargetsDisplay.textContent = config.totalTargets; |
| targetsHitDisplay.textContent = config.targetsHit; |
| |
| |
| clearTargets(); |
| clearBulletHoles(); |
| |
| |
| gameOverScreen.style.display = 'none'; |
| mainMenu.style.display = 'none'; |
| |
| |
| gameContainer.style.transform = 'scale(1)'; |
| |
| |
| startGameTimer(); |
| |
| |
| startTargetSpawning(); |
| |
| |
| startWindChanges(); |
| |
| |
| if (config.soundEnabled) { |
| bgMusic.currentTime = 0; |
| bgMusic.volume = 0.3; |
| bgMusic.play(); |
| } |
| |
| |
| startSniperEffects(); |
| |
| |
| showNotification(`MISSÃO ${config.currentMission}: ${mission.description}`); |
| } |
| |
| |
| function updateAmmoDisplay() { |
| ammoDisplay.textContent = config.ammo; |
| maxAmmoDisplay.textContent = config.maxAmmo; |
| } |
| |
| |
| function updateScoreDisplay() { |
| scoreDisplay.textContent = config.score; |
| } |
| |
| |
| function updateWindDisplay() { |
| windValue.textContent = `${config.windSpeed.toFixed(1)} m/s`; |
| |
| |
| let directionSymbol; |
| if (config.windDirection >= 337.5 || config.windDirection < 22.5) directionSymbol = '→'; |
| else if (config.windDirection < 67.5) directionSymbol = '↗'; |
| else if (config.windDirection < 112.5) directionSymbol = '↑'; |
| else if (config.windDirection < 157.5) directionSymbol = '↖'; |
| else if (config.windDirection < 202.5) directionSymbol = '←'; |
| else if (config.windDirection < 247.5) directionSymbol = '↙'; |
| else if (config.windDirection < 292.5) directionSymbol = '↓'; |
| else directionSymbol = '↘'; |
| |
| windDirection.textContent = directionSymbol; |
| } |
| |
| |
| function updateDistanceDisplay(x, y) { |
| |
| const distance = Math.sqrt(Math.pow(x - window.innerWidth/2, 2) + Math.pow(y - window.innerHeight/2, 2)); |
| const scaledDistance = Math.floor(distance / 10); |
| distanceDisplay.textContent = scaledDistance; |
| return scaledDistance; |
| } |
| |
| |
| function updateTimerDisplay() { |
| const minutes = Math.floor(timeLeft / 60000); |
| const seconds = Math.floor((timeLeft % 60000) / 1000); |
| timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; |
| |
| |
| if (timeLeft < 10000) { |
| timerDisplay.style.color = '#ff0000'; |
| } else { |
| timerDisplay.style.color = '#ffffff'; |
| } |
| } |
| |
| |
| function updateLevelDisplay() { |
| levelDisplay.textContent = config.level; |
| xpBar.style.width = `${(config.xp / config.xpToNextLevel) * 100}%`; |
| } |
| |
| |
| function startGameTimer() { |
| clearInterval(countdownInterval); |
| |
| countdownInterval = setInterval(() => { |
| timeLeft -= 1000; |
| updateTimerDisplay(); |
| |
| if (timeLeft <= 0) { |
| clearInterval(countdownInterval); |
| checkMissionCompletion(); |
| } |
| }, 1000); |
| } |
| |
| |
| function checkMissionCompletion() { |
| if (config.targetsHit >= config.totalTargets) { |
| |
| const xpEarned = calculateXPEarned(true); |
| config.xp += xpEarned; |
| |
| |
| checkLevelUp(); |
| |
| |
| if (config.currentMission < config.totalMissions) { |
| config.currentMission++; |
| showNotification(`MISSÃO ${config.currentMission} DESBLOQUEADA!`, 3000); |
| endGame(true); |
| } else { |
| endGame(true); |
| resultTitle.textContent = "TODAS AS MISSÕES CONCLUÍDAS!"; |
| } |
| } else { |
| |
| endGame(false); |
| resultTitle.textContent = "MISSÃO FALHOU"; |
| } |
| } |
| |
| |
| function calculateXPEarned(success) { |
| if (!success) return 0; |
| |
| let baseXP = 100; |
| let multiplier = 1; |
| |
| |
| if (config.difficulty === 'medium') multiplier = 1.2; |
| else if (config.difficulty === 'hard') multiplier = 1.5; |
| else if (config.difficulty === 'expert') multiplier = 2; |
| |
| |
| const accuracy = config.shotsFired > 0 ? (config.targetsHit / config.shotsFired) : 0; |
| if (accuracy > 0.8) multiplier += 0.3; |
| else if (accuracy > 0.6) multiplier += 0.2; |
| else if (accuracy > 0.4) multiplier += 0.1; |
| |
| |
| const timeBonus = timeLeft / config.missionObjectives[config.currentMission].time; |
| multiplier += timeBonus * 0.5; |
| |
| return Math.floor(baseXP * multiplier); |
| } |
| |
| |
| function checkLevelUp() { |
| if (config.xp >= config.xpToNextLevel) { |
| config.level++; |
| config.xp -= config.xpToNextLevel; |
| config.xpToNextLevel = Math.floor(config.xpToNextLevel * 1.2); |
| |
| if (config.soundEnabled) { |
| levelUpSound.currentTime = 0; |
| levelUpSound.play(); |
| } |
| |
| showNotification(`LEVEL UP! Agora você é nível ${config.level}`, 3000); |
| updateLevelDisplay(); |
| } |
| } |
| |
| |
| function startTargetSpawning() { |
| clearInterval(targetSpawnInterval); |
| |
| |
| let spawnInterval; |
| switch(config.difficulty) { |
| case 'easy': spawnInterval = 3000; break; |
| case 'medium': spawnInterval = 2500; break; |
| case 'hard': spawnInterval = 2000; break; |
| case 'expert': spawnInterval = 1500; break; |
| default: spawnInterval = 3000; |
| } |
| |
| targetSpawnInterval = setInterval(spawnTarget, spawnInterval); |
| } |
| |
| |
| function startWindChanges() { |
| clearInterval(windChangeInterval); |
| |
| |
| let windChangeIntervalTime; |
| switch(config.difficulty) { |
| case 'easy': windChangeIntervalTime = 10000; break; |
| case 'medium': windChangeIntervalTime = 8000; break; |
| case 'hard': windChangeIntervalTime = 6000; break; |
| case 'expert': windChangeIntervalTime = 4000; break; |
| default: windChangeIntervalTime = 10000; |
| } |
| |
| windChangeInterval = setInterval(changeWind, windChangeIntervalTime); |
| changeWind(); |
| } |
| |
| |
| function startSniperEffects() { |
| clearInterval(swayInterval); |
| clearInterval(breathingInterval); |
| |
| |
| const weaponSway = config.weapons[config.weapon].sway; |
| let difficultyMultiplier = 1; |
| |
| switch(config.difficulty) { |
| case 'medium': difficultyMultiplier = 1.3; break; |
| case 'hard': difficultyMultiplier = 1.7; break; |
| case 'expert': difficultyMultiplier = 2.2; break; |
| } |
| |
| config.sniperSway = 0; |
| config.breathingPhase = 0; |
| |
| swayInterval = setInterval(() => { |
| |
| config.sniperSway = Math.sin(Date.now() / 1000 * weaponSway * difficultyMultiplier) * 20; |
| |
| |
| config.breathingPhase += 0.01; |
| const breathingOffset = Math.sin(config.breathingPhase) * 5; |
| |
| |
| reticle.style.transform = `translate(-50%, -50%) translate(${config.sniperSway + breathingOffset}px, ${Math.cos(Date.now() / 1000 * weaponSway * 0.8 * difficultyMultiplier) * 10 + breathingOffset}px)`; |
| |
| |
| if (config.zoomLevel > 1.5 && config.showBulletDrop) { |
| const dropDistance = calculateBulletDrop(); |
| bulletDropIndicator.style.display = 'block'; |
| bulletDropIndicator.style.left = `${mouseX + config.sniperSway}px`; |
| bulletDropIndicator.style.top = `${mouseY + dropDistance}px`; |
| } else { |
| bulletDropIndicator.style.display = 'none'; |
| } |
| }, 16); |
| } |
| |
| |
| function calculateBulletDrop() { |
| |
| const distance = Math.sqrt(Math.pow(mouseX - window.innerWidth/2, 2) + Math.pow(mouseY - window.innerHeight/2, 2)); |
| const drop = (distance / 1000) * (config.zoomLevel * 0.5); |
| |
| |
| const windEffect = config.windSpeed * Math.sin(config.windDirection * Math.PI / 180) * 0.5; |
| |
| return drop + windEffect; |
| } |
| |
| |
| function changeWind() { |
| |
| let maxWindSpeed; |
| switch(config.difficulty) { |
| case 'easy': maxWindSpeed = 5; break; |
| case 'medium': maxWindSpeed = 8; break; |
| case 'hard': maxWindSpeed = 12; break; |
| case 'expert': maxWindSpeed = 15; break; |
| default: maxWindSpeed = 5; |
| } |
| |
| config.windSpeed = Math.random() * maxWindSpeed; |
| config.windDirection = Math.random() * 360; |
| updateWindDisplay(); |
| } |
| |
| |
| function spawnTarget() { |
| if (config.gameOver || config.totalTargets <= targets.length) return; |
| |
| const target = document.createElement('div'); |
| |
| |
| const targetTypeRoll = Math.random(); |
| let targetType, targetClass, health, speed, score; |
| |
| if (targetTypeRoll < 0.7) { |
| targetType = 'soldier'; |
| targetClass = 'target soldier'; |
| health = 2; |
| speed = 1; |
| score = 100; |
| } else if (targetTypeRoll < 0.9) { |
| targetType = 'vip'; |
| targetClass = 'target vip'; |
| health = 3; |
| speed = 0.8; |
| score = 250; |
| } else if (targetTypeRoll < 0.95) { |
| targetType = 'civilian'; |
| targetClass = 'target civilian'; |
| health = 1; |
| speed = 1.2; |
| score = -200; |
| } else { |
| targetType = 'hostage'; |
| targetClass = 'target hostage'; |
| health = 1; |
| speed = 0.7; |
| score = -300; |
| } |
| |
| target.className = targetClass; |
| |
| |
| const x = 100 + Math.random() * (window.innerWidth - 200); |
| const y = 100 + Math.random() * (window.innerHeight - 200); |
| |
| target.style.left = `${x}px`; |
| target.style.top = `${y}px`; |
| |
| |
| const speedX = (Math.random() - 0.5) * 2 * speed; |
| const speedY = (Math.random() - 0.5) * 2 * speed; |
| |
| |
| gameContainer.appendChild(target); |
| |
| |
| targets.push({ |
| element: target, |
| x: x, |
| y: y, |
| speedX: speedX, |
| speedY: speedY, |
| health: health, |
| maxHealth: health, |
| hit: false, |
| type: targetType, |
| score: score, |
| headshot: false |
| }); |
| |
| |
| setTimeout(() => { |
| if (!target.hit) { |
| removeTarget(target); |
| } |
| }, 10000); |
| } |
| |
| |
| function removeTarget(targetElement) { |
| const index = targets.findIndex(t => t.element === targetElement); |
| if (index !== -1) { |
| targets.splice(index, 1); |
| if (targetElement.parentNode) { |
| gameContainer.removeChild(targetElement); |
| } |
| } |
| } |
| |
| |
| function clearTargets() { |
| targets.forEach(target => { |
| if (target.element.parentNode) { |
| gameContainer.removeChild(target.element); |
| } |
| }); |
| targets = []; |
| } |
| |
| |
| function clearBulletHoles() { |
| bulletHoles.forEach(hole => { |
| if (hole.parentNode) { |
| gameContainer.removeChild(hole); |
| } |
| }); |
| bulletHoles = []; |
| } |
| |
| |
| function fireShot() { |
| if (config.ammo <= 0 || config.gameOver) return; |
| |
| config.ammo--; |
| config.shotsFired++; |
| updateAmmoDisplay(); |
| |
| |
| showRecoil(); |
| |
| |
| if (config.soundEnabled) { |
| gunshotSound.currentTime = 0; |
| gunshotSound.volume = 0.7; |
| gunshotSound.play(); |
| } |
| |
| |
| checkHit(); |
| |
| |
| if (config.ammo <= 0) { |
| setTimeout(reload, config.weapons[config.weapon].reloadTime); |
| } |
| } |
| |
| |
| function showRecoil() { |
| recoilEffect.style.opacity = '0.3'; |
| |
| |
| const recoilAmount = (1 - config.weapons[config.weapon].stability) * 30; |
| gameContainer.style.transform = `scale(${config.zoomLevel}) translate(${Math.random() * recoilAmount - recoilAmount/2}px, ${Math.random() * recoilAmount - recoilAmount/2}px)`; |
| gameContainer.style.transformOrigin = `${mouseX/window.innerWidth*100}% ${mouseY/window.innerHeight*100}%`; |
| |
| setTimeout(() => { |
| recoilEffect.style.opacity = '0'; |
| gameContainer.style.transform = `scale(${config.zoomLevel})`; |
| gameContainer.style.transformOrigin = `${mouseX/window.innerWidth*100}% ${mouseY/window.innerHeight*100}%`; |
| }, 100); |
| } |
| |
| |
| function checkHit() { |
| |
| const windOffsetX = Math.cos(config.windDirection * Math.PI / 180) * config.windSpeed * 5; |
| const windOffsetY = Math.sin(config.windDirection * Math.PI / 180) * config.windSpeed * 5; |
| |
| const shotX = mouseX + windOffsetX + config.sniperSway; |
| const shotY = mouseY + windOffsetY + calculateBulletDrop(); |
| |
| |
| createBulletHole(shotX, shotY); |
| |
| |
| let hit = false; |
| |
| for (let i = 0; i < targets.length; i++) { |
| const target = targets[i]; |
| const targetRect = target.element.getBoundingClientRect(); |
| |
| if ( |
| shotX >= targetRect.left && |
| shotX <= targetRect.right && |
| shotY >= targetRect.top && |
| shotY <= targetRect.bottom |
| ) { |
| |
| hit = true; |
| |
| |
| const isHeadshot = shotY < targetRect.top + targetRect.height * 0.3; |
| |
| |
| let damage = config.weapons[config.weapon].damage; |
| if (isHeadshot) { |
| damage *= 2; |
| target.headshot = true; |
| } |
| |
| target.health -= damage; |
| |
| |
| if (config.soundEnabled) { |
| if (isHeadshot) { |
| headshotSound.currentTime = 0; |
| headshotSound.play(); |
| } else { |
| hitSound.currentTime = 0; |
| hitSound.play(); |
| } |
| } |
| |
| |
| createBloodSplatter(shotX, shotY); |
| |
| |
| showHitMarker(shotX, shotY, isHeadshot); |
| |
| |
| if (target.health <= 0) { |
| target.hit = true; |
| config.targetsHit++; |
| targetsHitDisplay.textContent = config.targetsHit; |
| |
| |
| const distance = updateDistanceDisplay(target.x, target.y); |
| let score = target.score; |
| |
| |
| if (target.headshot) { |
| score *= 1.5; |
| showNotification("HEADSHOT!", 1000); |
| } |
| |
| |
| score += distance * 2; |
| |
| |
| switch(config.difficulty) { |
| case 'medium': score *= 1.2; break; |
| case 'hard': score *= 1.5; break; |
| case 'expert': score *= 2; break; |
| } |
| |
| config.score += Math.floor(score); |
| |
| updateScoreDisplay(); |
| removeTarget(target.element); |
| |
| |
| if (target.type === 'civilian' || target.type === 'hostage') { |
| if (config.soundEnabled) { |
| penaltySound.currentTime = 0; |
| penaltySound.play(); |
| } |
| |
| const penaltyText = target.type === 'civilian' ? "CIVIL ATINGIDO!" : "REFÉM ATINGIDO!"; |
| showNotification(penaltyText, 2000); |
| } |
| } else { |
| |
| target.element.style.transform = 'scale(1.2)'; |
| setTimeout(() => { |
| target.element.style.transform = 'scale(1)'; |
| }, 100); |
| } |
| |
| break; |
| } |
| } |
| |
| |
| if (!hit && config.soundEnabled) { |
| missSound.currentTime = 0; |
| missSound.play(); |
| } |
| } |
| |
| |
| function showHitMarker(x, y, isHeadshot) { |
| const marker = document.createElement('div'); |
| marker.className = 'hit-marker'; |
| |
| if (isHeadshot) { |
| marker.style.filter = 'hue-rotate(300deg) brightness(1.5)'; |
| } |
| |
| marker.style.left = `${x}px`; |
| marker.style.top = `${y}px`; |
| |
| gameContainer.appendChild(marker); |
| |
| |
| setTimeout(() => { |
| if (marker.parentNode) { |
| gameContainer.removeChild(marker); |
| } |
| }, 500); |
| } |
| |
| |
| function createBulletHole(x, y) { |
| const hole = document.createElement('div'); |
| hole.className = 'bullet-hole'; |
| hole.style.left = `${x - 10}px`; |
| hole.style.top = `${y - 10}px`; |
| |
| gameContainer.appendChild(hole); |
| bulletHoles.push(hole); |
| |
| |
| if (bulletHoles.length > 20) { |
| const oldHole = bulletHoles.shift(); |
| if (oldHole.parentNode) { |
| gameContainer.removeChild(oldHole); |
| } |
| } |
| } |
| |
| |
| function createBloodSplatter(x, y) { |
| const splatter = document.createElement('div'); |
| splatter.className = 'blood-splatter'; |
| splatter.style.left = `${x - 40}px`; |
| splatter.style.top = `${y - 40}px`; |
| |
| gameContainer.appendChild(splatter); |
| |
| |
| setTimeout(() => { |
| if (splatter.parentNode) { |
| gameContainer.removeChild(splatter); |
| } |
| }, 2000); |
| } |
| |
| |
| function reload() { |
| if (config.ammo >= config.maxAmmo || config.gameOver) return; |
| |
| |
| if (config.soundEnabled) { |
| reloadSound.currentTime = 0; |
| reloadSound.play(); |
| } |
| |
| |
| setTimeout(() => { |
| config.ammo = config.maxAmmo; |
| updateAmmoDisplay(); |
| }, config.weapons[config.weapon].reloadTime); |
| } |
| |
| |
| function endGame(success) { |
| config.gameOver = true; |
| clearInterval(targetSpawnInterval); |
| clearInterval(windChangeInterval); |
| clearInterval(countdownInterval); |
| clearInterval(swayInterval); |
| clearInterval(breathingInterval); |
| |
| |
| const accuracy = config.shotsFired > 0 |
| ? Math.floor((config.targetsHit / config.shotsFired) * 100) |
| : 0; |
| |
| |
| const xpEarned = calculateXPEarned(success); |
| config.xp += xpEarned; |
| |
| |
| checkLevelUp(); |
| |
| |
| finalScore.textContent = config.score; |
| accuracyDisplay.textContent = accuracy; |
| targetsHitDisplay.textContent = config.targetsHit; |
| totalTargetsDisplay.textContent = config.totalTargets; |
| xpEarnedDisplay.textContent = xpEarned; |
| |
| if (success) { |
| resultTitle.textContent = "MISSÃO CONCLUÍDA"; |
| resultTitle.style.color = "#4ade80"; |
| } else { |
| resultTitle.textContent = "MISSÃO FALHOU"; |
| resultTitle.style.color = "#f87171"; |
| } |
| |
| gameOverScreen.style.display = 'flex'; |
| |
| |
| bgMusic.pause(); |
| } |
| |
| |
| function zoomIn() { |
| if (config.zoomLevel >= config.maxZoom) return; |
| |
| config.zoomLevel += 0.5; |
| updateZoom(); |
| |
| |
| config.showBulletDrop = config.zoomLevel > 1.5; |
| } |
| |
| |
| function zoomOut() { |
| if (config.zoomLevel <= 1) return; |
| |
| config.zoomLevel -= 0.5; |
| updateZoom(); |
| |
| |
| if (config.zoomLevel <= 1.5) { |
| config.showBulletDrop = false; |
| bulletDropIndicator.style.display = 'none'; |
| } |
| } |
| |
| |
| function updateZoom() { |
| gameContainer.style.transform = `scale(${config.zoomLevel})`; |
| gameContainer.style.transformOrigin = `${mouseX/window.innerWidth*100}% ${mouseY/window.innerHeight*100}%`; |
| } |
| |
| |
| function toggleSound() { |
| config.soundEnabled = !config.soundEnabled; |
| |
| if (config.soundEnabled) { |
| soundBtn.innerHTML = '<i class="fas fa-volume-up"></i>'; |
| if (config.gameStarted && !config.gameOver) { |
| bgMusic.play(); |
| } |
| } else { |
| soundBtn.innerHTML = '<i class="fas fa-volume-mute"></i>'; |
| bgMusic.pause(); |
| } |
| } |
| |
| |
| function showNotification(message, duration = 2000) { |
| notification.textContent = message; |
| notification.style.opacity = '1'; |
| |
| setTimeout(() => { |
| notification.style.opacity = '0'; |
| }, duration); |
| } |
| |
| |
| document.addEventListener('mousemove', (e) => { |
| mouseX = e.clientX; |
| mouseY = e.clientY; |
| |
| |
| scope.style.left = `${mouseX}px`; |
| scope.style.top = `${mouseY}px`; |
| |
| |
| document.getElementById('breathing-effect').style.left = `${mouseX}px`; |
| document.getElementById('breathing-effect').style.top = `${mouseY}px`; |
| |
| |
| if (config.zoomLevel > 1) { |
| gameContainer.style.transformOrigin = `${mouseX/window.innerWidth*100}% ${mouseY/window.innerHeight*100}%`; |
| } |
| }); |
| |
| document.addEventListener('click', (e) => { |
| if (!config.gameStarted) return; |
| if (config.gameOver) return; |
| |
| fireShot(); |
| }); |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.code === 'Space') { |
| e.preventDefault(); |
| if (!config.gameStarted) { |
| initGame(); |
| } else if (config.ammo <= 0 && !config.gameOver) { |
| reload(); |
| } else if (!config.gameOver) { |
| fireShot(); |
| } |
| } else if (e.code === 'KeyR' && config.gameStarted && !config.gameOver) { |
| reload(); |
| } else if (e.code === 'KeyZ') { |
| zoomIn(); |
| } else if (e.code === 'KeyX') { |
| zoomOut(); |
| } else if (e.code === 'KeyM') { |
| toggleSound(); |
| } else if (e.code === 'Escape' && config.gameStarted && !config.gameOver) { |
| endGame(false); |
| } |
| }); |
| |
| zoomInBtn.addEventListener('click', zoomIn); |
| zoomOutBtn.addEventListener('click', zoomOut); |
| restartBtn.addEventListener('click', initGame); |
| soundBtn.addEventListener('click', toggleSound); |
| startBtn.addEventListener('click', initGame); |
| menuBtn.addEventListener('click', () => { |
| gameOverScreen.style.display = 'none'; |
| mainMenu.style.display = 'flex'; |
| }); |
| |
| howToPlayBtn.addEventListener('click', () => { |
| alert('CONTROLES DO JOGO:\n\n- CLIQUE ou ESPAÇO: Atirar\n- R: Recarregar\n- Z: Zoom In\n- X: Zoom Out\n- M: Alternar Som\n- ESC: Sair da Missão\n\nOBJETIVO:\nNeutralize os alvos hostis (soldados e VIPs) evitando civis e reféns. Complete cada missão dentro do tempo limite para avançar.\n\nDICAS:\n- Mantenha a calma e controle sua respiração\n- Considere o vento e a distância\n- Headshots dão mais pontos\n- Armas diferentes têm características únicas'); |
| }); |
| |
| |
| difficultyBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| difficultyBtns.forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| config.difficulty = btn.dataset.difficulty; |
| }); |
| }); |
| |
| |
| weaponCards.forEach(card => { |
| card.addEventListener('click', () => { |
| weaponCards.forEach(c => c.classList.remove('active')); |
| card.classList.add('active'); |
| config.weapon = card.dataset.weapon; |
| }); |
| }); |
| |
| |
| function updateTargets() { |
| targets.forEach(target => { |
| |
| target.x += target.speedX; |
| target.y += target.speedY; |
| |
| |
| if (target.x <= 0 || target.x >= window.innerWidth - 60) { |
| target.speedX *= -1; |
| } |
| |
| if (target.y <= 0 || target.y >= window.innerHeight - 60) { |
| target.speedY *= -1; |
| } |
| |
| |
| target.element.style.left = `${target.x}px`; |
| target.element.style.top = `${target.y}px`; |
| }); |
| |
| requestAnimationFrame(updateTargets); |
| } |
| |
| |
| updateTargets(); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Xacodavt/o" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |