Mobile-emulator / knifehit.html
ngiactcp
Add several new playable games to the application
61bdef8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Knife Hit Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;700;900&display=swap" rel="stylesheet">
<style>
:root {
--primary: #ff4757;
--bg-dark: #0f172a;
--surface: rgba(255, 255, 255, 0.05);
--border: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
overflow: hidden;
background-color: var(--bg-dark);
font-family: 'Outfit', sans-serif;
touch-action: none;
user-select: none;
color: white;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 50% 30%, #1e293b 0%, #0f172a 100%);
}
canvas { display: block; }
.glass {
background: var(--surface);
backdrop-filter: blur(12px);
border: 1px solid var(--border);
border-radius: 24px;
}
.screen {
position: absolute;
inset: 0;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(15, 23, 42, 0.95);
}
.btn-main {
background: var(--primary);
color: white;
padding: 16px 48px;
border-radius: 16px;
font-weight: 900;
font-size: 1.25rem;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 8px 0 #b33939;
transition: all 0.1s;
margin: 10px;
width: 240px;
border: none;
cursor: pointer;
}
.btn-main:active { transform: translateY(4px); box-shadow: 0 4px 0 #b33939; }
.diff-tag {
padding: 8px 20px;
border-radius: 12px;
font-weight: 700;
cursor: pointer;
border: 2px solid transparent;
background: rgba(255,255,255,0.05);
}
.diff-tag.active { border-color: var(--primary); background: rgba(255, 71, 87, 0.2); }
.hidden { display: none !important; }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
.logo-anim { animation: pulse 2s infinite ease-in-out; }
#pause-btn {
position: absolute;
top: 24px;
right: 24px;
z-index: 60;
cursor: pointer;
padding: 12px;
}
</style>
</head>
<body>
<div id="game-container">
<div id="hud" class="absolute top-0 left-0 w-full p-6 flex justify-between items-start z-50 hidden">
<div class="flex gap-4">
<div class="glass p-3 px-5">
<p class="text-[10px] uppercase font-bold opacity-50">Score</p>
<p id="score-val" class="text-3xl font-black">0</p>
</div>
<div class="glass p-3 px-5 flex items-center gap-2">
<span class="text-xl">🍎</span>
<p id="fruit-val" class="text-2xl font-black text-red-400">0</p>
</div>
</div>
<div id="pause-btn" class="glass">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
</div>
<div class="glass p-3 px-5 text-right mr-16">
<p class="text-[10px] uppercase font-bold opacity-50">Level</p>
<p id="level-val" class="text-xl font-black">1</p>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<!-- Main Menu -->
<div id="home-screen" class="screen">
<div class="logo-anim mb-4">
<svg width="80" height="80" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#451a03" stroke="#78350f" stroke-width="4"/><path d="M50 20 L50 80 M20 50 L80 50" stroke="rgba(255,255,255,0.1)" stroke-width="2"/></svg>
</div>
<h1 class="text-6xl font-black italic mb-2">KNIFE<span class="text-red-500">HIT</span></h1>
<div class="flex gap-2 my-4 glass p-2">
<div class="diff-tag active" data-diff="easy">EASY</div>
<div class="diff-tag" data-diff="medium">MED</div>
<div class="diff-tag" data-diff="hard">HARD</div>
</div>
<button id="play-btn" class="btn-main">START GAME</button>
<button id="resume-saved-btn" class="btn-main hidden" style="opacity: 0.8; font-size: 0.9rem;">RESUME SAVED</button>
</div>
<!-- Pause Menu -->
<div id="pause-screen" class="screen hidden">
<h2 class="text-5xl font-black mb-8">PAUSED</h2>
<button id="resume-btn" class="btn-main">RESUME</button>
<button id="save-exit-btn" class="btn-main" style="background: #475569; box-shadow: 0 8px 0 #1e293b;">SAVE & EXIT</button>
</div>
<!-- Game Over -->
<div id="game-over-screen" class="screen hidden">
<div class="glass p-10 flex flex-col items-center">
<h2 class="text-4xl font-black mb-2 text-red-500">CRASHED!</h2>
<p id="final-score" class="text-xl opacity-60 mb-8">Score: 0</p>
<button id="restart-btn" class="btn-main">RETRY</button>
<button onclick="location.reload()" class="btn-main opacity-50 text-sm">MENU</button>
</div>
</div>
</div>
<script>
const SVG_KNIFE = `data:image/svg+xml;base64,${btoa('<svg xmlns="http://www.w3.org/2000/svg" width="40" height="120" viewBox="0 0 40 120"><path d="M20 5 L32 90 L8 90 Z" fill="#e2e8f0"/><path d="M15 90 H25 V115 Q25 120 20 120 Q15 120 15 115 Z" fill="#ff4757"/><rect x="15" y="95" width="10" height="4" fill="#b33939"/></svg>')}`;
const KNIFE_TIP_OFFSET = 5;
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const knifeImg = new Image(); knifeImg.src = SVG_KNIFE;
const DIFFICULTY = {
easy: { speed: 0.03, count: 7, fruitProb: 0.4 },
medium: { speed: 0.05, count: 10, fruitProb: 0.6 },
hard: { speed: 0.07, count: 13, fruitProb: 0.8 }
};
let state = {
isPlaying: false,
isPaused: false,
score: 0,
fruits: 0,
level: 1,
difficulty: 'easy',
rotation: 0,
rotationSpeed: 0.03,
knivesInLog: [],
fruitsInLog: [],
activeKnife: { x: 0, y: 0, status: 'idle' },
remKnives: 0,
shake: 0,
particles: []
};
function init() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
state.activeKnife.x = canvas.width / 2;
state.activeKnife.y = canvas.height - 160;
// Check for saved data
const saved = localStorage.getItem('knifeHitSave');
if (saved) document.getElementById('resume-saved-btn').classList.remove('hidden');
}
function createSplash(x, y) {
for(let i=0; i<12; i++) {
state.particles.push({
x, y,
vx: (Math.random() - 0.5) * 12,
vy: (Math.random() - 0.5) * 12,
life: 1.0
});
}
}
function draw() {
if (!state.isPlaying || state.isPaused) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const cx = canvas.width / 2;
const cy = canvas.height * 0.35;
const r = 90;
// Particles
state.particles.forEach((p, idx) => {
ctx.globalAlpha = p.life;
ctx.fillStyle = '#ef4444';
ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();
p.x += p.vx; p.y += p.vy; p.life -= 0.04;
if(p.life <= 0) state.particles.splice(idx, 1);
});
ctx.globalAlpha = 1;
// Inventory
for(let i=0; i < state.remKnives; i++) {
ctx.drawImage(knifeImg, 40, (canvas.height - 100) - (i * 15), 15, 45);
}
ctx.save();
ctx.translate(cx + (Math.random()*state.shake), cy + (Math.random()*state.shake));
ctx.rotate(state.rotation);
ctx.beginPath(); ctx.arc(0, 0, r, 0, Math.PI*2);
ctx.fillStyle = '#451a03'; ctx.fill();
ctx.strokeStyle = '#78350f'; ctx.lineWidth = 4; ctx.stroke();
state.fruitsInLog.forEach(f => {
if(!f.active) return;
ctx.save();
ctx.rotate(f.angle);
ctx.font = "32px serif";
ctx.textAlign = "center";
ctx.fillText("🍎", 0, r + 25);
ctx.restore();
});
state.knivesInLog.forEach(k => {
ctx.save();
ctx.rotate(k.angle);
ctx.drawImage(knifeImg, -15, r - 5, 30, 90);
ctx.restore();
});
ctx.restore();
if (state.activeKnife.status !== 'empty') {
ctx.drawImage(knifeImg, cx - 20, state.activeKnife.y, 40, 120);
}
if (state.activeKnife.status === 'flying') {
state.activeKnife.y -= 70;
const knifeTipY = state.activeKnife.y + KNIFE_TIP_OFFSET;
if (knifeTipY <= cy + r) {
handleImpact(cx, cy, r);
}
}
state.rotation += state.rotationSpeed;
if (state.shake > 0) state.shake *= 0.8;
requestAnimationFrame(draw);
}
function handleImpact(cx, cy, r) {
// APPLYING YOUR NAILED IT FIX: 0.1
const impactAngle = (Math.PI / 0.1) - state.rotation;
state.fruitsInLog.forEach(f => {
if(f.active && Math.abs(AngleDiff(f.angle, impactAngle)) < 0.28) {
f.active = false;
state.fruits++;
document.getElementById('fruit-val').textContent = state.fruits;
createSplash(cx, cy + r);
}
});
let crashed = false;
for (let k of state.knivesInLog) {
if (Math.abs(AngleDiff(k.angle, impactAngle)) < 0.28) { crashed = true; break; }
}
if (crashed) {
state.isPlaying = false;
document.getElementById('final-score').textContent = `Score: ${state.score}`;
document.getElementById('game-over-screen').classList.remove('hidden');
localStorage.removeItem('knifeHitSave'); // Delete save on death
} else {
state.knivesInLog.push({ angle: impactAngle });
state.score++;
state.remKnives--;
state.shake = 15;
document.getElementById('score-val').textContent = state.score;
if (state.remKnives <= 0) {
state.activeKnife.status = 'empty';
setTimeout(nextLevel, 400);
} else {
state.activeKnife.y = canvas.height - 160;
state.activeKnife.status = 'idle';
}
}
}
function AngleDiff(a1, a2) {
let diff = (a1 - a2 + Math.PI) % (Math.PI * 2) - Math.PI;
return diff < -Math.PI ? diff + Math.PI * 2 : diff;
}
function nextLevel() {
if (!state.isPlaying) return;
state.level++;
document.getElementById('level-val').textContent = state.level;
state.knivesInLog = [];
state.fruitsInLog = [];
if(Math.random() < DIFFICULTY[state.difficulty].fruitProb) {
state.fruitsInLog.push({ angle: Math.random()*Math.PI*2, active: true });
}
state.remKnives = DIFFICULTY[state.difficulty].count + Math.floor(state.level/2);
state.rotationSpeed = DIFFICULTY[state.difficulty].speed + (state.level * 0.002);
if(state.level > 2 && Math.random() > 0.4) state.rotationSpeed *= -1;
state.activeKnife.y = canvas.height - 160;
state.activeKnife.status = 'idle';
}
function togglePause() {
state.isPaused = !state.isPaused;
if (state.isPaused) {
document.getElementById('pause-screen').classList.remove('hidden');
} else {
document.getElementById('pause-screen').classList.add('hidden');
requestAnimationFrame(draw);
}
}
function saveAndExit() {
const saveData = {
score: state.score,
level: state.level,
fruits: state.fruits,
difficulty: state.difficulty
};
localStorage.setItem('knifeHitSave', JSON.stringify(saveData));
location.reload();
}
function startGame(resumeData = null) {
if (resumeData) {
state.score = resumeData.score;
state.level = resumeData.level;
state.fruits = resumeData.fruits;
state.difficulty = resumeData.difficulty;
} else {
state.score = 0; state.level = 1; state.fruits = 0;
}
state.knivesInLog = [];
state.fruitsInLog = [];
state.remKnives = DIFFICULTY[state.difficulty].count + Math.floor(state.level/2);
state.rotationSpeed = DIFFICULTY[state.difficulty].speed + (state.level * 0.002);
state.isPlaying = true;
state.isPaused = false;
state.activeKnife.status = 'idle';
state.activeKnife.y = canvas.height - 160;
document.getElementById('score-val').textContent = state.score;
document.getElementById('fruit-val').textContent = state.fruits;
document.getElementById('level-val').textContent = state.level;
document.getElementById('home-screen').classList.add('hidden');
document.getElementById('pause-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
document.getElementById('hud').classList.remove('hidden');
requestAnimationFrame(draw);
}
// UI Events
window.addEventListener('pointerdown', (e) => {
if (e.target.closest('button') || e.target.closest('.diff-tag') || e.target.closest('#pause-btn')) return;
if (state.isPlaying && !state.isPaused && state.activeKnife.status === 'idle') {
state.activeKnife.status = 'flying';
}
});
document.getElementById('pause-btn').onclick = togglePause;
document.getElementById('resume-btn').onclick = togglePause;
document.getElementById('save-exit-btn').onclick = saveAndExit;
document.getElementById('play-btn').onclick = () => startGame();
document.getElementById('restart-btn').onclick = () => startGame();
document.getElementById('resume-saved-btn').onclick = () => {
const saved = JSON.parse(localStorage.getItem('knifeHitSave'));
startGame(saved);
};
document.querySelectorAll('.diff-tag').forEach(tag => {
tag.onclick = () => {
document.querySelectorAll('.diff-tag').forEach(t => t.classList.remove('active'));
tag.classList.add('active');
state.difficulty = tag.dataset.diff;
};
});
window.onresize = init;
init();
</script>
</body>
</html>