<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Dart Arrow</title>
<style>
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
:root {
--bg: #eef0f3;
--panel: rgba(255, 255, 255, 0.88);
--ink: #111318;
--muted: #69707d;
--line: rgba(17, 19, 24, 0.12);
--accent: #0f8f72;
--danger: #d94444;
--gold: #d99b19;
--shadow: 0 20px 55px rgba(24, 29, 38, 0.18);
}
body.dark {
--bg: #171a21;
--panel: rgba(31, 35, 44, 0.92);
--ink: #f5f6f8;
--muted: #aeb5c2;
--line: rgba(245, 246, 248, 0.14);
--accent: #26c6a2;
--danger: #ff6b6b;
--gold: #ffc247;
--shadow: 0 20px 55px rgba(0, 0, 0, 0.35);
}
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: Arial, Helvetica, sans-serif;
background: radial-gradient(circle at 50% 10%, #ffffff 0, var(--bg) 58%);
color: var(--ink);
touch-action: none;
}
body.dark {
background: radial-gradient(circle at 50% 10%, #2b303b 0, var(--bg) 58%);
}
#gameContainer {
position: relative;
width: 100%;
max-width: 460px;
height: 100%;
max-height: 820px;
margin: 0 auto;
overflow: hidden;
background: var(--bg);
box-shadow: var(--shadow);
}
canvas {
display: block;
width: 100%;
height: 100%;
}
#topBar {
position: absolute;
top: 14px;
left: 14px;
right: 14px;
z-index: 4;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
pointer-events: none;
}
.stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
}
.pill {
min-width: 62px;
padding: 7px 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
color: var(--ink);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
backdrop-filter: blur(10px);
font-size: 11px;
font-weight: 700;
line-height: 1.15;
letter-spacing: 0;
}
.pill span {
display: block;
margin-top: 3px;
font-size: 18px;
}
.iconRow {
display: flex;
gap: 8px;
pointer-events: auto;
}
.iconBtn {
width: 40px;
height: 40px;
display: grid;
place-items: center;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
color: var(--ink);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
cursor: pointer;
font-size: 18px;
}
.overlay {
position: absolute;
inset: 0;
z-index: 8;
display: none;
align-items: center;
justify-content: center;
padding: 22px;
background: rgba(238, 240, 243, 0.72);
backdrop-filter: blur(12px);
}
body.dark .overlay {
background: rgba(23, 26, 33, 0.72);
}
.overlay.show {
display: flex;
}
.panel {
width: min(100%, 390px);
padding: 22px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
box-shadow: var(--shadow);
}
.title {
margin: 0 0 6px;
font-size: 40px;
line-height: 1;
letter-spacing: 0;
}
.subtitle {
margin: 0 0 18px;
color: var(--muted);
font-size: 14px;
line-height: 1.35;
}
.menuGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 12px 0;
}
.wide {
grid-column: 1 / -1;
}
.btn {
width: 100%;
min-height: 48px;
padding: 12px 14px;
border: 1px solid transparent;
border-radius: 8px;
background: var(--ink);
color: var(--bg);
cursor: pointer;
font-size: 15px;
font-weight: 700;
}
.btn.secondary {
border-color: var(--line);
background: transparent;
color: var(--ink);
}
.btn.accent {
background: var(--accent);
color: #ffffff;
}
.btn:active,
.iconBtn:active {
transform: scale(0.97);
}
.setting {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 0;
border-top: 1px solid var(--line);
font-size: 14px;
font-weight: 700;
}
.setting small {
display: block;
margin-top: 3px;
color: var(--muted);
font-size: 12px;
font-weight: 400;
}
.switch {
position: relative;
width: 48px;
height: 28px;
flex: 0 0 auto;
}
.switch input {
display: none;
}
.slider {
position: absolute;
inset: 0;
border-radius: 999px;
background: #adb3bd;
cursor: pointer;
}
.slider::after {
content: "";
position: absolute;
width: 22px;
height: 22px;
top: 3px;
left: 3px;
border-radius: 50%;
background: #ffffff;
transition: transform 0.18s;
}
.switch input:checked + .slider {
background: var(--accent);
}
.switch input:checked + .slider::after {
transform: translateX(20px);
}
#messageTitle {
margin: 0 0 8px;
font-size: 36px;
line-height: 1.05;
}
#messageDetail {
margin: 0 0 18px;
color: var(--muted);
line-height: 1.35;
}
#countdown {
position: absolute;
inset: 0;
z-index: 7;
display: none;
align-items: center;
justify-content: center;
font-size: 96px;
font-weight: 900;
color: var(--ink);
text-shadow: 0 8px 30px rgba(0, 0, 0, 0.16);
pointer-events: none;
}
#countdown.show {
display: flex;
animation: pop 0.35s ease;
}
@keyframes pop {
from { transform: scale(0.7); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@media (max-width: 360px) {
.title { font-size: 34px; }
.pill { min-width: 56px; padding: 6px 8px; }
.pill span { font-size: 16px; }
.iconBtn { width: 36px; height: 36px; }
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="topBar">
<div class="stats">
<div class="pill">SEVİYE<span id="levelDisplay">1</span></div>
<div class="pill">REKOR<span id="bestDisplay">1</span></div>
<div class="pill">MOD<span id="modeDisplay">Klasik</span></div>
</div>
<div class="iconRow">
<button class="iconBtn" id="pauseBtn" title="Duraklat">⏸</button>
<button class="iconBtn" id="restartBtn" title="Yeniden başlat">↻</button>
</div>
</div>
<div id="countdown">3</div>
<div class="overlay show" id="menuOverlay">
<div class="panel">
<h1 class="title">Dart Arrow</h1>
<p class="subtitle">Doğru anda ateş et, çarpışmadan tüm iğneleri hedefe yerleştir.</p>
<div class="menuGrid">
<button class="btn accent wide" id="continueBtn">Devam Et</button>
<button class="btn" data-mode="classic">Klasik</button>
<button class="btn secondary" data-mode="endless">Sonsuz</button>
<button class="btn secondary" data-mode="timed">Zamanlı</button>
<button class="btn secondary" data-mode="hard">Zor</button>
</div>
<div class="setting">
<div>Ses<small>Atış, başarı ve çarpışma sesleri</small></div>
<label class="switch">
<input type="checkbox" id="soundToggle" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting">
<div>Titreşim<small>Destekleyen telefonlarda dokunsal geri bildirim</small></div>
<label class="switch">
<input type="checkbox" id="vibrationToggle" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting">
<div>Karanlık Tema<small>Düşük ışıkta daha rahat oyun</small></div>
<label class="switch">
<input type="checkbox" id="themeToggle">
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="overlay" id="messageOverlay">
<div class="panel">
<h2 id="messageTitle">Yandın!</h2>
<p id="messageDetail">Aynı noktaya denk geldi.</p>
<div class="menuGrid">
<button class="btn accent wide" id="primaryActionBtn">Tekrar Dene</button>
<button class="btn secondary" id="menuBtn">Menü</button>
<button class="btn secondary" id="nextActionBtn">Sonraki Seviye</button>
</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const levelDisplay = document.getElementById('levelDisplay');
const bestDisplay = document.getElementById('bestDisplay');
const modeDisplay = document.getElementById('modeDisplay');
const menuOverlay = document.getElementById('menuOverlay');
const messageOverlay = document.getElementById('messageOverlay');
const messageTitle = document.getElementById('messageTitle');
const messageDetail = document.getElementById('messageDetail');
const primaryActionBtn = document.getElementById('primaryActionBtn');
const nextActionBtn = document.getElementById('nextActionBtn');
const menuBtn = document.getElementById('menuBtn');
const pauseBtn = document.getElementById('pauseBtn');
const restartBtn = document.getElementById('restartBtn');
const continueBtn = document.getElementById('continueBtn');
const countdownEl = document.getElementById('countdown');
const soundToggle = document.getElementById('soundToggle');
const vibrationToggle = document.getElementById('vibrationToggle');
const themeToggle = document.getElementById('themeToggle');
const TAU = Math.PI * 2;
const maxLevel = 1200;
const storageKey = 'dartArrowSaveV2';
const modes = {
classic: 'Klasik',
endless: 'Sonsuz',
timed: 'Zamanlı',
hard: 'Zor'
};
let state = loadState();
let gameState = 'MENU';
let targetAngle = 0;
let targetSpeed = 0.02;
let targetRadius = 50;
let pinRadius = 12;
let pinLineLength = 138;
let bulletSpeed = 25;
let startingBulletsCount = 0;
let timer = 0;
let levelStartedAt = 0;
let pausedRemainingTime = 0;
let lastTime = performance.now();
let pins = [];
let bullets = [];
let activeBullet = null;
let particles = [];
let flash = null;
let audioCtx = null;
let countdownToken = 0;
function loadState() {
const fallback = {
level: 1,
best: 1,
mode: 'classic',
sound: true,
vibration: true,
dark: false
};
try {
return { ...fallback, ...JSON.parse(localStorage.getItem(storageKey) || '{}') };
} catch (error) {
return fallback;
}
}
function saveState() {
localStorage.setItem(storageKey, JSON.stringify(state));
}
function resizeCanvas() {
const dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
const rect = canvas.parentElement.getBoundingClientRect();
canvas.width = Math.floor(rect.width * dpr);
canvas.height = Math.floor(rect.height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
canvas.logicalWidth = rect.width;
canvas.logicalHeight = rect.height;
pinLineLength = Math.max(105, Math.min(145, rect.height * 0.18));
targetRadius = Math.max(42, Math.min(54, rect.width * 0.115));
}
function updateHud() {
levelDisplay.textContent = state.level;
bestDisplay.textContent = state.best;
modeDisplay.textContent = modes[state.mode];
soundToggle.checked = state.sound;
vibrationToggle.checked = state.vibration;
themeToggle.checked = state.dark;
document.body.classList.toggle('dark', state.dark);
continueBtn.textContent = state.best > 1 ? 'Devam Et - Seviye ' + state.level : 'Başla';
saveState();
}
function getDifficulty() {
const level = state.mode === 'endless' ? Math.min(state.level, 2000) : state.level;
const curve = Math.log2(level + 1);
const hardBoost = state.mode === 'hard' ? 1.35 : 1;
const timedBoost = state.mode === 'timed' ? 1.12 : 1;
const speedBase = 0.010 + curve * 0.0042;
const speedWave = Math.sin(level * 0.73) * 0.006;
const speed = Math.min(0.09, (speedBase + speedWave) * hardBoost * timedBoost);
const initialPins = Math.min(state.mode === 'hard' ? 13 : 11, 2 + Math.floor(level / 5) + Math.floor(curve / 2));
const bulletsCount = Math.min(state.mode === 'hard' ? 30 : 26, 5 + Math.floor(level / 3) + (level % 4 === 0 ? 2 : 0));
const direction = level % 3 === 0 ? -1 : 1;
const wobble = level >= 8 || state.mode !== 'classic';
const stopGo = level >= 14 || state.mode === 'hard';
const timeLimit = state.mode === 'timed' ? Math.max(7, 16 - Math.floor(level / 12)) : 0;
return {
speed: speed * direction,
initialPins,
bulletsCount,
wobble,
stopGo,
timeLimit
};
}
function initLevel(skipCountdown = false) {
resizeCanvas();
const difficulty = getDifficulty();
targetAngle = 0;
targetSpeed = difficulty.speed;
startingBulletsCount = difficulty.bulletsCount;
timer = difficulty.timeLimit;
pausedRemainingTime = timer;
levelStartedAt = performance.now();
pins = [];
bullets = [];
activeBullet = null;
particles = [];
flash = null;
const jitter = state.mode === 'classic' && state.level < 5 ? 0 : 0.18;
for (let i = 0; i < difficulty.initialPins; i++) {
const offset = Math.sin((state.level + i) * 1.77) * jitter;
pins.push({
angle: normalize((TAU / difficulty.initialPins) * i + offset),
num: ''
});
}
for (let i = startingBulletsCount; i > 0; i--) {
bullets.push(i);
}
gameState = skipCountdown ? 'PLAYING' : 'COUNTDOWN';
menuOverlay.classList.remove('show');
messageOverlay.classList.remove('show');
updateHud();
if (skipCountdown) {
return;
}
runCountdown();
}
function runCountdown() {
const token = ++countdownToken;
let count = 3;
countdownEl.textContent = count;
countdownEl.classList.add('show');
tone(520, 0.06, 'sine', 0.04);
const tick = () => {
if (token !== countdownToken || gameState !== 'COUNTDOWN') return;
count--;
if (count > 0) {
countdownEl.textContent = count;
countdownEl.classList.remove('show');
void countdownEl.offsetWidth;
countdownEl.classList.add('show');
tone(520 + count * 50, 0.06, 'sine', 0.04);
setTimeout(tick, 650);
} else {
countdownEl.textContent = 'GO';
countdownEl.classList.remove('show');
void countdownEl.offsetWidth;
countdownEl.classList.add('show');
tone(760, 0.1, 'triangle', 0.05);
setTimeout(() => {
if (token === countdownToken) {
countdownEl.classList.remove('show');
gameState = 'PLAYING';
levelStartedAt = performance.now();
}
}, 450);
}
};
setTimeout(tick, 650);
}
function normalize(angle) {
return (angle % TAU + TAU) % TAU;
}
function draw() {
const width = canvas.logicalWidth || canvas.width;
const height = canvas.logicalHeight || canvas.height;
const cx = width / 2;
const cy = height * 0.36;
const remaining = bullets.length + (activeBullet ? 1 : 0);
ctx.clearRect(0, 0, width, height);
drawBackground(width, height);
drawTarget(cx, cy, remaining);
drawActiveBullet(cx);
drawQueue(cx, height);
drawParticles();
drawPrompts(cx, height);
if (flash) {
ctx.fillStyle = flash.color;
ctx.globalAlpha = Math.max(0, flash.life);
ctx.fillRect(0, 0, width, height);
ctx.globalAlpha = 1;
}
}
function drawBackground(width, height) {
const gradient = ctx.createLinearGradient(0, 0, 0, height);
if (document.body.classList.contains('dark')) {
gradient.addColorStop(0, '#252a33');
gradient.addColorStop(1, '#171a21');
} else {
gradient.addColorStop(0, '#fbfbfc');
gradient.addColorStop(1, '#e8ebef');
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
ctx.strokeStyle = document.body.classList.contains('dark') ? 'rgba(255,255,255,0.035)' : 'rgba(0,0,0,0.035)';
ctx.lineWidth = 1;
for (let y = 95; y < height; y += 44) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
}
function drawTarget(cx, cy, remaining) {
const ink = getInk();
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(targetAngle);
pins.forEach(pin => {
const px = Math.cos(pin.angle) * pinLineLength;
const py = Math.sin(pin.angle) * pinLineLength;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(px, py);
ctx.strokeStyle = ink;
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.arc(px, py, pinRadius, 0, TAU);
ctx.fillStyle = ink;
ctx.fill();
if (pin.num !== '') {
ctx.save();
ctx.translate(px, py);
ctx.rotate(-targetAngle);
drawSmallNumber(pin.num, 0, 1);
ctx.restore();
}
});
ctx.beginPath();
ctx.arc(0, 0, targetRadius + 4, 0, TAU);
ctx.fillStyle = state.mode === 'hard' ? '#d94444' : state.mode === 'timed' ? '#d99b19' : ink;
ctx.fill();
ctx.beginPath();
ctx.arc(0, 0, targetRadius - 7, 0, TAU);
ctx.fillStyle = ink;
ctx.fill();
ctx.rotate(-targetAngle);
ctx.fillStyle = '#ffffff';
ctx.font = '900 ' + (remaining >= 100 ? 32 : 44) + 'px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(remaining, 0, 2);
ctx.restore();
}
function drawActiveBullet(cx) {
if (!activeBullet) return;
const ink = getInk();
ctx.beginPath();
ctx.moveTo(cx, activeBullet.y);
ctx.lineTo(cx, activeBullet.y + 22);
ctx.strokeStyle = ink;
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.arc(cx, activeBullet.y, pinRadius, 0, TAU);
ctx.fillStyle = ink;
ctx.fill();
drawSmallNumber(activeBullet.num, cx, activeBullet.y + 1);
}
function drawQueue(cx, height) {
const ink = getInk();
const bulletStartY = height * 0.74;
const spacing = pinRadius * 2.35;
const showCount = Math.min(7, bullets.length);
for (let i = 0; i < showCount; i++) {
const by = bulletStartY + (i * spacing);
ctx.beginPath();
ctx.arc(cx, by, pinRadius, 0, TAU);
ctx.fillStyle = ink;
ctx.fill();
drawSmallNumber(bullets[bullets.length - 1 - i], cx, by + 1);
}
if (bullets.length > showCount) {
ctx.fillStyle = getMuted();
ctx.font = 'bold 13px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('+' + (bullets.length - showCount), cx, bulletStartY + showCount * spacing + 4);
}
}
function drawSmallNumber(text, x, y) {
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x, y);
}
function drawPrompts(cx, height) {
if (gameState === 'PLAYING' && bullets.length === startingBulletsCount && !activeBullet) {
ctx.fillStyle = getMuted();
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'center';
ctx.fillText('Ateş etmek için dokun', cx, height * 0.62);
}
if (gameState === 'PAUSED') {
ctx.fillStyle = 'rgba(0,0,0,0.35)';
ctx.fillRect(0, 0, canvas.logicalWidth, canvas.logicalHeight);
ctx.fillStyle = '#ffffff';
ctx.font = '900 42px Arial';
ctx.textAlign = 'center';
ctx.fillText('DURAKLATILDI', canvas.logicalWidth / 2, canvas.logicalHeight / 2);
}
if (state.mode === 'timed' && gameState === 'PLAYING') {
const timeLeft = Math.max(0, timer - (performance.now() - levelStartedAt) / 1000);
ctx.fillStyle = timeLeft <= 3 ? '#d94444' : getInk();
ctx.font = '900 18px Arial';
ctx.textAlign = 'right';
ctx.fillText(timeLeft.toFixed(1), canvas.logicalWidth - 18, 96);
}
}
function drawParticles() {
particles.forEach(p => {
ctx.globalAlpha = Math.max(0, p.life);
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, TAU);
ctx.fillStyle = p.color;
ctx.fill();
});
ctx.globalAlpha = 1;
}
function update(now) {
const dt = Math.min(32, now - lastTime);
lastTime = now;
updateParticles(dt);
if (flash) {
flash.life -= dt / 220;
if (flash.life <= 0) flash = null;
}
if (gameState !== 'PLAYING') return;
const difficulty = getDifficulty();
const elapsed = (now - levelStartedAt) / 1000;
let speedMultiplier = 1;
if (difficulty.wobble) speedMultiplier += Math.sin(elapsed * 2.4) * 0.28;
if (difficulty.stopGo && Math.sin(elapsed * 1.35) > 0.72) speedMultiplier = 0.18;
targetAngle += targetSpeed * speedMultiplier * (dt / 16.67);
if (state.mode === 'timed' && elapsed >= timer) {
fail('Süre Bitti!', 'Bu seviyede daha hızlı ateş etmelisin.');
return;
}
if (!activeBullet) return;
activeBullet.y -= bulletSpeed * (dt / 16.67);
const cy = canvas.logicalHeight * 0.36;
if (activeBullet.y <= cy + pinLineLength) {
let hitAngle = normalize((Math.PI / 2) - targetAngle);
let collision = false;
for (let i = 0; i < pins.length; i++) {
let pinAngle = normalize(pins[i].angle);
let diff = Math.abs(hitAngle - pinAngle);
if (diff > Math.PI) diff = TAU - diff;
const distance = 2 * pinLineLength * Math.sin(diff / 2);
if (distance < pinRadius * 2 + 2) {
collision = true;
break;
}
}
if (collision) {
fail('Yandın!', 'İğne başka bir iğneye çarptı.');
} else {
pins.push({ angle: hitAngle, num: activeBullet.num });
burst(canvas.logicalWidth / 2, cy + pinLineLength, getAccent(), 8);
activeBullet = null;
tone(420, 0.045, 'triangle', 0.035);
if (bullets.length === 0) {
success();
}
}
}
}
function updateParticles(dt) {
particles = particles.filter(p => {
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 0.0009 * dt;
p.life -= dt / p.decay;
return p.life > 0;
});
}
function shoot(event) {
if (event) event.preventDefault();
if (gameState === 'COUNTDOWN') return;
if (gameState === 'PAUSED') {
resumeGame();
return;
}
if (gameState !== 'PLAYING' || activeBullet || bullets.length === 0) return;
const num = bullets.pop();
activeBullet = {
y: canvas.logicalHeight * 0.74,
num
};
tone(260, 0.055, 'square', 0.025);
vibrate(12);
}
function fail(title, detail) {
gameState = 'GAMEOVER';
activeBullet = null;
flash = { color: 'rgba(217, 68, 68, 0.42)', life: 1 };
burst(canvas.logicalWidth / 2, canvas.logicalHeight * 0.36, '#d94444', 24);
tone(120, 0.22, 'sawtooth', 0.05);
vibrate([50, 30, 80]);
showMessage(title, detail, false);
}
function success() {
gameState = 'SUCCESS';
flash = { color: 'rgba(15, 143, 114, 0.32)', life: 1 };
burst(canvas.logicalWidth / 2, canvas.logicalHeight * 0.36, getAccent(), 34);
tone(620, 0.08, 'triangle', 0.04);
setTimeout(() => tone(820, 0.11, 'triangle', 0.035), 85);
vibrate(35);
if (state.mode === 'classic' || state.mode === 'timed' || state.mode === 'hard') {
state.level = Math.min(maxLevel, state.level + 1);
} else {
state.level++;
}
state.best = Math.max(state.best, state.level);
updateHud();
showMessage('Harika!', 'Seviye tamamlandı. Yeni seviye hazır.', true);
}
function showMessage(title, detail, won) {
messageTitle.textContent = title;
messageDetail.textContent = detail;
primaryActionBtn.textContent = won ? 'Devam Et' : 'Tekrar Dene';
nextActionBtn.style.display = won ? 'block' : 'none';
setTimeout(() => messageOverlay.classList.add('show'), 260);
}
function burst(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = (TAU / count) * i + Math.random() * 0.3;
const speed = 0.08 + Math.random() * 0.18;
particles.push({
x,
y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: 2 + Math.random() * 4,
life: 1,
decay: 380 + Math.random() * 260,
color
});
}
}
function tone(frequency, duration, type, volume) {
if (!state.sound) return;
try {
audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const gain = audioCtx.createGain();
oscillator.type = type;
oscillator.frequency.value = frequency;
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration);
oscillator.connect(gain);
gain.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + duration);
} catch (error) {
state.sound = false;
}
}
function vibrate(pattern) {
if (state.vibration && navigator.vibrate) {
navigator.vibrate(pattern);
}
}
function pauseGame() {
if (gameState === 'PLAYING') {
pausedRemainingTime = getRemainingTime();
gameState = 'PAUSED';
pauseBtn.textContent = '▶';
}
}
function resumeGame() {
if (gameState === 'PAUSED') {
gameState = 'PLAYING';
levelStartedAt = performance.now() - Math.max(0, timer - pausedRemainingTime) * 1000;
pauseBtn.textContent = '⏸';
}
}
function getRemainingTime() {
if (state.mode !== 'timed') return 0;
return Math.max(0, timer - (performance.now() - levelStartedAt) / 1000);
}
function openMenu() {
gameState = 'MENU';
countdownToken++;
countdownEl.classList.remove('show');
messageOverlay.classList.remove('show');
menuOverlay.classList.add('show');
pauseBtn.textContent = '⏸';
}
function getInk() {
return document.body.classList.contains('dark') ? '#f5f6f8' : '#111318';
}
function getMuted() {
return document.body.classList.contains('dark') ? 'rgba(245,246,248,0.62)' : 'rgba(17,19,24,0.52)';
}
function getAccent() {
return document.body.classList.contains('dark') ? '#26c6a2' : '#0f8f72';
}
function loop(now) {
update(now);
draw();
requestAnimationFrame(loop);
}
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('mousedown', shoot);
canvas.addEventListener('touchstart', shoot, { passive: false });
document.querySelectorAll('[data-mode]').forEach(button => {
button.addEventListener('click', () => {
state.mode = button.dataset.mode;
state.level = state.mode === 'endless' ? 1 : Math.min(state.level, maxLevel);
updateHud();
initLevel();
});
});
continueBtn.addEventListener('click', () => initLevel());
primaryActionBtn.addEventListener('click', () => initLevel());
nextActionBtn.addEventListener('click', () => initLevel());
menuBtn.addEventListener('click', openMenu);
restartBtn.addEventListener('click', () => initLevel());
pauseBtn.addEventListener('click', () => {
if (gameState === 'PAUSED') {
resumeGame();
} else {
pauseGame();
}
});
soundToggle.addEventListener('change', () => {
state.sound = soundToggle.checked;
updateHud();
});
vibrationToggle.addEventListener('change', () => {
state.vibration = vibrationToggle.checked;
updateHud();
});
themeToggle.addEventListener('change', () => {
state.dark = themeToggle.checked;
updateHud();
});
document.addEventListener('keydown', event => {
if (event.code === 'Space') shoot(event);
if (event.code === 'Escape') openMenu();
if (event.code === 'KeyP') gameState === 'PAUSED' ? resumeGame() : pauseGame();
});
resizeCanvas();
updateHud();
initLevel(true);
openMenu();
requestAnimationFrame(loop);
</script>
</body>
</html>
obisa adlı üyeden alıntı:
mesajı görüntüle
