-
- WASD
- D-Pad
-
-
- J
- B Button
-
-
- K
- A Button
-
-
- Enter
- Start
-
-
- Right Shift
- Select
-
-
-
diff --git a/web/main.js b/web/main.js
index 95cf1d9..b1156c1 100644
--- a/web/main.js
+++ b/web/main.js
@@ -17,9 +17,6 @@ Promise.all([wasmReady, documentReady]).then(async () => {
const TARGET_FPS = 60;
const SCALE = 2;
- const audioBufferSize = go.AudioBufferSize;
- const audioSampleRate = go.AudioSampleRate;
-
// ========================
// Canvas setup
// ========================
@@ -38,10 +35,10 @@ Promise.all([wasmReady, documentReady]).then(async () => {
// Audio setup
// ========================
+ const audioBufferSize = go.AudioBufferSize;
+ const audioSampleRate = go.AudioSampleRate;
console.log(`[INFO] audio sample rate: ${audioSampleRate}, buffer size: ${audioBufferSize}`);
- let audioCtx = new AudioContext({
- sampleRate: audioSampleRate,
- });
+ let audioCtx = new AudioContext({sampleRate: audioSampleRate});
await audioCtx.audioWorklet.addModule("audio.js");
let audioNode = new AudioWorkletNode(audioCtx, "audio-processor");
@@ -63,7 +60,6 @@ Promise.all([wasmReady, documentReady]).then(async () => {
}
}, {once: true});
-
// ========================
// Input handling
// ========================
@@ -104,6 +100,25 @@ Promise.all([wasmReady, documentReady]).then(async () => {
}
});
+ const elementKeyMap = {
+ "dpad-up": BUTTON_UP,
+ "dpad-down": BUTTON_DOWN,
+ "dpad-left": BUTTON_LEFT,
+ "dpad-right": BUTTON_RIGHT,
+ "button-start": BUTTON_START,
+ "button-select": BUTTON_SELECT,
+ "button-b": BUTTON_B,
+ "button-a": BUTTON_A,
+ };
+
+ for (let [id, mask] of Object.entries(elementKeyMap)) {
+ let element = document.getElementById(id);
+ element.addEventListener("mousedown", () => { buttonsPressed |= mask; });
+ element.addEventListener("touchstart", () => { buttonsPressed |= mask; });
+ element.addEventListener("mouseup", () => { buttonsPressed &= ~mask; });
+ element.addEventListener("touchend", () => { buttonsPressed &= ~mask; });
+ }
+
// ========================
// ROM loading
// ========================
@@ -132,6 +147,16 @@ Promise.all([wasmReady, documentReady]).then(async () => {
});
}
+ document.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ });
+
+ document.addEventListener('drop', (e) => {
+ e.preventDefault();
+ fileInput.files = e.dataTransfer.files;
+ fileInput.dispatchEvent(new Event('input'));
+ });
+
// ========================
// Game loop
// ========================
@@ -152,7 +177,7 @@ Promise.all([wasmReady, documentReady]).then(async () => {
let framePtr = go.GetFrameBufferPtr();
let image = new ImageData(new Uint8ClampedArray(getMemoryBuffer(), framePtr, WIDTH * HEIGHT * 4), WIDTH, HEIGHT);
ctx.putImageData(image, 0, 0);
- return
+ return;
}
let audioBufPtr = go.GetAudioBufferPtr();
@@ -165,14 +190,14 @@ Promise.all([wasmReady, documentReady]).then(async () => {
const frameTime = 1000 / TARGET_FPS;
function loop() {
- requestAnimationFrame(loop)
+ requestAnimationFrame(loop);
- const now = performance.now()
- const elapsed = now - lastFrameTime
- if (elapsed < frameTime) return
+ const now = performance.now();
+ const elapsed = now - lastFrameTime;
+ if (elapsed < frameTime) return;
- const excessTime = elapsed % frameTime
- lastFrameTime = now - excessTime
+ const excessTime = elapsed % frameTime;
+ lastFrameTime = now - excessTime;
if (isInFocus()) {
executeFrame();
diff --git a/web/style.css b/web/style.css
index e63b7c3..bee821e 100644
--- a/web/style.css
+++ b/web/style.css
@@ -11,142 +11,264 @@ body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
-.container {
- background-color: #ffffff;
- width: 512px;
+.console {
border-radius: 12px;
+ background: #959595;
+ padding: 20px;
+}
+
+.console__screen {
overflow: hidden;
- box-shadow: 0 10px 50px rgba(0, 0, 0, 0.3);
+ border-radius: 10px;
+ background: #101010;
+ margin-bottom: 15px;
+}
+
+.console__controls {
+ /*margin-bottom: 10px;*/
+}
+
+.console__rom {
+ margin-bottom: 15px;
+}
+
+.screen {
+ position: relative;
+}
+
+.screen__canvas {
+ display: block;
}
-.header {
- background-color: #e53e3e;
+.screen__unmute {
+ position: absolute;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5px);
+ border-radius: 20px;
+ padding: 10px;
color: white;
- padding: 15px;
- text-align: center;
+ left: 50%;
+ transform: translateX(-50%);
+ bottom: 40px;
+ font-size: 1.1em;
+ white-space: nowrap;
+}
+
+/* Controls styles are inspired by
+ https://codepen.io/injectilo/pen/MYJrmm */
+
+.controls {
+ padding: 30px;
+ background: #686868;
+ display: flex;
position: relative;
+ flex-direction: row;
+ justify-content: space-between;
+ border: 1px solid #4e4e4e;
+ border-radius: 10px;
}
-.header h1 {
- margin: 0;
- font-size: 1.5em;
- letter-spacing: 1px;
+.controls__dpad {
+ position: relative;
}
-.game-section {
+.controls__center {
+ margin-top: 60px;
+}
+
+.controls__ab {
+ margin-top: 50px;
+}
+
+.dpad {
position: relative;
- background: #000;
+ height: 110px;
+ width: 110px;
+ color: #000000;
+}
+
+.dpad__horizontal {
+ position: absolute;
width: 100%;
+ height: 38px;
+ border-radius: 4px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: #252725;
+ z-index: 1;
}
-.game-window {
- height: 480px;
- border-bottom: 1px solid #2d3748;
+.dpad__vertical {
+ position: absolute;
+ width: 38px;
+ height: 100%;
+ left: 50%;
+ border-radius: 4px;
+ transform: translateX(-50%);
+ background: #252725;
+ z-index: 2;
}
-.game-window canvas {
- display: block;
+.dpad__button {
+ color: rgba(255, 255, 255, 0.6);
+ font-weight: bold;
+ position: absolute;
+ text-align: center;
+ line-height: 35px;
+ cursor: pointer;
+ user-select: none;
+ width: 38px;
+ height: 38px;
+ z-index: 1;
}
-.upload-section {
- padding: 20px;
+.dpad__button.-left {
+ box-shadow: inset 0 1px rgba(255, 255, 255, 0.5);
+ z-index: 0;
+ left: 0;
}
-.rom-upload {
- font-size: 0.9em;
- padding: 10px;
- display: flex;
- gap: 10px;
- align-items: center;
- border: 1px solid #edf2f7;
- border-radius: 8px;
- background: #f7fafc;
- margin-bottom: 20px;
+.dpad__button.-right {
+ box-shadow: inset 0 1px rgba(255, 255, 255, 0.5);
+ z-index: 0;
+ right: 0;
}
-.info-section {
- padding: 20px;
+.dpad__button.-up {
+ box-shadow: inset 0 1px rgba(255, 255, 255, 0.5) ;
+ z-index: 1;
+ top: 0;
}
-.controls-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
+.dpad__button.-down {
+ bottom: 0;
}
-.control-item {
- background: #f7fafc;
- padding: 10px;
- border-radius: 8px;
- text-align: center;
- border: 1px solid #edf2f7;
+.dpad__border {
+ position: absolute;
+ background: #edece7;
}
-.control-key {
- font-weight: bold;
- color: #e53e3e;
- display: block;
- margin-bottom: 4px;
+.dpad__border.-vert {
+ width: 46px;
+ top: -3px;
+ bottom: -3px;
+ z-index: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ border-radius: 4px;
}
-.control-action {
- font-size: 0.9em;
- color: #4a5568;
+.dpad__border.-horiz {
+ height: 46px;
+ left: -3px;
+ right: -3px;
+ z-index: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ border-radius: 4px;
}
-.project-info {
- background: #f7fafc;
- padding: 15px;
- border-radius: 8px;
- font-size: 0.9em;
- line-height: 1.6;
+.dpad__circle {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ margin: auto;
+ z-index: 3;
+ border-radius: 50%;
+ background: linear-gradient(180deg, rgb(22, 22, 22) 30%, rgb(58, 58, 58) 100%);
+ box-shadow: inset 0 -2px 0 0 rgba(255, 255, 255, 0.1);
}
-.project-info h2 {
- color: #e53e3e;
- margin: 0 0 10px 0;
- font-size: 1.2em;
+.center-buttons {
+ margin: auto;
+ width: 142px;
+ height: 40px;
+ border-radius: 10px;
+ background-color: #edece7;
+ border: 4px solid #e0ded4;
+ box-shadow: inset 0 0 2px 2px rgba(0, 0, 0, 0.3);
+ z-index: 3;
+ display: flex;
}
-.features {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 10px;
- margin-top: 10px;
+.center-buttons__button {
+ position: relative;
+ top: 3px;
+ width: 42px;
+ height: 18px;
+ cursor: pointer;
+ user-select: none;
+ background-color: #464646;
+ border-radius: 10px;
+ border: 1px solid #272723;
+ margin: 9px 13px;
+ box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.51);
+}
+
+.center-buttons__label {
+ color: rgba(255, 255, 255, 0.6);
+ position: absolute;
+ font-weight: bold;
+ text-align: center;
+ left: 50%;
+ transform: translateX(-50%);
+ top: -40px;
}
-.feature-item {
+.ab {
+ position: relative;
display: flex;
- align-items: center;
- gap: 8px;
}
-.feature-item::before {
- content: "•";
- color: #e53e3e;
+.ab__border {
+ position: relative;
+ border-radius: 4px;
+ background-color: #edece7;
+ margin: 6px;
+ padding: 6px;
}
-.source-link {
+.ab__button {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ float: left;
+ text-align: center;
+ line-height: 40px;
+ font-weight: bold;
+ cursor: pointer;
+ user-select: none;
+ color: rgba(255, 255, 255, 0.6);
+ border: 1px rgba(0, 0, 0, 0.51) solid;
+ background: linear-gradient(to bottom, #df2015 0%, #f84936 100%);
+ box-shadow: inset 0 1px 2px 0 #fbfbfb, 0 1px 1px 0 rgba(0, 0, 0, 0.71);
+}
+
+.rom-select {
+ border-radius: 12px;
+ border: 1px dashed #4e4e4e;
+ text-align: center;
+ cursor: pointer;
+ display: block;
+ padding: 15px;
+}
+
+.rom-select svg {
position: relative;
+ top: 3px;
+}
+
+.source-link {
+ padding: 15px 0;
text-align: center;
- margin-bottom: 20px;
font-size: 0.9em;
}
.source-link a {
- color: #4a5568;
+ color: #a9a9a9;
}
-
-.unmute {
- position: absolute;
- background: rgba(0, 0, 0, 0.5);
- backdrop-filter: blur(5px);
- border-radius: 20px;
- padding: 10px;
- color: white;
- left: 50%;
- transform: translateX(-50%);
- bottom: 40px;
- font-size: 1.1em;
- white-space: nowrap;
-}
\ No newline at end of file