README.md
Rendering markdown...
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CVE-2025-21082: HyperOS AVCodec UAF Simülasyonu</title>
<style>
:root {
--bg-color: #0b0c10;
--panel-bg: rgba(31, 40, 51, 0.7);
--accent-cyan: #66fcf1;
--accent-blue: #45a29e;
--alert-red: #ff4655;
--warn-yellow: #f1c40f;
--success-green: #2ecc71;
--text-main: #c5c6c7;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--bg-color);
background-image:
linear-gradient(rgba(102, 252, 241, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(102, 252, 241, 0.03) 1px, transparent 1px);
background-size: 30px 30px;
color: var(--text-main);
overflow-x: hidden;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
padding-top: 20px;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
color: #fff;
text-shadow: 0 0 10px rgba(102, 252, 241, 0.5);
letter-spacing: 1px;
}
.header .subtitle {
font-size: 1.2em;
color: var(--accent-blue);
font-family: 'Courier New', Courier, monospace;
}
.simulation-area {
background: var(--panel-bg);
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
backdrop-filter: blur(12px);
border: 1px solid rgba(102, 252, 241, 0.2);
position: relative;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
background: transparent;
border: 1px solid;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 2px;
font-family: 'Courier New', Courier, monospace;
}
.btn-primary {
color: var(--accent-cyan);
border-color: var(--accent-cyan);
box-shadow: 0 0 10px rgba(102, 252, 241, 0.1);
}
.btn-secondary {
color: var(--text-main);
border-color: var(--text-main);
}
.btn-success {
color: var(--success-green);
border-color: var(--success-green);
}
.btn:hover:not(:disabled) {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.05);
}
.btn-primary:hover:not(:disabled) {
box-shadow: 0 0 20px rgba(102, 252, 241, 0.4);
background: rgba(102, 252, 241, 0.1);
}
.btn:disabled {
opacity: 0.3;
cursor: not-allowed;
transform: none;
}
.scene {
display: none;
min-height: 400px;
position: relative;
}
.scene.active {
display: block;
animation: glitchFade 0.6s ease-out;
}
@keyframes glitchFade {
0% { opacity: 0; transform: translateY(10px) scale(0.98); filter: hue-rotate(90deg); }
50% { opacity: 0.5; filter: hue-rotate(-90deg); }
100% { opacity: 1; transform: translateY(0) scale(1); filter: hue-rotate(0); }
}
.scene-title {
font-size: 1.8em;
margin-bottom: 25px;
text-align: center;
color: #fff;
border-bottom: 1px solid rgba(102, 252, 241, 0.2);
padding-bottom: 15px;
}
.memory-layout {
display: flex;
justify-content: space-around;
margin: 40px 0;
flex-wrap: wrap;
gap: 20px;
}
.memory-block {
width: 150px;
height: 100px;
border: 2px solid rgba(255,255,255,0.1);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family: 'Courier New', Courier, monospace;
font-weight: bold;
font-size: 0.9em;
transition: all 0.3s ease;
background: rgba(0,0,0,0.4);
position: relative;
overflow: hidden;
}
.memory-block::before {
content: '';
position: absolute;
top: 0; left: -100%; width: 50%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transition: 0.5s;
}
.memory-block:hover::before {
left: 100%;
}
.memory-block.allocated {
border-color: var(--success-green);
color: var(--success-green);
box-shadow: inset 0 0 15px rgba(46, 204, 113, 0.2);
}
.memory-block.freed {
border-color: var(--alert-red);
color: var(--alert-red);
box-shadow: inset 0 0 15px rgba(255, 70, 85, 0.2);
animation: pulse-red 1.5s infinite;
}
.memory-block.corrupted {
border-color: var(--warn-yellow) !important;
color: var(--warn-yellow) !important;
box-shadow: inset 0 0 15px rgba(241, 196, 15, 0.2) !important;
animation: glitch-anim 0.3s infinite;
}
@keyframes pulse-red {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
@keyframes glitch-anim {
0% { transform: translate(0) }
20% { transform: translate(-2px, 1px) }
40% { transform: translate(-1px, -1px) }
60% { transform: translate(2px, 1px) }
80% { transform: translate(1px, -1px) }
100% { transform: translate(0) }
}
.thread-visualization {
display: flex;
justify-content: space-between;
margin: 30px 0;
gap: 20px;
}
.thread {
width: 50%;
padding: 25px;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
background: rgba(0,0,0,0.3);
text-align: center;
transition: all 0.3s ease;
}
.thread h3 {
margin-bottom: 15px;
font-family: 'Courier New', Courier, monospace;
}
.thread.main {
border-top: 3px solid var(--accent-blue);
}
.thread.worker {
border-top: 3px solid #9b59b6;
}
.thread.active {
background: rgba(102, 252, 241, 0.05);
box-shadow: 0 0 15px rgba(102, 252, 241, 0.1);
}
.timeline {
margin: 40px 0;
position: relative;
border-left: 2px solid rgba(102, 252, 241, 0.3);
padding-left: 20px;
margin-left: 20px;
}
.timeline-item {
display: flex;
align-items: center;
margin: 20px 0;
opacity: 0.2;
transform: translateX(-10px);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
font-family: 'Courier New', Courier, monospace;
}
.timeline-item.active {
opacity: 1;
transform: translateX(0);
}
.timeline-marker {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--accent-cyan);
position: absolute;
left: -27px;
box-shadow: 0 0 10px var(--accent-cyan);
}
.code-block {
background: #050505;
border-radius: 6px;
padding: 20px;
margin: 20px 0;
font-family: 'Consolas', 'Courier New', monospace;
border-left: 3px solid var(--accent-cyan);
overflow-x: auto;
white-space: pre;
font-size: 0.9em;
color: #a9b7c6;
box-shadow: inset 0 0 10px rgba(0,0,0,0.8);
}
.code-comment { color: #629755; font-style: italic; }
.code-keyword { color: #cc7832; font-weight: bold; }
.code-string { color: #6a8759; }
.code-func { color: #ffc66d; }
.code-alert { color: var(--alert-red); font-weight: bold; }
.vulnerability-indicator {
position: absolute;
top: 25px;
right: 25px;
font-size: 2em;
color: var(--alert-red);
animation: warning-flash 1s infinite;
display: none;
text-shadow: 0 0 20px var(--alert-red);
}
.vulnerability-indicator.show { display: block; }
@keyframes warning-flash {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.1); }
}
.progress-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.05);
margin: 20px 0 40px 0;
overflow: hidden;
border-radius: 2px;
}
.progress-fill {
height: 100%;
background: var(--accent-cyan);
width: 0%;
transition: width 0.4s ease;
box-shadow: 0 0 10px var(--accent-cyan);
}
.info-panel {
background: rgba(0, 0, 0, 0.5);
border-left: 4px solid var(--warn-yellow);
padding: 20px;
margin: 20px 0;
line-height: 1.6;
font-size: 0.95em;
}
.info-panel h3 {
color: var(--warn-yellow);
margin-bottom: 12px;
font-family: 'Courier New', Courier, monospace;
}
.legend {
display: flex;
justify-content: center;
gap: 25px;
margin: 30px 0 10px 0;
flex-wrap: wrap;
font-size: 0.85em;
font-family: 'Courier New', Courier, monospace;
}
.legend-item { display: flex; align-items: center; gap: 8px; }
.legend-color { width: 14px; height: 14px; border-radius: 3px; }
@media (max-width: 768px) {
.container { padding: 15px; }
.header h1 { font-size: 1.8em; }
.thread-visualization { flex-direction: column; }
.thread { width: 100%; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>[ CVE-2025-21082 ]</h1>
<div class="subtitle">>_ HyperOS AVCodec UAF Zafiyet Simülasyonu</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="startSimulation()">▶ Simülasyonu Başlat</button>
<button class="btn btn-secondary" onclick="previousScene()" id="prevBtn" disabled>◄ Önceki</button>
<button class="btn btn-secondary" onclick="nextScene()" id="nextBtn">Sonraki ►</button>
<button class="btn btn-success" onclick="resetSimulation()">↻ Sistemi Sıfırla</button>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="simulation-area">
<div class="vulnerability-indicator" id="globalWarning">⚠ CRITICAL</div>
<div class="scene active" id="scene1">
<div class="scene-title">0x01: Mimari ve Zafiyete Genel Bakış</div>
<div class="info-panel">
<h3>> Sistem Analizi</h3>
<p>HyperOS AVCodec, Android'in MediaCodec altyapısını yapay zeka destekli dinamik ölçeklendirme katmanlarıyla genişletir. Keşfedilen temel zafiyet, asenkron donanım bileşenlerinin hızlı serbest bırakılması (release) işlemi sırasında oluşan bir <strong>Thread Senkronizasyon (Race Condition)</strong> hatasından kaynaklanmaktadır.</p>
</div>
<div class="code-block">
<span class="code-comment">// HyperOS AVCodec Çekirdek Asenkron Mimari Yapısı</span>
<span class="code-keyword">class</span> AVCodecContext {
MediaBuffer* buffer_;
std::thread worker_thread_;
AIEnhancementLayer* ai_layer_;
<span class="code-keyword">void</span> <span class="code-func">processFrameAsync</span>() {
<span class="code-comment">// Asenkron frame işleme döngüsü başlatılıyor</span>
worker_thread_ = std::thread([<span class="code-keyword">this</span>]() {
ai_layer_->enhanceFrame(buffer_); <span class="code-alert">// Race Condition hedef pointer'ı</span>
});
<span class="code-comment">// EKSİK: Durum doğrulaması veya mutex kilit mekanizması yok!</span>
}
};
</div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="border: 1px solid #2ecc71; background: rgba(46, 204, 113, 0.2);"></div>
<span>Tahsis Edilmiş (Allocated)</span>
</div>
<div class="legend-item">
<div class="legend-color" style="border: 1px solid #ff4655; background: rgba(255, 70, 85, 0.2);"></div>
<span>Serbest Bırakılmış (Freed)</span>
</div>
<div class="legend-item">
<div class="legend-color" style="border: 1px solid #f1c40f; background: rgba(241, 196, 15, 0.2);"></div>
<span>İstismar Edilmiş (Corrupted)</span>
</div>
</div>
</div>
<div class="scene" id="scene2">
<div class="scene-title">0x02: Bellek Bağlamı (Context) Tahsisi</div>
<div class="memory-layout">
<div class="memory-block allocated" id="contextBlock">
CodecContext<br><span style="color:var(--accent-cyan)">0xDEADBEEF</span>
</div>
<div class="memory-block allocated" id="bufferBlock">
MediaBuffer<br><span style="color:var(--accent-cyan)">1024 bytes</span>
</div>
<div class="memory-block allocated" id="aiBlock">
AI Metadata<br><span style="color:var(--accent-cyan)">256 bytes</span>
</div>
</div>
<div class="thread-visualization">
<div class="thread main active">
<h3>[ Main Thread ]</h3>
<p style="color:#aaa; font-size: 0.9em; line-height: 1.8;">> AVCodec mimarisi oluşturuluyor...<br>> Medya hattı buffer'ları ayrılıyor...<br>> Yapay zeka katmanı başlatılıyor...</p>
</div>
<div class="thread worker">
<h3>[ Worker Thread ]</h3>
<p style="color:#aaa; font-size: 0.9em; line-height: 1.8;">> Pipeline başlatma görevi bekleniyor...<br>> Bekleme durumu (Idle) - Sinyale hazır</p>
</div>
</div>
<div class="code-block">
<span class="code-comment">// Çalışma zamanı (runtime) bellek pointer'ları başlatılıyor</span>
CodecContext* ctx = <span class="code-keyword">new</span> <span class="code-func">CodecContext</span>();
ctx->buffer_ = <span class="code-func">allocateBuffer</span>(1024);
ctx->ai_layer_ = <span class="code-keyword">new</span> <span class="code-func">AIEnhancementLayer</span>();
<span class="code-comment">// Heap düzeni stabil. Context Magic Doğrulama Bayrağı: 0xDEADBEEF</span>
</div>
</div>
<div class="scene" id="scene3">
<div class="scene-title">0x03: Race Condition Tetiklenmesi</div>
<div class="thread-visualization">
<div class="thread main active" id="mainThread3">
<h3 style="color: var(--accent-cyan)">[ Main Thread ]</h3>
<p style="font-family: monospace; margin-bottom: 10px;">> processFrameAsync() çağrıldı...</p>
<p style="color: var(--alert-red); font-weight: bold; font-family: monospace;">! Senkronizasyon olmadan release() tetiklendi !</p>
<div style="background: rgba(255, 70, 85, 0.2); border: 1px solid var(--alert-red); padding: 5px; margin-top: 15px; font-size: 0.8em; letter-spacing: 1px;">ZAFİYET PENCERESİ AÇIK</div>
</div>
<div class="thread worker active" id="workerThread3">
<h3 style="color: #9b59b6">[ Worker Thread ]</h3>
<p style="font-family: monospace; margin-bottom: 10px;">> Ses/Görüntü akışı çözülüyor...</p>
<p style="color: var(--warn-yellow); font-family: monospace;">> Paylaşılan bellek bölgesine erişiliyor...</p>
<div style="background: rgba(241, 196, 15, 0.2); border: 1px solid var(--warn-yellow); padding: 5px; margin-top: 15px; font-size: 0.8em; letter-spacing: 1px;">RACE CONDITION AKTİF</div>
</div>
</div>
<div class="timeline">
<div class="timeline-item" id="t1">
<div class="timeline-marker"></div>
<span>[T+0ms] processFrameAsync() fonksiyonu başlatıldı.</span>
</div>
<div class="timeline-item" id="t2">
<div class="timeline-marker"></div>
<span>[T+10ms] Worker thread arka planda pointer üzerinden okuma yapıyor.</span>
</div>
<div class="timeline-item" id="t3">
<div class="timeline-marker"></div>
<span><strong style="color:var(--alert-red)">[T+12ms] Main thread aniden release() çağrısı yapıyor.</strong></span>
</div>
<div class="timeline-item" id="t4">
<div class="timeline-marker"></div>
<span>[T+15ms] Worker thread döngüdeyken, bellekteki pointer'lar serbest bırakılıyor (Free).</span>
</div>
</div>
</div>
<div class="scene" id="scene4">
<div class="scene-title">0x04: Use-After-Free (UAF) İstismarı</div>
<div class="memory-layout">
<div class="memory-block freed" id="contextBlock4">
SERBEST BIRAKILDI<br><span style="color:#fff">0xFEEDFACE</span>
</div>
<div class="memory-block" id="bufferBlock4">
Bozulmuş Heap<br><span style="color:#fff">0xAAAABBBB</span>
</div>
<div class="memory-block" id="aiBlock4">
Zararlı Payload<br><span style="color:#fff">0xCCCCDDDD</span>
</div>
</div>
<div class="info-panel" style="border-color: var(--alert-red);">
<h3 style="color: var(--alert-red);">> UAF Tespiti & İstismar Vektörü</h3>
<p style="margin-bottom: 8px;"><strong>Doğrulama Atlatıldı:</strong> Beklenen başlık etiketi <code>0xDEADBEEF</code> yerine bozulmuş blok <code>0xFEEDFACE</code> okundu.</p>
<p style="margin-bottom: 8px;"><strong>Heap Geri Kazanımı (Reclaiming):</strong> Main Thread tarafından bırakılan bellek blokları, Worker thread işlemi bitirmeden hemen önce saldırgan kontrollü verilerle dolduruldu.</p>
<p><strong>Etki:</strong> Bozulmuş sanal fonksiyon tabloları (vtable hijacking) üzerinden kontrol akışı ele geçirildi. İsteğe bağlı kod yürütme (RCE) sağlandı.</p>
</div>
<div class="code-block" style="border-color: var(--alert-red);">
<span class="code-comment">// Bellek izleme simülasyon döngüsü (Rust tabanlı)</span>
<span class="code-keyword">unsafe fn</span> <span class="code-func">process_frame</span>(&<span class="code-keyword">mut</span> self) -> <span class="code-keyword">bool</span> {
<span class="code-keyword">if</span> self.magic != <span class="code-string">0xDEADBEEF</span> {
println!(<span class="code-string">"[!] Kritik UAF Tespit Edildi! Reclaimed Pointer Magic: 0x{:08X}"</span>, self.magic);
<span class="code-keyword">return false</span>; <span class="code-comment">// Zafiyet başarıyla tetiklendi!</span>
}
<span class="code-keyword">return true</span>;
}
</div>
</div>
<div class="scene" id="scene5">
<div class="scene-title">0x05: Yama Doğrulaması (Mitigation)</div>
<div style="display: flex; justify-content: space-between; margin: 20px 0; gap: 15px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 280px; background: rgba(255, 70, 85, 0.05); padding: 20px; border-radius: 8px; border: 1px solid rgba(255, 70, 85, 0.2);">
<h3 style="color: var(--alert-red); font-family: monospace; margin-bottom: 10px;">[-] Zafiyetli Kod</h3>
<div class="code-block" style="margin: 0; padding: 15px; font-size: 0.8em; border-color: var(--alert-red); background: #000;">
<span class="code-keyword">void</span> <span class="code-func">release</span>() {
<span class="code-keyword">delete</span> buffer_;
buffer_ = <span class="code-keyword">nullptr</span>;
<span class="code-comment">// Aktif worker güvenliği yok!</span>
<span class="code-comment">// Worker bozuk alanı taramaya devam eder.</span>
}
</div>
</div>
<div style="flex: 1; min-width: 280px; background: rgba(46, 204, 113, 0.05); padding: 20px; border-radius: 8px; border: 1px solid rgba(46, 204, 113, 0.2);">
<h3 style="color: var(--success-green); font-family: monospace; margin-bottom: 10px;">[+] Yamalanmış Kod</h3>
<div class="code-block" style="margin: 0; padding: 15px; font-size: 0.8em; border-color: var(--success-green); background: #000;">
<span class="code-keyword">void</span> <span class="code-func">release</span>() {
shutdown_requested_ = <span class="code-keyword">true</span>;
worker_cv_.notify_all();
<span class="code-comment">// Aktif işlem bitene kadar BEKLE</span>
<span class="code-keyword">if</span> (worker_thread_.joinable()) {
worker_thread_.join();
}
<span class="code-keyword">delete</span> buffer_; <span class="code-comment">// Güvenli temizlik</span>
}
</div>
</div>
</div>
<div class="info-panel" style="border-color: var(--success-green);">
<h3 style="color: var(--success-green);">> Mimari Koruma Stratejileri</h3>
<ul style="margin-left: 20px; padding-left: 5px; color: #ccc;">
<li style="margin-bottom: 8px;"><strong>Belirlenmiş Yaşam Döngüsü Kontrolleri:</strong> Bellek temizleme işleminden önce aktif asenkron kanallar üzerinde katı <code>join()</code> sınırları uygulamak.</li>
<li style="margin-bottom: 8px;"><strong>Güvenli İşaretçiler (Smart Pointers):</strong> C++'da <code>std::shared_ptr</code> veya Rust'ın bellek güvenliği soyutlamalarını kullanarak referans sayımını otomatikleştirmek.</li>
<li><strong>Sanitizer Taramaları:</strong> Test ortamlarına LLVM AddressSanitizer (ASan) ve ThreadSanitizer entegre ederek bellek sızıntılarını derleme aşamasında yakalamak.</li>
</ul>
</div>
</div>
</div>
</div>
<script>
let currentScene = 1;
const totalScenes = 5;
let isPlaying = false;
let autoPlayInterval;
function updateProgress() {
const progress = (currentScene / totalScenes) * 100;
document.getElementById('progressFill').style.width = progress + '%';
}
function showScene(sceneNumber) {
for (let i = 1; i <= totalScenes; i++) {
document.getElementById(`scene${i}`).classList.remove('active');
}
document.getElementById(`scene${sceneNumber}`).classList.add('active');
document.getElementById('prevBtn').disabled = sceneNumber === 1;
document.getElementById('nextBtn').disabled = sceneNumber === totalScenes;
updateProgress();
animateScene(sceneNumber);
}
function animateScene(sceneNumber) {
const warningElement = document.getElementById('globalWarning');
if (sceneNumber === 3 || sceneNumber === 4) {
warningElement.classList.add('show');
} else {
warningElement.classList.remove('show');
}
switch(sceneNumber) {
case 2:
setTimeout(() => {
const blocks = ['contextBlock', 'bufferBlock', 'aiBlock'];
blocks.forEach((id, index) => {
setTimeout(() => {
const block = document.getElementById(id);
if (block) {
block.style.transform = 'scale(1.1) translateY(-5px)';
block.style.boxShadow = '0 10px 20px rgba(46, 204, 113, 0.2)';
setTimeout(() => {
block.style.transform = 'scale(1) translateY(0)';
block.style.boxShadow = 'none';
}, 250);
}
}, index * 200);
});
}, 200);
break;
case 3:
document.querySelectorAll('#scene3 .timeline-item').forEach(el => el.classList.remove('active'));
setTimeout(() => {
const targets = ['t1', 't2', 't3', 't4'];
targets.forEach((id, index) => {
setTimeout(() => {
const element = document.getElementById(id);
if (element) element.classList.add('active');
}, index * 800);
});
}, 150);
break;
case 4:
setTimeout(() => {
const b1 = document.getElementById('bufferBlock4');
const b2 = document.getElementById('aiBlock4');
if (b1) b1.className = 'memory-block corrupted';
if (b2) b2.className = 'memory-block corrupted';
}, 500);
break;
}
}
function nextScene() {
if (currentScene < totalScenes) {
currentScene++;
showScene(currentScene);
}
}
function previousScene() {
if (currentScene > 1) {
currentScene--;
showScene(currentScene);
}
}
function startSimulation() {
if (isPlaying) {
clearInterval(autoPlayInterval);
isPlaying = false;
document.querySelector('.btn-primary').innerHTML = '▶ Simülasyonu Başlat';
} else {
isPlaying = true;
document.querySelector('.btn-primary').innerHTML = '⏸ Simülasyonu Duraklat';
autoPlayInterval = setInterval(() => {
if (currentScene < totalScenes) {
nextScene();
} else {
clearInterval(autoPlayInterval);
isPlaying = false;
document.querySelector('.btn-primary').innerHTML = '▶ Simülasyonu Başlat';
}
}, 5000); // 5 saniyelik okuma/geçiş süresi
}
}
function resetSimulation() {
clearInterval(autoPlayInterval);
isPlaying = false;
currentScene = 1;
const b1 = document.getElementById('bufferBlock4');
const b2 = document.getElementById('aiBlock4');
if (b1) b1.className = 'memory-block';
if (b2) b2.className = 'memory-block';
document.querySelectorAll('.timeline-item').forEach(item => item.classList.remove('active'));
document.querySelector('.btn-primary').innerHTML = '▶ Simülasyonu Başlat';
showScene(1);
}
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowLeft': previousScene(); break;
case 'ArrowRight': nextScene(); break;
case ' ': e.preventDefault(); startSimulation(); break;
case 'r': case 'R': resetSimulation(); break;
}
});
</script>
</body>
</html>