SES KOPYASI — ARCADE KAOS
by DriftBuilder15708 lines22.4 KB
<!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>SES KOPYASI — ARCADE KAOS</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #0a0a0f; color: #fff; height: 100vh;
overflow: hidden; display: flex; align-items: center; justify-content: center;
user-select: none; transition: background 0.2s;
}
body.danger { animation: dangerPulse 0.5s infinite; }
@keyframes dangerPulse {
0%,100% { background: #0a0a0f; }
50% { background: #2a0010; }
}
#game {
width: 100%; max-width: 480px; height: 100vh; max-height: 900px;
position: relative; display: flex; flex-direction: column;
align-items: center; justify-content: center; padding: 20px;
}
/* MENÜ */
#menu { text-align: center; animation: fadeIn 0.6s ease; }
#menu h1 {
font-size: 2.2rem; font-weight: 900;
background: linear-gradient(90deg, #ff3366, #ffcc00, #00ff88);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin-bottom: 6px; letter-spacing: -1px;
}
#menu p { color: #888; margin-bottom: 18px; font-size: 0.85rem; letter-spacing: 2px; }
.rules {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px; padding: 14px; margin-bottom: 18px;
text-align: left; font-size: 0.78rem; line-height: 1.7; color: #bbb;
}
.rules b { color: #ff3366; }
.rules .key { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.best {
background: rgba(255,255,255,0.05); padding: 10px 20px; border-radius: 12px;
margin-bottom: 18px; display: inline-block; font-size: 0.9rem; color: #aaa;
}
.best span { color: #00ff88; font-weight: 700; }
button {
background: linear-gradient(135deg, #ff3366, #ffcc00);
color: #000; border: none; padding: 18px 60px;
font-size: 1.15rem; font-weight: 800; border-radius: 50px;
cursor: pointer; transition: transform 0.15s; letter-spacing: 1px;
}
button:active { transform: scale(0.95); }
/* OYUN ALANI */
#arena { display: none; text-align: center; width: 100%; }
#roundInfo {
position: absolute; top: 12px; left: 0; right: 0;
display: flex; justify-content: space-between;
padding: 0 20px; font-size: 0.78rem; color: #666; font-weight: 600;
}
#roundInfo b { color: #fff; }
#combo {
position: absolute; top: 42px; left: 0; right: 0;
text-align: center; font-size: 1.2rem; font-weight: 900;
color: #ffcc00; opacity: 0; transition: all 0.2s;
text-shadow: 0 0 20px currentColor;
}
#combo.show { opacity: 1; animation: comboPop 0.4s ease; }
@keyframes comboPop { 0%{transform:scale(0.5)} 50%{transform:scale(1.4)} 100%{transform:scale(1)} }
/* Sinyal alanı */
#signalArea {
position: relative; width: 260px; height: 260px;
margin: 0 auto 25px;
}
.signal {
position: absolute; inset: 0;
border-radius: 50%; display: flex; align-items: center;
justify-content: center; font-weight: 800;
transition: all 0.12s; background: #1a1a24; color: #333;
}
.signal.active {
animation: pulse 0.3s ease;
}
.signal.active.glow {
box-shadow: 0 0 60px currentColor, 0 0 120px currentColor;
}
.signal .emoji { font-size: 3.8rem; display: block; margin-bottom: 4px; }
.signal .label { font-size: 0.95rem; letter-spacing: 1px; }
/* Çift sinyal modu */
.signal.dual {
width: 130px; height: 130px;
}
.signal.dual .emoji { font-size: 2.3rem; }
.signal.dual .label { font-size: 0.65rem; letter-spacing: 0; }
.signal.dual.pos-left { left: 0; top: 65px; right: auto; }
.signal.dual.pos-right { right: 0; left: auto; top: 65px; }
.signal.dual.pos-top { left: 65px; top: 0; bottom: auto; }
.signal.dual.pos-bottom { left: 65px; bottom: 0; top: auto; }
/* Sahte sinyal */
.signal.ghost {
opacity: 0.35;
animation: ghostFlicker 0.15s infinite;
}
@keyframes ghostFlicker {
0%,100% { opacity: 0.35; }
50% { opacity: 0.7; }
}
/* Sinyal değişim uyarısı */
.signal.swapping { animation: swapFlash 0.15s 3; }
@keyframes swapFlash {
0%,100% { transform: scale(1); }
50% { transform: scale(1.05); filter: brightness(1.5); }
}
/* Sinyal zaman çubuğu (ring) */
#ringTimer {
position: absolute; inset: -8px;
border-radius: 50%;
background: conic-gradient(#00ff88 var(--p, 100%), transparent 0);
z-index: -1;
transition: --p 0.05s linear;
}
#hint {
font-size: 0.95rem; color: #aaa; margin-bottom: 15px;
min-height: 24px; font-weight: 700;
}
#hint.big { font-size: 1.3rem; animation: comboPop 0.3s; }
#meter {
width: 100%; height: 8px; background: #1a1a24; border-radius: 4px;
overflow: hidden; margin-bottom: 12px;
}
#meterFill {
height: 100%; width: 0%;
background: linear-gradient(90deg, #00ff88, #ffaa00, #ff3366);
transition: width 0.05s linear; border-radius: 4px;
}
#countdown {
position: absolute; inset: 0;
background: rgba(0,0,0,0.9);
display: none; align-items: center; justify-content: center;
font-size: 8rem; font-weight: 900; color: #ff3366; z-index: 10;
}
#countdown.show { display: flex; animation: pop 0.5s ease; }
#result {
position: absolute; inset: 0;
background: rgba(0,0,0,0.96);
display: none; flex-direction: column;
align-items: center; justify-content: center;
padding: 25px; text-align: center; z-index: 20;
}
#result.show { display: flex; animation: fadeIn 0.3s ease; }
#result h2 { font-size: 1.9rem; margin-bottom: 8px; }
#result .score { font-size: 4rem; font-weight: 900; color: #00ff88; margin: 15px 0; }
#result .stats {
display: flex; gap: 10px; margin-bottom: 25px; flex-wrap: wrap; justify-content: center;
}
#result .stats div {
background: rgba(255,255,255,0.05); padding: 10px 14px;
border-radius: 10px; font-size: 0.72rem; color: #888;
}
#result .stats b { color: #fff; display: block; font-size: 1.1rem; margin-top: 3px; }
@keyframes fadeIn { from{opacity:0} to{opacity:1} }
@keyframes pop { 0%{transform:scale(0.5);opacity:0} 50%{transform:scale(1.1)} 100%{transform:scale(1);opacity:1} }
@keyframes pulse { 0%{transform:scale(1)} 50%{transform:scale(1.06)} 100%{transform:scale(1)} }
@keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-8px)} 75%{transform:translateX(8px)} }
.shake { animation: shake 0.3s ease; }
</style>
</head>
<body>
<div id="game">
<!-- MENÜ -->
<div id="menu">
<h1>SES KOPYASI</h1>
<p>ARCADE KAOS</p>
<div class="rules">
<div><span class="key" style="background:#00ff88"></span><b style="color:#00ff88">YEŞİL</b> = kısa & sessiz (şıklat)</div>
<div><span class="key" style="background:#00ccff"></span><b style="color:#00ccff">MAVİ</b> = kısa & orta (el çırp)</div>
<div><span class="key" style="background:#ff3366"></span><b style="color:#ff3366">KIRMIZI</b> = uzun & yüksek (BAĞIR)</div>
<div><span class="key" style="background:#ffcc00"></span><b style="color:#ffcc00">SARI</b> = uzun & sessiz (mırıldan)</div>
<hr style="border:none; border-top:1px solid #222; margin:8px 0;">
⚡ Her turda süre <b>%8 kısalır</b><br>
🔥 Combo x5 = x2 puan, x10 = x3 puan<br>
👻 Sahte sinyal çıkabilir (yanıp söner)<br>
🔄 Sinyal tur içinde rengini değiştirebilir<br>
🎨 Aynı anda 2 sinyal gelebilir!
</div>
<div class="best">Rekor: <span id="bestScore">0</span></div>
<br>
<button onclick="startGame()">KAOSU BAŞLAT</button>
</div>
<!-- OYUN ALANI -->
<div id="arena">
<div id="roundInfo">
<div>Tur <b id="roundNum">1</b></div>
<div>Skor <b id="scoreNum">0</b></div>
<div>Can <b id="lifeNum">❤❤❤</b></div>
</div>
<div id="combo"></div>
<div id="signalArea">
<div id="ringTimer"></div>
<div class="signal" id="signal1">
<div>
<span class="emoji">🎤</span>
<div class="label">HAZIR</div>
</div>
</div>
<div class="signal" id="signal2" style="display:none;"></div>
</div>
<div id="hint">Hazır ol...</div>
<div id="meter"><div id="meterFill"></div></div>
</div>
<!-- GERİ SAYIM -->
<div id="countdown">3</div>
<!-- SONUÇ -->
<div id="result">
<h2 id="resultTitle">OYUN BİTTİ</h2>
<div class="score" id="finalScore">0</div>
<div class="stats">
<div>Tur<b id="statRound">1</b></div>
<div>Maks Combo<b id="statCombo">0</b></div>
<div>İsabet<b id="statAcc">0%</b></div>
</div>
<button onclick="startGame()">TEKRAR OYNA</button>
</div>
</div>
<script>
// ==================== KONFİG ====================
const CONFIG = {
baseWindow: 1300, // ms
windowDecay: 0.92, // her turda %8 azalır
minWindow: 450,
tolerance: 0.2,
maxLives: 3,
dualChance: 0.2, // çift sinyal şansı
ghostChance: 0.15, // sahte sinyal şansı
swapChance: 0.25 // renk değiştirme şansı
};
const SIGNALS = {
green: { color:'#00ff88', emoji:'🟢', label:'KISA/SESSİZ', hint:'Şıklat!', dB:'low', dur:'short' },
blue: { color:'#00ccff', emoji:'🔵', label:'KISA/ORTA', hint:'El çırp!', dB:'mid', dur:'short' },
red: { color:'#ff3366', emoji:'🔴', label:'UZUN/YÜKSEK', hint:'BAĞIR!', dB:'high', dur:'long' },
yellow: { color:'#ffcc00', emoji:'🟡', label:'UZUN/SESSİZ', hint:'Mırıldan!', dB:'low', dur:'long' }
};
const DB_RANGES = {
low: { min: 0.00, max: 0.20 },
mid: { min: 0.20, max: 0.50 },
high: { min: 0.50, max: 1.00 }
};
const DUR_RANGES = {
short: { min: 0, max: 280 },
long: { min: 280, max: 99999 }
};
// ==================== DURUM ====================
let audioCtx, analyser, micStream, dataArray;
let isPlaying = false;
let round = 1, score = 0, lives = CONFIG.maxLives;
let combo = 0, maxCombo = 0, hits = 0, attempts = 0;
let reactionWindow = CONFIG.baseWindow;
let currentSignals = []; // aktif sinyaller
let roundTimeout = null;
let swapTimeout = null;
let ghostTimeout = null;
let rafId = null;
let listenStart = 0, listenEnd = 0;
let peakVolume = 0;
let soundDetected = false;
let silenceStart = 0;
let evaluated = false;
// ==================== SES ====================
async function initMic() {
try {
micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 512;
analyser.smoothingTimeConstant = 0.25;
const src = audioCtx.createMediaStreamSource(micStream);
src.connect(analyser);
dataArray = new Uint8Array(analyser.frequencyBinCount);
return true;
} catch(e) {
alert('Mikrofon izni gerekli kanka! 🎤');
return false;
}
}
function getVolume() {
if (!analyser) return 0;
analyser.getByteTimeDomainData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
const v = (dataArray[i] - 128) / 128;
sum += v * v;
}
return Math.sqrt(sum / dataArray.length);
}
// ==================== OYUN ====================
async function startGame() {
document.getElementById('menu').style.display = 'none';
document.getElementById('result').classList.remove('show');
document.getElementById('arena').style.display = 'block';
document.body.classList.remove('danger');
if (!audioCtx) {
const ok = await initMic();
if (!ok) return;
}
if (audioCtx.state === 'suspended') await audioCtx.resume();
round = 1; score = 0; lives = CONFIG.maxLives;
combo = 0; maxCombo = 0; hits = 0; attempts = 0;
reactionWindow = CONFIG.baseWindow;
isPlaying = true;
updateHUD();
await countdown(3);
nextRound();
}
function countdown(n) {
return new Promise(resolve => {
const el = document.getElementById('countdown');
el.classList.add('show');
let i = n;
el.textContent = i;
const iv = setInterval(() => {
i--;
if (i > 0) {
el.textContent = i;
el.style.animation = 'none';
void el.offsetWidth;
el.style.animation = 'pop 0.5s ease';
} else {
clearInterval(iv);
el.classList.remove('show');
resolve();
}
}, 700);
});
}
function pickSignalKey() {
const keys = Object.keys(SIGNALS);
return keys[Math.floor(Math.random() * keys.length)];
}
function nextRound() {
if (!isPlaying) return;
if (lives <= 0) return endGame();
// Temizle
clearTimeout(roundTimeout);
clearTimeout(swapTimeout);
clearTimeout(ghostTimeout);
cancelAnimationFrame(rafId);
// Zorlaştırma
if (round > 1) {
reactionWindow = Math.max(CONFIG.minWindow, reactionWindow * CONFIG.windowDecay);
}
// Sinyalleri belirle
currentSignals = [];
const isDual = Math.random() < CONFIG.dualChance && round > 3;
const isGhost = !isDual && Math.random() < CONFIG.ghostChance;
if (isDual) {
const k1 = pickSignalKey();
let k2 = pickSignalKey();
while (k2 === k1) k2 = pickSignalKey();
currentSignals = [
{ key: k1, ...SIGNALS[k1], ghost: false, swap: false },
{ key: k2, ...SIGNALS[k2], ghost: false, swap: false }
];
renderDual(currentSignals);
document.getElementById('hint').textContent = '🎨 İKİSİNİ DE YAP!';
document.getElementById('hint').style.color = '#ffcc00';
} else {
const k = pickSignalKey();
const isSwap = Math.random() < CONFIG.swapChance && round > 5;
currentSignals = [{
key: k, ...SIGNALS[k],
ghost: isGhost,
swap: isSwap,
swapTo: isSwap ? (() => { let n = pickSignalKey(); while(n === k) n = pickSignalKey(); return n; })() : null
}];
renderSingle(currentSignals[0]);
document.getElementById('hint').textContent = '👉 ' + SIGNALS[k].hint;
document.getElementById('hint').style.color = SIGNALS[k].color;
}
// Sahte sinyal kurulumu
if (isGhost) {
const el = document.getElementById('signal1');
el.classList.add('ghost');
ghostTimeout = setTimeout(() => {
// Sahte sinyal kayboldu — puan cezası yok, ama refleks boşa gitti
if (!evaluated) {
showHint('👻 SAHTEYDİ!', '#888');
}
}, 400);
}
// Renk değiştirme tuzağı
if (!isDual && currentSignals[0].swap) {
swapTimeout = setTimeout(() => {
if (!isPlaying || evaluated) return;
const newKey = currentSignals[0].swapTo;
currentSignals[0] = { key: newKey, ...SIGNALS[newKey], ghost: false, swap: false };
const el = document.getElementById('signal1');
el.classList.add('swapping');
setTimeout(() => el.classList.remove('swapping'), 450);
renderSingle(currentSignals[0]);
showHint('🔄 DEĞİŞTİ!', '#ff3366');
}, Math.max(250, reactionWindow * 0.35));
}
// Dinleme başlat
soundDetected = false;
evaluated = false;
peakVolume = 0;
listenStart = 0;
listenEnd = 0;
silenceStart = 0;
animateRing(reactionWindow);
meterLoop();
// Zaman aşımı
roundTimeout = setTimeout(() => {
if (!soundDetected && !evaluated) {
evaluated = true;
fail('Çok yavaş! ⏰');
}
}, reactionWindow);
}
function renderSingle(sig) {
const el1 = document.getElementById('signal1');
const el2 = document.getElementById('signal2');
el2.style.display = 'none';
el2.className = 'signal';
el1.className = 'signal active glow' + (sig.ghost ? ' ghost' : '');
el1.style.color = sig.color;
el1.style.background = sig.color + '22';
el1.innerHTML = `
<div>
<span class="emoji">${sig.emoji}</span>
<div class="label" style="color:${sig.color}">${sig.label}</div>
</div>`;
}
function renderDual(sigs) {
const el1 = document.getElementById('signal1');
const el2 = document.getElementById('signal2');
el1.className = 'signal active glow dual pos-left';
el1.style.color = sigs[0].color;
el1.style.background = sigs[0].color + '22';
el1.innerHTML = `
<div>
<span class="emoji">${sigs[0].emoji}</span>
<div class="label" style="color:${sigs[0].color}">${sigs[0].label}</div>
</div>`;
el2.style.display = 'flex';
el2.className = 'signal active glow dual pos-right';
el2.style.color = sigs[1].color;
el2.style.background = sigs[1].color + '22';
el2.innerHTML = `
<div>
<span class="emoji">${sigs[1].emoji}</span>
<div class="label" style="color:${sigs[1].color}">${sigs[1].label}</div>
</div>`;
}
function showHint(text, color) {
const el = document.getElementById('hint');
el.textContent = text;
el.style.color = color;
el.classList.add('big');
setTimeout(() => el.classList.remove('big'), 300);
}
function animateRing(duration) {
const ring = document.getElementById('ringTimer');
const start = performance.now();
const tick = () => {
if (evaluated || !isPlaying) { ring.style.setProperty('--p', '0%'); return; }
const p = Math.max(0, 100 - ((performance.now() - start) / duration) * 100);
ring.style.setProperty('--p', p + '%');
if (p > 0) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
function meterLoop() {
if (!isPlaying || evaluated) return;
const vol = getVolume();
document.getElementById('meterFill').style.width = Math.min(100, vol * 200) + '%';
if (vol > 0.05) {
if (!soundDetected) {
soundDetected = true;
listenStart = performance.now();
peakVolume = vol;
waitForSilence();
return;
}
}
rafId = requestAnimationFrame(meterLoop);
}
function waitForSilence() {
const check = () => {
if (!isPlaying || evaluated) return;
const vol = getVolume();
if (vol > peakVolume) peakVolume = vol;
document.getElementById('meterFill').style.width = Math.min(100, vol * 200) + '%';
if (vol < 0.04) {
if (silenceStart === 0) silenceStart = performance.now();
if (performance.now() - silenceStart > 130) {
listenEnd = silenceStart;
evaluate();
return;
}
} else {
silenceStart = 0;
}
if (performance.now() - listenStart > 1800) {
listenEnd = performance.now();
evaluate();
return;
}
requestAnimationFrame(check);
};
requestAnimationFrame(check);
}
function evaluate() {
if (evaluated) return;
evaluated = true;
clearTimeout(roundTimeout);
cancelAnimationFrame(rafId);
attempts++;
const duration = listenEnd - listenStart;
const volume = peakVolume;
// Hedef sinyal: eğer çiftse, daha uygun olanı seç (oyuncu dostu)
let target = currentSignals[0];
if (currentSignals.length === 2) {
// Oyuncunun yaptığı sese en yakın sinyali seç
target = currentSignals.reduce((best, s) => {
const dbR = DB_RANGES[s.dB], duR = DUR_RANGES[s.dur];
const dbOK = volume >= dbR.min && volume <= dbR.max;
const duOK = duration >= duR.min && duration <= duR.max;
const score = (dbOK?2:0) + (duOK?2:0);
return score > best.score ? {...s, score} : best;
}, {score:-1});
if (!target.key) target = currentSignals[0];
}
const dbR = DB_RANGES[target.dB];
const duR = DUR_RANGES[target.dur];
const dbOK = volume >= dbR.min * (1 - CONFIG.tolerance) &&
volume <= Math.min(1, dbR.max * (1 + CONFIG.tolerance));
const duOK = duration >= duR.min * (1 - CONFIG.tolerance) &&
duration <= duR.max * (1 + CONFIG.tolerance);
if (dbOK && duOK) {
hits++;
combo++;
maxCombo = Math.max(maxCombo, combo);
// Puanlama
let pts = 10;
const reflexBonus = Math.max(0, Math.round((reactionWindow - duration) / 60));
pts += reflexBonus;
if (combo >= 10) pts *= 3;
else if (combo >= 5) pts *= 2;
score += pts;
// Combo göster
if (combo >= 5) {
const comboEl = document.getElementById('combo');
comboEl.textContent = `🔥 COMBO x${combo} ${combo >= 10 ? '(x3!)' : '(x2!)'}`;
comboEl.classList.add('show');
setTimeout(() => comboEl.classList.remove('show'), 700);
}
showHint(`✅ +${pts} ${reflexBonus > 100 ? '⚡' : ''}`, '#00ff88');
updateHUD();
setTimeout(() => {
round++;
updateHUD();
nextRound();
}, 450);
} else {
const reason = !dbOK
? `Ses yanlış (${volume < 0.15 ? 'çok sessiz' : volume < 0.4 ? 'orta' : 'yüksek'})`
: `Süre yanlış (${duration < 280 ? 'kısa' : 'uzun'})`;
fail(reason);
}
}
function fail(reason) {
evaluated = true;
clearTimeout(roundTimeout);
cancelAnimationFrame(rafId);
combo = 0;
lives--;
updateHUD();
hideCombo();
const el = document.getElementById('signal1');
el.classList.add('shake');
setTimeout(() => el.classList.remove('shake'), 320);
showHint('❌ ' + reason, '#ff3366');
if (lives === 1) document.body.classList.add('danger');
if (lives <= 0) {
setTimeout(endGame, 700);
} else {
setTimeout(() => {
round++;
updateHUD();
nextRound();
}, 900);
}
}
function hideCombo() {
document.getElementById('combo').classList.remove('show');
}
function updateHUD() {
document.getElementById('roundNum').textContent = round;
document.getElementById('scoreNum').textContent = score;
const lifeStr = '❤'.repeat(Math.max(0, lives)) || '💀';
const lifeEl = document.getElementById('lifeNum');
lifeEl.textContent = lifeStr;
lifeEl.style.color = lives === 1 ? '#ff3366' : '#fff';
}
function endGame() {
isPlaying = false;
evaluated = true;
clearTimeout(roundTimeout);
clearTimeout(swapTimeout);
clearTimeout(ghostTimeout);
cancelAnimationFrame(rafId);
document.body.classList.remove('danger');
const best = Math.max(score, parseInt(localStorage.getItem('echoBest') || '0'));
localStorage.setItem('echoBest', best);
document.getElementById('bestScore').textContent = best;
document.getElementById('finalScore').textContent = score;
document.getElementById('statRound').textContent = round;
document.getElementById('statCombo').textContent = maxCombo;
document.getElementById('statAcc').textContent = attempts ? Math.round(hits/attempts*100) + '%' : '0%';
let title = '😅 TEKRAR DENE';
if (score > 250) title = '🔥 EFSANE!';
else if (score > 120) title = '💪 İYİ!';
else if (score > 50) title = '👍 FENA DEĞİL';
document.getElementById('resultTitle').textContent = title;
document.getElementById('result').classList.add('show');
document.getElementById('arena').style.display = 'none';
}
// ==================== BAŞLAT ====================
window.addEventListener('load', () => {
document.getElementById('bestScore').textContent = localStorage.getItem('echoBest') || '0';
});
</script>
</body>
</html>Game Source: SES KOPYASI — ARCADE KAOS
Creator: DriftBuilder15
Libraries: none
Complexity: complex (708 lines, 22.4 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: ses-kopyasi-arcade-kaos-driftbuilder15" to link back to the original. Then publish at arcadelab.ai/publish.