diff --git a/index.html b/index.html index 94ae6dc..33b68d6 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - +
@@ -23,16 +23,72 @@ } .target { position: absolute; - width: 50px; - height: 50px; + width: 70px; + height: 70px; border-radius: 50%; - transition: transform 0.1s; + transition: transform 0.1s, opacity 0.2s; + background-size: cover; + background-position: center; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.7); } + + .target::before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.3); + z-index: 2; + } + + .target::after { + content: ''; + position: absolute; + top: 25px; + left: 25px; + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.6); + z-index: 3; + } + .target-type-1 { - background-color: red; + border: 3px solid #aa0000; } + .target-type-2 { - background-color: blue; + border: 3px solid #0033aa; + } + + .health-bar { + position: absolute; + bottom: -10px; + left: 0; + width: 100%; + height: 5px; + background-color: #333; + border-radius: 2px; + overflow: hidden; + } + + .health-fill { + height: 100%; + background-color: lime; + transition: width 0.2s; + } + + .damage-indicator { + position: absolute; + font-size: 14px; + color: white; + font-weight: bold; + text-shadow: 0px 0px 3px black; + opacity: 1; + transition: opacity 1s, transform 1s; } .explosion { position: absolute; @@ -71,93 +127,6 @@ 0% { opacity: 1; transform: translate(-30px, -30px) scale(0.6); } 100% { opacity: 0; transform: translate(-30px, -30px) scale(1); } } - .gun-model { - position: fixed; - bottom: 0; - right: 50%; - transform: translateX(100px); - width: 300px; - height: 200px; - background-image: url('/api/placeholder/300/200'); - background-size: contain; - background-repeat: no-repeat; - background-position: bottom right; - z-index: 100; - pointer-events: none; - transition: transform 0.05s; - filter: drop-shadow(0 0 10px rgba(255, 165, 0, 0.5)); - } - - .target { - position: absolute; - width: 70px; - height: 70px; - border-radius: 50%; - transition: transform 0.1s, opacity 0.2s; - background-size: cover; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.7); - } - - .target::before { - content: ''; - position: absolute; - top: 15px; - left: 15px; - width: 40px; - height: 40px; - border-radius: 50%; - background: rgba(0, 0, 0, 0.3); - z-index: 2; - } - - .target::after { - content: ''; - position: absolute; - top: 25px; - left: 25px; - width: 20px; - height: 20px; - border-radius: 50%; - background: rgba(0, 0, 0, 0.6); - z-index: 3; - } - - .target-type-1 { - background-color: #ff3333; - border: 3px solid #aa0000; - } - - .target-type-2 { - background-color: #3366ff; - border: 3px solid #0033aa; - } - - .health-bar { - position: absolute; - bottom: -10px; - left: 0; - width: 100%; - height: 5px; - background-color: #333; - border-radius: 2px; - overflow: hidden; - } - - .health-fill { - height: 100%; - background-color: lime; - transition: width 0.2s; - } - - .damage-indicator { - position: absolute; - font-size: 14px; - color: white; - font-weight: bold; - text-shadow: 0px 0px 3px black; - opacity: 1; - transition: opacity 1s, transform 1s; - } #hud { position: absolute; top: 20px; @@ -316,6 +285,22 @@ left: 17px; top: 17px; } + .gun-model { + position: fixed; + bottom: 0; + right: 50%; + transform: translateX(100px); + width: 300px; + height: 200px; + background-image: url('/api/placeholder/300/200'); + background-size: contain; + background-repeat: no-repeat; + background-position: bottom right; + z-index: 100; + pointer-events: none; + transition: transform 0.05s; + filter: drop-shadow(0 0 10px rgba(255, 165, 0, 0.5)); + } #game-over { position: absolute; top: 50%; @@ -401,6 +386,15 @@ const crosshair = document.getElementById('crosshair'); const gunModel = document.getElementById('gun-model'); + // Custom target images + const targetImages = [ + "https://pub-6affea2f837c47c2a8d1d16f2b8fa68e.r2.dev/happyjew.jpg", + "https://pub-6affea2f837c47c2a8d1d16f2b8fa68e.r2.dev/happyjew1.jpg", + "https://pub-6affea2f837c47c2a8d1d16f2b8fa68e.r2.dev/happyjew2.jpg", + "https://pub-6affea2f837c47c2a8d1d16f2b8fa68e.r2.dev/happyjew3.jpg", + "https://pub-6affea2f837c47c2a8d1d16f2b8fa68e.r2.dev/happyjew4.jpg" + ]; + let score = 0; let timeRemaining = 60; let gameActive = false; @@ -458,6 +452,10 @@ reloading = false; targets.forEach(target => target.element.remove()); targets = []; + currentStreak = 0; + document.getElementById('current-streak').textContent = currentStreak; + headshotCount = 0; + document.getElementById('headshots').textContent = headshotCount; // Update UI scoreElement.textContent = score; @@ -503,15 +501,31 @@ const targetType = Math.random() < 0.7 ? 1 : 2; // 70% chance of type 1 (red - target to shoot) + // Choose a random image from our collection + const randomImageIndex = Math.floor(Math.random() * targetImages.length); + const targetImage = targetImages[randomImageIndex]; + const targetElement = document.createElement('div'); targetElement.className = `target target-type-${targetType}`; + // Set the background image + targetElement.style.backgroundImage = `url(${targetImage})`; + // Random position const x = Math.random() * (gameContainer.offsetWidth - 50); const y = Math.random() * (gameContainer.offsetHeight - 50); targetElement.style.left = x + 'px'; targetElement.style.top = y + 'px'; + // Add health bar + const healthBar = document.createElement('div'); + healthBar.className = 'health-bar'; + const healthFill = document.createElement('div'); + healthFill.className = 'health-fill'; + healthFill.style.width = '100%'; + healthBar.appendChild(healthFill); + targetElement.appendChild(healthBar); + gameContainer.appendChild(targetElement); // Target object @@ -521,7 +535,10 @@ x: x, y: y, speed: 1 + Math.random() * 2, - direction: Math.random() * Math.PI * 2 + direction: Math.random() * Math.PI * 2, + health: 100, + healthBar: healthFill, + image: targetImage }; targets.push(target); @@ -575,20 +592,20 @@ // Update ammo display function updateAmmoDisplay() { - // Add ammo display to HUD if it doesn't exist - if (!document.getElementById('ammo-display')) { - const ammoDiv = document.createElement('div'); - ammoDiv.id = 'ammo-display'; - document.getElementById('hud').appendChild(ammoDiv); - } + const ammoCounter = document.getElementById('ammo-counter'); + ammoCounter.innerHTML = ''; - // Update the ammo count - document.getElementById('ammo-display').textContent = `Ammo: ${ammo} / 6`; + // Create bullet icons + for (let i = 0; i < maxAmmo; i++) { + const bulletIcon = document.createElement('div'); + bulletIcon.className = i < ammo ? 'bullet-icon' : 'bullet-icon empty-bullet'; + ammoCounter.appendChild(bulletIcon); + } } // Reload function function reload() { - if (reloading || ammo === 6) return; + if (reloading || ammo === maxAmmo) return; reloading = true; reloadSound.currentTime = 0; @@ -610,7 +627,7 @@ gunModel.style.transform = 'translateX(100px) translateY(30px) rotate(-10deg)'; setTimeout(() => { - ammo = 6; + ammo = maxAmmo; updateAmmoDisplay(); reloading = false; reloadText.remove(); @@ -643,6 +660,32 @@ recoilAnimation = requestAnimationFrame(animateRecoil); } + // Show hit marker + function showHitMarker() { + const hitmarker = document.getElementById('hitmarker'); + hitmarker.style.opacity = '1'; + + setTimeout(() => { + hitmarker.style.opacity = '0'; + }, 100); + } + + // Show score popup + function showScorePopup(x, y, amount, isHeadshot) { + const popup = document.getElementById('score-popup'); + popup.textContent = `${isHeadshot ? 'HEADSHOT! +' : '+'}${amount}`; + popup.style.left = x + 'px'; + popup.style.top = (y - 30) + 'px'; + popup.style.color = isHeadshot ? '#ff0000' : '#ffffff'; + popup.style.opacity = '1'; + popup.style.transform = 'translateY(0)'; + + setTimeout(() => { + popup.style.opacity = '0'; + popup.style.transform = 'translateY(-20px)'; + }, 1000); + } + // Handle shots gameContainer.addEventListener('click', (e) => { if (!gameActive) return; @@ -686,46 +729,107 @@ flash.remove(); }, 150); + // Add bullet hole to background + const bulletHole = document.createElement('div'); + bulletHole.className = 'bullet-hole'; + bulletHole.style.left = (clickX - 4) + 'px'; + bulletHole.style.top = (clickY - 4) + 'px'; + bulletHole.style.zIndex = '1'; // Below targets + gameContainer.appendChild(bulletHole); + + // Remove bullet hole after some time + setTimeout(() => { + bulletHole.remove(); + }, 10000); + + let targetHit = false; + // Check for hit targets for (let i = targets.length - 1; i >= 0; i--) { const target = targets[i]; - const targetCenterX = target.x + 25; - const targetCenterY = target.y + 25; + const targetCenterX = target.x + 35; + const targetCenterY = target.y + 35; - // Check if shot hit the target (simple circle collision) + // Check if shot hit the target (circle collision) const distance = Math.sqrt( Math.pow(clickX - targetCenterX, 2) + Math.pow(clickY - targetCenterY, 2) ); - if (distance < 25) { - // Hit logic - if (target.type === 1) { // Red target (correct) - score += 10; - } else { // Blue target (wrong) - score = Math.max(0, score - 5); - } + // Determine if it's a headshot (hit near center) + const isHeadshot = distance < 15; + + if (distance < 35) { + targetHit = true; + showHitMarker(); - // Add bullet hole - const bulletHole = document.createElement('div'); - bulletHole.className = 'bullet-hole'; - bulletHole.style.left = (clickX - 4) + 'px'; - bulletHole.style.top = (clickY - 4) + 'px'; - gameContainer.appendChild(bulletHole); + // Calculate damage based on distance from center + let damage = isHeadshot ? 100 : 25 + (35 - distance); - // Remove bullet hole after some time + // Apply damage + target.health -= damage; + + // Update health bar + target.healthBar.style.width = Math.max(0, target.health) + '%'; + + // Show damage indicator + const damageText = document.createElement('div'); + damageText.className = 'damage-indicator'; + damageText.textContent = Math.round(damage); + damageText.style.left = (target.x + 35) + 'px'; + damageText.style.top = (target.y - 10) + 'px'; + gameContainer.appendChild(damageText); + + // Animate damage text setTimeout(() => { - bulletHole.remove(); - }, 10000); + damageText.style.opacity = '0'; + damageText.style.transform = 'translateY(-20px)'; + setTimeout(() => { + damageText.remove(); + }, 1000); + }, 10); - scoreElement.textContent = score; - - // Visual feedback - createExplosion(target.x, target.y); - - // Remove the target - target.element.remove(); - targets.splice(i, 1); + // Target is destroyed + if (target.health <= 0) { + // Calculate score based on target type + let pointsGained = 0; + if (target.type === 1) { // Red target (correct) + pointsGained = isHeadshot ? 30 : 10; + currentStreak++; + + // Update streak counter + document.getElementById('current-streak').textContent = currentStreak; + + // Update headshot counter if applicable + if (isHeadshot) { + headshotCount++; + document.getElementById('headshots').textContent = headshotCount; + } + + // Bonus for streak + if (currentStreak > 3) { + pointsGained += 5 * Math.floor(currentStreak / 3); + } + } else { // Blue target (wrong) + pointsGained = -5; + currentStreak = 0; + document.getElementById('current-streak').textContent = currentStreak; + } + + // Add points + score += pointsGained; + scoreElement.textContent = score; + + // Show score popup + showScorePopup(target.x + 35, target.y, pointsGained, isHeadshot); + + // Visual feedback + createExplosion(target.x, target.y); + + // Remove the target + target.element.remove(); + targets.splice(i, 1); + } // Only hit one target per shot break; @@ -733,6 +837,16 @@ } }); + // Handle keyboard controls + document.addEventListener('keydown', (e) => { + if (!gameActive) return; + + // 'R' key for reload + if (e.key === 'r' || e.key === 'R') { + reload(); + } + }); + // Start and restart buttons startGameButton.addEventListener('click', () => { startGame(); @@ -743,6 +857,18 @@ startGame(); requestAnimationFrame(updateTargets); }); + + // Preload images + function preloadImages() { + targetImages.forEach(src => { + const img = new Image(); + img.src = src; + }); + } + + // Initialize game + preloadImages(); + updateAmmoDisplay(); });