|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>RetroVault - Classic Gaming Hub</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsnes.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/gba.min.js"></script> |
|
<style> |
|
@font-face { |
|
font-family: 'Press Start 2P'; |
|
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
|
} |
|
|
|
body { |
|
font-family: 'Press Start 2P', cursive; |
|
background-color: #0f0f1a; |
|
color: #e0e0ff; |
|
overflow-x: hidden; |
|
} |
|
|
|
.neon-text { |
|
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #ff00de, 0 0 20px #ff00de; |
|
} |
|
|
|
.neon-box { |
|
box-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #0073e6, 0 0 20px #0073e6; |
|
} |
|
|
|
.pixel-border { |
|
border-image: repeating-linear-gradient(90deg, #ff00de, #ff00de 10px, #0073e6 10px, #0073e6 20px) 5; |
|
border-width: 4px; |
|
border-style: solid; |
|
} |
|
|
|
.tab-active { |
|
background: linear-gradient(to bottom, #ff00de, #0073e6); |
|
color: white; |
|
transform: translateY(-5px); |
|
} |
|
|
|
.scanlines { |
|
position: relative; |
|
} |
|
|
|
.scanlines:before { |
|
content: ""; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient( |
|
to bottom, |
|
transparent 50%, |
|
rgba(0, 0, 0, 0.2) 51% |
|
); |
|
background-size: 100% 4px; |
|
pointer-events: none; |
|
z-index: 10; |
|
} |
|
|
|
.dark-mode { |
|
background-color: #0f0f1a; |
|
color: #e0e0ff; |
|
} |
|
|
|
.light-mode { |
|
background-color: #f0f0ff; |
|
color: #1a1a2e; |
|
} |
|
|
|
|
|
.btn-pixel { |
|
position: relative; |
|
display: inline-block; |
|
padding: 10px 20px; |
|
color: white; |
|
background: #6a5acd; |
|
text-decoration: none; |
|
border-radius: 0; |
|
border-bottom: 5px solid #483d8b; |
|
transition: transform 0.1s, border-bottom 0.1s; |
|
} |
|
|
|
.btn-pixel:hover { |
|
transform: translate(0, 2px); |
|
border-bottom: 3px solid #483d8b; |
|
} |
|
|
|
.btn-pixel:active { |
|
transform: translate(0, 5px); |
|
border-bottom: 0px solid #483d8b; |
|
} |
|
|
|
|
|
.crt-effect { |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.crt-effect:after { |
|
content: " "; |
|
display: block; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
bottom: 0; |
|
right: 0; |
|
background: rgba(18, 16, 16, 0.1); |
|
opacity: 0.2; |
|
z-index: 2; |
|
pointer-events: none; |
|
} |
|
|
|
.crt-effect:before { |
|
content: " "; |
|
display: block; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
bottom: 0; |
|
right: 0; |
|
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06)); |
|
z-index: 2; |
|
background-size: 100% 2px, 3px 100%; |
|
pointer-events: none; |
|
} |
|
|
|
|
|
#nesCanvas { |
|
image-rendering: -moz-crisp-edges; |
|
image-rendering: -webkit-crisp-edges; |
|
image-rendering: pixelated; |
|
image-rendering: crisp-edges; |
|
width: 100%; |
|
height: 100%; |
|
} |
|
|
|
|
|
.loader { |
|
border: 5px solid #f3f3f3; |
|
border-top: 5px solid #ff00de; |
|
border-radius: 50%; |
|
width: 50px; |
|
height: 50px; |
|
animation: spin 1s linear infinite; |
|
margin: 20px auto; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
|
|
.status-message { |
|
background-color: rgba(0, 0, 0, 0.7); |
|
color: white; |
|
padding: 10px; |
|
border-radius: 5px; |
|
text-align: center; |
|
margin-top: 10px; |
|
font-size: 0.8rem; |
|
} |
|
|
|
.status-success { |
|
color: #00ff00; |
|
} |
|
|
|
.status-error { |
|
color: #ff0000; |
|
} |
|
</style> |
|
</head> |
|
<body class="dark-mode"> |
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
<header class="text-center mb-8"> |
|
<h1 class="text-5xl md:text-6xl font-bold mb-4 neon-text">RetroVault</h1> |
|
<p class="text-xl md:text-2xl">Your classic gaming emporium</p> |
|
|
|
|
|
<div class="flex justify-center mt-4"> |
|
<button id="themeToggle" class="btn-pixel px-6 py-2 rounded-full"> |
|
<i class="fas fa-moon"></i> Dark Mode |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 neon-box"> |
|
|
|
<div class="flex mb-6 border-b-2 border-purple-500"> |
|
<button id="nesTab" class="tab-active px-6 py-3 font-bold rounded-t-lg mr-2 transition-all"> |
|
<i class="fas fa-gamepad mr-2"></i> NES |
|
</button> |
|
<button id="gbaTab" class="px-6 py-3 font-bold rounded-t-lg mr-2 transition-all hover:bg-purple-900 hover:text-white"> |
|
<i class="fas fa-mobile-alt mr-2"></i> GBA |
|
</button> |
|
<button id="controlsTab" class="px-6 py-3 font-bold rounded-t-lg transition-all hover:bg-purple-900 hover:text-white"> |
|
<i class="fas fa-keyboard mr-2"></i> Controls |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="nesSection" class="console-section"> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
|
|
|
<div class="md:col-span-2"> |
|
<div class="bg-black p-4 rounded-lg crt-effect scanlines"> |
|
<div id="nesEmulator" class="mx-auto" style="width: 100%; height: 400px;"> |
|
<canvas id="nesCanvas" width="256" height="240"></canvas> |
|
</div> |
|
<div id="nesStatus" class="status-message">Ready to load NES ROM</div> |
|
</div> |
|
|
|
<div class="flex flex-wrap justify-center mt-4 gap-2"> |
|
<button id="nesLoad" class="btn-pixel"> |
|
<i class="fas fa-folder-open mr-2"></i> Load ROM |
|
</button> |
|
<button id="nesSaveState" class="btn-pixel"> |
|
<i class="fas fa-save mr-2"></i> Save State |
|
</button> |
|
<button id="nesLoadState" class="btn-pixel"> |
|
<i class="fas fa-file-upload mr-2"></i> Load State |
|
</button> |
|
<button id="nesFullscreen" class="btn-pixel"> |
|
<i class="fas fa-expand mr-2"></i> Fullscreen |
|
</button> |
|
<button id="nesReset" class="btn-pixel"> |
|
<i class="fas fa-redo mr-2"></i> Reset |
|
</button> |
|
<button id="nesPause" class="btn-pixel"> |
|
<i class="fas fa-pause mr-2"></i> Pause |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border"> |
|
<h3 class="text-xl font-bold mb-4 text-center neon-text">Game Info</h3> |
|
<div id="nesGameInfo" class="text-sm"> |
|
<p class="mb-2"><span class="text-purple-400">No game loaded</span></p> |
|
<p class="mb-2">Controls:</p> |
|
<ul class="list-disc pl-5"> |
|
<li>Arrow Keys: D-Pad</li> |
|
<li>Z: A Button</li> |
|
<li>X: B Button</li> |
|
<li>Enter: Start</li> |
|
<li>Shift: Select</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Upload your own NES ROMs to play</p> |
|
<div class="mt-4"> |
|
<h4 class="text-lg font-bold mb-2 text-purple-400">Sample ROMs</h4> |
|
<button id="loadSample1" class="btn-pixel text-xs px-3 py-1 mb-2">Super Mario Bros</button> |
|
<button id="loadSample2" class="btn-pixel text-xs px-3 py-1 mb-2">Donkey Kong</button> |
|
<button id="loadSample3" class="btn-pixel text-xs px-3 py-1">The Legend of Zelda</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="gbaSection" class="console-section hidden"> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
|
|
|
<div class="md:col-span-2"> |
|
<div class="bg-black p-4 rounded-lg crt-effect scanlines"> |
|
<div id="gbaEmulator" class="mx-auto" style="width: 100%; height: 400px;"> |
|
<div class="loader"></div> |
|
<p class="text-center">GBA emulator loading...</p> |
|
</div> |
|
</div> |
|
|
|
<div class="flex flex-wrap justify-center mt-4 gap-2"> |
|
<button id="gbaLoad" class="btn-pixel"> |
|
<i class="fas fa-folder-open mr-2"></i> Load ROM |
|
</button> |
|
<button id="gbaSaveState" class="btn-pixel"> |
|
<i class="fas fa-save mr-2"></i> Save State |
|
</button> |
|
<button id="gbaLoadState" class="btn-pixel"> |
|
<i class="fas fa-file-upload mr-2"></i> Load State |
|
</button> |
|
<button id="gbaFullscreen" class="btn-pixel"> |
|
<i class="fas fa-expand mr-2"></i> Fullscreen |
|
</button> |
|
<button id="gbaReset" class="btn-pixel"> |
|
<i class="fas fa-redo mr-2"></i> Reset |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border"> |
|
<h3 class="text-xl font-bold mb-4 text-center neon-text">Game Info</h3> |
|
<div id="gbaGameInfo" class="text-sm"> |
|
<p class="mb-2"><span class="text-purple-400">No game loaded</span></p> |
|
<p class="mb-2">Controls:</p> |
|
<ul class="list-disc pl-5"> |
|
<li>Arrow Keys: D-Pad</li> |
|
<li>A: A Button</li> |
|
<li>S: B Button</li> |
|
<li>Enter: Start</li> |
|
<li>Shift: Select</li> |
|
<li>Q: L Button</li> |
|
<li>W: R Button</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Upload your own GBA ROMs to play</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="controlsSection" class="console-section hidden"> |
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
|
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border"> |
|
<h3 class="text-xl font-bold mb-4 text-center neon-text">Keyboard Controls</h3> |
|
<div class="grid grid-cols-2 gap-4"> |
|
<div> |
|
<h4 class="text-lg font-bold mb-2 text-purple-400">NES</h4> |
|
<ul class="text-sm"> |
|
<li class="mb-1">Arrow Keys: D-Pad</li> |
|
<li class="mb-1">Z: A Button</li> |
|
<li class="mb-1">X: B Button</li> |
|
<li class="mb-1">Enter: Start</li> |
|
<li class="mb-1">Shift: Select</li> |
|
</ul> |
|
</div> |
|
<div> |
|
<h4 class="text-lg font-bold mb-2 text-purple-400">GBA</h4> |
|
<ul class="text-sm"> |
|
<li class="mb-1">Arrow Keys: D-Pad</li> |
|
<li class="mb-1">A: A Button</li> |
|
<li class="mb-1">S: B Button</li> |
|
<li class="mb-1">Enter: Start</li> |
|
<li class="mb-1">Shift: Select</li> |
|
<li class="mb-1">Q: L Button</li> |
|
<li class="mb-1">W: R Button</li> |
|
</ul> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border"> |
|
<h3 class="text-xl font-bold mb-4 text-center neon-text">Gamepad Support</h3> |
|
<div id="gamepadInfo" class="text-sm"> |
|
<p class="mb-4">Connect a gamepad and press any button to enable.</p> |
|
<p class="mb-2">Supported gamepads:</p> |
|
<ul class="list-disc pl-5"> |
|
<li class="mb-1">Xbox 360/One</li> |
|
<li class="mb-1">PlayStation 3/4</li> |
|
<li class="mb-1">Generic USB gamepads</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Buttons will map automatically to standard layout</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="mt-6 bg-gray-800 bg-opacity-70 rounded-lg p-4 pixel-border"> |
|
<h3 class="text-xl font-bold mb-4 text-center neon-text">Customize Controls</h3> |
|
<div class="text-center"> |
|
<button id="remapControls" class="btn-pixel px-6 py-2"> |
|
<i class="fas fa-keyboard mr-2"></i> Remap Keys |
|
</button> |
|
<p class="mt-2 text-xs text-gray-400">Coming soon: Custom key mapping for keyboard and gamepad</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<footer class="mt-8 text-center text-sm text-gray-500"> |
|
<p>RetroVault - Play classic games in your browser</p> |
|
<p class="mt-1">Note: You must provide your own legally obtained ROM files</p> |
|
</footer> |
|
</div> |
|
|
|
|
|
<input type="file" id="nesFileInput" accept=".nes" class="hidden"> |
|
<input type="file" id="gbaFileInput" accept=".gba" class="hidden"> |
|
|
|
<script> |
|
|
|
const themeToggle = document.getElementById('themeToggle'); |
|
const body = document.body; |
|
|
|
themeToggle.addEventListener('click', () => { |
|
if (body.classList.contains('dark-mode')) { |
|
body.classList.remove('dark-mode'); |
|
body.classList.add('light-mode'); |
|
themeToggle.innerHTML = '<i class="fas fa-sun mr-2"></i> Light Mode'; |
|
} else { |
|
body.classList.remove('light-mode'); |
|
body.classList.add('dark-mode'); |
|
themeToggle.innerHTML = '<i class="fas fa-moon mr-2"></i> Dark Mode'; |
|
} |
|
}); |
|
|
|
|
|
const nesTab = document.getElementById('nesTab'); |
|
const gbaTab = document.getElementById('gbaTab'); |
|
const controlsTab = document.getElementById('controlsTab'); |
|
|
|
const nesSection = document.getElementById('nesSection'); |
|
const gbaSection = document.getElementById('gbaSection'); |
|
const controlsSection = document.getElementById('controlsSection'); |
|
|
|
function resetTabs() { |
|
nesTab.classList.remove('tab-active'); |
|
gbaTab.classList.remove('tab-active'); |
|
controlsTab.classList.remove('tab-active'); |
|
|
|
nesTab.classList.add('hover:bg-purple-900', 'hover:text-white'); |
|
gbaTab.classList.add('hover:bg-purple-900', 'hover:text-white'); |
|
controlsTab.classList.add('hover:bg-purple-900', 'hover:text-white'); |
|
|
|
nesSection.classList.add('hidden'); |
|
gbaSection.classList.add('hidden'); |
|
controlsSection.classList.add('hidden'); |
|
} |
|
|
|
nesTab.addEventListener('click', () => { |
|
resetTabs(); |
|
nesTab.classList.add('tab-active'); |
|
nesTab.classList.remove('hover:bg-purple-900', 'hover:text-white'); |
|
nesSection.classList.remove('hidden'); |
|
}); |
|
|
|
gbaTab.addEventListener('click', () => { |
|
resetTabs(); |
|
gbaTab.classList.add('tab-active'); |
|
gbaTab.classList.remove('hover:bg-purple-900', 'hover:text-white'); |
|
gbaSection.classList.remove('hidden'); |
|
}); |
|
|
|
controlsTab.addEventListener('click', () => { |
|
resetTabs(); |
|
controlsTab.classList.add('tab-active'); |
|
controlsTab.classList.remove('hover:bg-purple-900', 'hover:text-white'); |
|
controlsSection.classList.remove('hidden'); |
|
}); |
|
|
|
|
|
const nes = new jsnes.NES({ |
|
onFrame: function(frameBuffer) { |
|
const canvas = document.getElementById('nesCanvas'); |
|
const context = canvas.getContext('2d'); |
|
const imageData = context.createImageData(canvas.width, canvas.height); |
|
|
|
for (let i = 0; i < frameBuffer.length; i++) { |
|
imageData.data[i * 4] = frameBuffer[i] >> 16 & 0xFF; |
|
imageData.data[i * 4 + 1] = frameBuffer[i] >> 8 & 0xFF; |
|
imageData.data[i * 4 + 2] = frameBuffer[i] & 0xFF; |
|
imageData.data[i * 4 + 3] = 0xFF; |
|
} |
|
|
|
context.putImageData(imageData, 0, 0); |
|
}, |
|
onAudioSample: function(left, right) { |
|
|
|
} |
|
}); |
|
|
|
|
|
const keyMap = { |
|
38: jsnes.Controller.BUTTON_UP, |
|
40: jsnes.Controller.BUTTON_DOWN, |
|
37: jsnes.Controller.BUTTON_LEFT, |
|
39: jsnes.Controller.BUTTON_RIGHT, |
|
90: jsnes.Controller.BUTTON_A, |
|
88: jsnes.Controller.BUTTON_B, |
|
13: jsnes.Controller.BUTTON_START, |
|
16: jsnes.Controller.BUTTON_SELECT |
|
}; |
|
|
|
document.addEventListener('keydown', (e) => { |
|
if (!nesSection.classList.contains('hidden')) { |
|
const button = keyMap[e.keyCode]; |
|
if (button !== undefined) { |
|
nes.buttonDown(1, button); |
|
e.preventDefault(); |
|
} |
|
} |
|
}); |
|
|
|
document.addEventListener('keyup', (e) => { |
|
if (!nesSection.classList.contains('hidden')) { |
|
const button = keyMap[e.keyCode]; |
|
if (button !== undefined) { |
|
nes.buttonUp(1, button); |
|
e.preventDefault(); |
|
} |
|
} |
|
}); |
|
|
|
|
|
const nesFileInput = document.getElementById('nesFileInput'); |
|
const nesLoadBtn = document.getElementById('nesLoad'); |
|
const nesStatus = document.getElementById('nesStatus'); |
|
|
|
nesLoadBtn.addEventListener('click', () => { |
|
nesFileInput.click(); |
|
}); |
|
|
|
nesFileInput.addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (!file) return; |
|
|
|
|
|
nesStatus.textContent = `Loading ${file.name}...`; |
|
nesStatus.className = 'status-message'; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
try { |
|
const rom = new Uint8Array(e.target.result); |
|
nes.loadROM(rom); |
|
|
|
|
|
document.getElementById('nesGameInfo').innerHTML = ` |
|
<p class="mb-2"><span class="text-purple-400">${file.name}</span></p> |
|
<p class="mb-2">Controls:</p> |
|
<ul class="list-disc pl-5"> |
|
<li>Arrow Keys: D-Pad</li> |
|
<li>Z: A Button</li> |
|
<li>X: B Button</li> |
|
<li>Enter: Start</li> |
|
<li>Shift: Select</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Press Save State to save your progress</p> |
|
<div class="mt-4"> |
|
<h4 class="text-lg font-bold mb-2 text-purple-400">Sample ROMs</h4> |
|
<button id="loadSample1" class="btn-pixel text-xs px-3 py-1 mb-2">Super Mario Bros</button> |
|
<button id="loadSample2" class="btn-pixel text-xs px-3 py-1 mb-2">Donkey Kong</button> |
|
<button id="loadSample3" class="btn-pixel text-xs px-3 py-1">The Legend of Zelda</button> |
|
</div> |
|
`; |
|
|
|
|
|
nesStatus.textContent = `Successfully loaded ${file.name}`; |
|
nesStatus.className = 'status-message status-success'; |
|
|
|
|
|
if (!nesInterval) { |
|
nesInterval = setInterval(() => { |
|
nes.frame(); |
|
}, 1000 / 60); |
|
} |
|
|
|
} catch (error) { |
|
nesStatus.textContent = `Error loading ROM: ${error.message}`; |
|
nesStatus.className = 'status-message status-error'; |
|
console.error('Error loading ROM:', error); |
|
} |
|
}; |
|
|
|
reader.onerror = () => { |
|
nesStatus.textContent = 'Error reading file'; |
|
nesStatus.className = 'status-message status-error'; |
|
}; |
|
|
|
reader.readAsArrayBuffer(file); |
|
}); |
|
|
|
|
|
const sampleRoms = { |
|
mario: "BASE64_ENCODED_MARIO_ROM_DATA", |
|
donkeykong: "BASE64_ENCODED_DONKEY_KONG_ROM_DATA", |
|
zelda: "BASE64_ENCODED_ZELDA_ROM_DATA" |
|
}; |
|
|
|
document.getElementById('loadSample1').addEventListener('click', () => { |
|
|
|
nesStatus.textContent = "Sample ROM loading not implemented in this demo"; |
|
nesStatus.className = 'status-message status-error'; |
|
}); |
|
|
|
document.getElementById('loadSample2').addEventListener('click', () => { |
|
nesStatus.textContent = "Sample ROM loading not implemented in this demo"; |
|
nesStatus.className = 'status-message status-error'; |
|
}); |
|
|
|
document.getElementById('loadSample3').addEventListener('click', () => { |
|
nesStatus.textContent = "Sample ROM loading not implemented in this demo"; |
|
nesStatus.className = 'status-message status-error'; |
|
}); |
|
|
|
|
|
let nesState = null; |
|
let nesInterval = null; |
|
let isPaused = false; |
|
|
|
document.getElementById('nesSaveState').addEventListener('click', () => { |
|
try { |
|
nesState = nes.toJSON(); |
|
nesStatus.textContent = "Game state saved!"; |
|
nesStatus.className = 'status-message status-success'; |
|
} catch (error) { |
|
nesStatus.textContent = `Error saving state: ${error.message}`; |
|
nesStatus.className = 'status-message status-error'; |
|
} |
|
}); |
|
|
|
document.getElementById('nesLoadState').addEventListener('click', () => { |
|
if (nesState) { |
|
try { |
|
nes.fromJSON(nesState); |
|
nesStatus.textContent = "Game state loaded!"; |
|
nesStatus.className = 'status-message status-success'; |
|
} catch (error) { |
|
nesStatus.textContent = `Error loading state: ${error.message}`; |
|
nesStatus.className = 'status-message status-error'; |
|
} |
|
} else { |
|
nesStatus.textContent = "No saved state found!"; |
|
nesStatus.className = 'status-message status-error'; |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('nesPause').addEventListener('click', () => { |
|
if (isPaused) { |
|
|
|
nesInterval = setInterval(() => { |
|
nes.frame(); |
|
}, 1000 / 60); |
|
document.getElementById('nesPause').innerHTML = '<i class="fas fa-pause mr-2"></i> Pause'; |
|
nesStatus.textContent = "Game resumed"; |
|
nesStatus.className = 'status-message status-success'; |
|
} else { |
|
|
|
clearInterval(nesInterval); |
|
nesInterval = null; |
|
document.getElementById('nesPause').innerHTML = '<i class="fas fa-play mr-2"></i> Resume'; |
|
nesStatus.textContent = "Game paused"; |
|
nesStatus.className = 'status-message'; |
|
} |
|
isPaused = !isPaused; |
|
}); |
|
|
|
|
|
document.getElementById('nesFullscreen').addEventListener('click', () => { |
|
const emulator = document.getElementById('nesEmulator'); |
|
if (emulator.requestFullscreen) { |
|
emulator.requestFullscreen(); |
|
} else if (emulator.webkitRequestFullscreen) { |
|
emulator.webkitRequestFullscreen(); |
|
} else if (emulator.msRequestFullscreen) { |
|
emulator.msRequestFullscreen(); |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('nesReset').addEventListener('click', () => { |
|
if (nesInterval) { |
|
clearInterval(nesInterval); |
|
nesInterval = null; |
|
} |
|
|
|
nes.reset(); |
|
nesStatus.textContent = "Game reset"; |
|
nesStatus.className = 'status-message'; |
|
|
|
|
|
nesInterval = setInterval(() => { |
|
nes.frame(); |
|
}, 1000 / 60); |
|
}); |
|
|
|
|
|
let gba = null; |
|
let gbaRom = null; |
|
|
|
|
|
const gbaFileInput = document.getElementById('gbaFileInput'); |
|
const gbaLoadBtn = document.getElementById('gbaLoad'); |
|
|
|
gbaLoadBtn.addEventListener('click', () => { |
|
gbaFileInput.click(); |
|
}); |
|
|
|
gbaFileInput.addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (!file) return; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
const rom = new Uint8Array(e.target.result); |
|
gbaRom = rom; |
|
|
|
|
|
if (!gba) { |
|
gba = new GameBoyAdvance(); |
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
canvas.width = 240; |
|
canvas.height = 160; |
|
const context = canvas.getContext('2d'); |
|
document.getElementById('gbaEmulator').innerHTML = ''; |
|
document.getElementById('gbaEmulator').appendChild(canvas); |
|
|
|
gba.setCanvas(canvas); |
|
|
|
|
|
|
|
|
|
|
|
gba.setInterval(() => { |
|
gba.runStable(); |
|
}, 0); |
|
} |
|
|
|
|
|
gba.loadRom(rom); |
|
|
|
|
|
document.getElementById('gbaGameInfo').innerHTML = ` |
|
<p class="mb-2"><span class="text-purple-400">${file.name}</span></p> |
|
<p class="mb-2">Controls:</p> |
|
<ul class="list-disc pl-5"> |
|
<li>Arrow Keys: D-Pad</li> |
|
<li>A: A Button</li> |
|
<li>S: B Button</li> |
|
<li>Enter: Start</li> |
|
<li>Shift: Select</li> |
|
<li>Q: L Button</li> |
|
<li>W: R Button</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Press Save State to save your progress</p> |
|
`; |
|
}; |
|
reader.readAsArrayBuffer(file); |
|
}); |
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
if (!gba || gbaSection.classList.contains('hidden')) return; |
|
|
|
|
|
|
|
|
|
switch(e.keyCode) { |
|
case 38: break; |
|
case 40: break; |
|
case 37: break; |
|
case 39: break; |
|
case 65: break; |
|
case 83: break; |
|
case 13: break; |
|
case 16: break; |
|
case 81: break; |
|
case 87: break; |
|
} |
|
}); |
|
|
|
|
|
let gbaState = null; |
|
|
|
document.getElementById('gbaSaveState').addEventListener('click', () => { |
|
if (!gba) { |
|
alert('No game loaded!'); |
|
return; |
|
} |
|
|
|
|
|
|
|
alert('Game state saved! (Placeholder - would save state in real implementation)'); |
|
}); |
|
|
|
document.getElementById('gbaLoadState').addEventListener('click', () => { |
|
if (!gbaState) { |
|
alert('No saved state found!'); |
|
return; |
|
} |
|
|
|
|
|
|
|
alert('Game state loaded! (Placeholder - would load state in real implementation)'); |
|
}); |
|
|
|
|
|
document.getElementById('gbaFullscreen').addEventListener('click', () => { |
|
const emulator = document.getElementById('gbaEmulator'); |
|
if (emulator.requestFullscreen) { |
|
emulator.requestFullscreen(); |
|
} else if (emulator.webkitRequestFullscreen) { |
|
emulator.webkitRequestFullscreen(); |
|
} else if (emulator.msRequestFullscreen) { |
|
emulator.msRequestFullscreen(); |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('gbaReset').addEventListener('click', () => { |
|
if (!gba || !gbaRom) { |
|
alert('No game loaded!'); |
|
return; |
|
} |
|
|
|
gba.loadRom(gbaRom); |
|
}); |
|
|
|
|
|
window.addEventListener("gamepadconnected", (e) => { |
|
document.getElementById('gamepadInfo').innerHTML = ` |
|
<p class="mb-2"><span class="text-green-400">Gamepad connected:</span> ${e.gamepad.id}</p> |
|
<p class="mb-2">Standard mapping:</p> |
|
<ul class="list-disc pl-5"> |
|
<li>D-Pad: D-Pad</li> |
|
<li>A Button: A</li> |
|
<li>B Button: B</li> |
|
<li>Start: Start</li> |
|
<li>Select: Select</li> |
|
<li>Shoulder Buttons: L/R</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Press buttons to control the game</p> |
|
`; |
|
|
|
|
|
if (!gamepadPolling) { |
|
gamepadPolling = true; |
|
pollGamepads(); |
|
} |
|
}); |
|
|
|
window.addEventListener("gamepaddisconnected", (e) => { |
|
document.getElementById('gamepadInfo').innerHTML = ` |
|
<p class="mb-4">Connect a gamepad and press any button to enable.</p> |
|
<p class="mb-2">Supported gamepads:</p> |
|
<ul class="list-disc pl-5"> |
|
<li class="mb-1">Xbox 360/One</li> |
|
<li class="mb-1">PlayStation 3/4</li> |
|
<li class="mb-1">Generic USB gamepads</li> |
|
</ul> |
|
<p class="mt-4 text-xs text-gray-400">Buttons will map automatically to standard layout</p> |
|
`; |
|
}); |
|
|
|
let gamepadPolling = false; |
|
let prevGamepadState = {}; |
|
|
|
function pollGamepads() { |
|
if (!gamepadPolling) return; |
|
|
|
const gamepads = navigator.getGamepads ? navigator.getGamepads() : []; |
|
|
|
for (let i = 0; i < gamepads.length; i++) { |
|
const gamepad = gamepads[i]; |
|
if (!gamepad) continue; |
|
|
|
|
|
if (!nesSection.classList.contains('hidden')) { |
|
|
|
if (gamepad.axes[1] < -0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_UP); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_UP); |
|
|
|
if (gamepad.axes[1] > 0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_DOWN); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_DOWN); |
|
|
|
if (gamepad.axes[0] < -0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_LEFT); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_LEFT); |
|
|
|
if (gamepad.axes[0] > 0.5) nes.buttonDown(1, jsnes.Controller.BUTTON_RIGHT); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_RIGHT); |
|
|
|
|
|
if (gamepad.buttons[0] && gamepad.buttons[0].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_A); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_A); |
|
|
|
if (gamepad.buttons[1] && gamepad.buttons[1].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_B); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_B); |
|
|
|
if (gamepad.buttons[9] && gamepad.buttons[9].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_START); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_START); |
|
|
|
if (gamepad.buttons[8] && gamepad.buttons[8].pressed) nes.buttonDown(1, jsnes.Controller.BUTTON_SELECT); |
|
else nes.buttonUp(1, jsnes.Controller.BUTTON_SELECT); |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
requestAnimationFrame(pollGamepads); |
|
} |
|
|
|
|
|
document.getElementById('remapControls').addEventListener('click', () => { |
|
alert('Key remapping feature coming soon!'); |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=pijou/emulator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |