<!DOCTYPE html>
<html>

<head>

    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.2" />
    <meta name="title" content="gba.ninja - Gameboy Advance emulator in the browser" />
    <meta name="description"
        content="Run Gameboy Advance games in a web browser. Based on the VisualBoyAdvance-M emulator." />

    <title>gba.ninja</title>

    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script>function gtag() { }</script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag("js", new Date());
        gtag("config", "UA-45495852-6");
    </script>

    <script>
        if (navigator.serviceWorker) {
            navigator.serviceWorker.register("./sw.js", { scope: "./", });
            navigator.serviceWorker.addEventListener("message", event => {
                var msg = event.data.msg;
                switch (msg.name) {
                    default:
                        console.log("unknown sw message", event);
                        gtag("event", "unknown_sw_message_1", {
                            name: msg.name,
                        });
                        return;
                }
            });
        }
    </script>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0px;
            font-family: sans-serif;
            background-color: #f3ddff;
        }

        canvas {
            right: 0px;
            left: 0px;
            bottom: 0px;
            top: 0px;
            position: absolute;
            width: 100%;
            height: 100%;
        }
    </style>


    <script id="2d-vertex-shader" type="x-shader/x-vertex">
            attribute vec2 a_position;
            varying highp vec2 v_textureCoord;

            void main() {
                /* 
                 * This scales the quad so that the screen texture fits the viewport.
                 * The texture is 256 * 256, but only 240 * 160 is used. The quad is 2*2, centered on (0,0)
                 */
                gl_Position = vec4((a_position.x * 2.0 * 1.0666) - 1.0, (a_position.y * 2.0 * 1.6) * -1.0 + 1.0, 0, 1);
                v_textureCoord = vec2(a_position.x, a_position.y);
            }
        </script>

    <script id="2d-fragment-shader" type="x-shader/x-fragment">
            varying highp vec2 v_textureCoord;

            uniform sampler2D u_sampler;

            void main(void) {
                gl_FragColor = texture2D(u_sampler, vec2(v_textureCoord.s, v_textureCoord.t));
            }
        </script>

    <script>
        window.onerror = function (messageOrEvent, source, lineno, colno, error) {
            try {
                var str = "";
                if (typeof messageOrEvent === "object") {
                    str += "Event: " + messageOrEvent.type + " " + messageOrEvent.message + " ;";
                } else {
                    str += messageOrEvent + "; ";
                }
                if (source) {
                    str += " Source: " + source + "; ";
                }
                if (lineno !== void 0) {
                    str += " Line: " + lineno + "; ";
                }
                if (colno !== void 0) {
                    str += " Col: " + colno + "; ";
                }
                if (error) {
                    str += " Message: " + error.message + "; ";
                    try {
                        str += " StackTop: " + error.stack.split(/\n/g)[1].trim() + "; ";
                    } catch (e) { }
                }
                console.log("Remote logged: ", str);
                gtag("event", "exception", {
                    description: str,
                });
            } catch (e) {
                console.error(e);
            }
        };
    </script>

    <script>
        "use strict";

        try {
            void new Image("/logo.png");
        } catch (e) {
            // Not sure why but on some browsers this crashes.
        }


        var qs = {
            autorun: "./thepurplenight.gba", exclusive: true,
        };


        function escapeHtml(string) {
            var entityMap = {
                "&": "&amp;",
                "<": "&lt;",
                ">": "&gt;",
                '"': '&quot;',
                "'": '&#039;',
            };
            return string.replace(/[&<>"']/g, function (s) {
                return entityMap[s] || s;
            });
        };
        function unescapeHtml(string) {
            var reverseEntityMap = {
                "&amp;": "&",
                "&lt;": "<",
                "&gt;": ">",
                '&quot;': '"',
                '&#039;': "'",
            };
            return string.replace(/&.+?;/g, function (s) {
                return reverseEntityMap[s] || s;
            });
        };

        // Disable backspace navigation
        var backspaceHandler = function (e) {
            if (e.which === 8) {
                if (!/INPUT|SELECT|TEXTAREA/i.test(e.target.tagName) || e.target.disabled || e.target.readOnly) {
                    e.preventDefault();
                }
            }
        }.bind(this);
        document.addEventListener("keydown", backspaceHandler);
        document.addEventListener("keypress", backspaceHandler);


        // Shim performance.now
        window.performance = window.performance || {};
        performance.now = (function () {
            return performance.now ||
                performance.mozNow ||
                performance.msNow ||
                performance.oNow ||
                performance.webkitNow ||
                function () {
                    return new Date().getTime();
                };
        })();


        // Shim localstorage
        if (!window.localStorage) {
            window.localStorage = {};
        }

        try {
            localStorage._ = 1;
        } catch (e) {
            window.isShittyLocalstorage = true;
        }


        var readyHandlers = {};
        var onReady = function (what, fn) {
            if (readyHandlers[what] === null) {
                setTimeout(function () { fn(); });
                return;
            }
            if (readyHandlers[what] === void 0) {
                readyHandlers[what] = [];
            }
            readyHandlers[what].push(fn);
        };
        var triggerReady = function (what) {
            if (readyHandlers[what]) {
                readyHandlers[what].forEach(function (v) {
                    v();
                });
            }
            readyHandlers[what] = null;
        };


        function isPowerOf2(x) {
            return (x != 0) && ((x & (x - 1)) == 0);
        }

        var modalEls;
        var modalRefcount = 0;
        function modal(text, options) {
            modalRefcount++;

            modalEls = modalEls || {
                modal: document.querySelector(".modal"),
                modalTitle: document.querySelector(".modal-title"),
                modalTitleText: document.querySelector(".modal-title").childNodes[0],
                modalText: document.querySelector(".modal-text"),
                modalTextText: document.querySelector(".modal-text").childNodes[0],
                modalLeftButton: document.querySelector(".modal-button-left"),
                modalLeftButtonText: document.querySelector(".modal-button-left").childNodes[0],
                modalRightButton: document.querySelector(".modal-button-right"),
                modalRightButtonText: document.querySelector(".modal-button-right").childNodes[0],
                modalProgress: document.querySelector(".modal-progress"),
                modalInput: document.querySelector(".modal-input"),
            };

            var removeEvents;
            function hideModal() {
                modalRefcount--;
                if (modalRefcount <= 0) {
                    modalRefcount = 0;
                    document.body.style.overflow = "";
                }
                modalEls.modal.style.display = "none";
                removeEvents();
            }

            function setProgress(n) {
                modalEls.modalProgress.style.display = "block";
                modalEls.modalProgress.style.width = n + "%";
            }

            function getInputValue() {
                return modalEls.modalInput.value;
            }

            options = options || {};
            options = {
                title: options.title || null,
                text: text,
                leftButtonText: options.leftButtonText || "OK",
                leftButtonFn: options.hasOwnProperty("leftButtonFn") ?
                    options.leftButtonFn : function () { },
                rightButtonText: options.rightButtonText || "OK",
                rightButtonFn: options.rightButtonFn || null,
                input: typeof options.input === "string" ? options.input : null,
            };

            modalEls.modal.style.display = "block";
            document.body.style.overflow = "hidden";
            window.scrollTo(0, 0);
            if (options.title) {
                modalEls.modalTitle.style.display = "block";
                modalEls.modalTitleText.textContent = options.title;
            } else {
                modalEls.modalTitle.style.display = "none";
            }
            modalEls.modalTextText.textContent = options.text;
            modalEls.modalLeftButtonText.textContent = options.leftButtonText;
            modalEls.modalRightButtonText.textContent = options.rightButtonText;
            if (options.leftButtonFn) {
                modalEls.modalLeftButton.style.display = "";
            } else {
                modalEls.modalLeftButton.style.display = "none";
            }
            if (options.rightButtonFn) {
                modalEls.modalRightButton.style.display = "";
            } else {
                modalEls.modalRightButton.style.display = "none";
            }
            modalEls.modalProgress.style.display = "none";
            modalEls.modalInput.value = "";
            if (typeof options.input === "string") {
                modalEls.modalInput.style.display = "";
            } else {
                modalEls.modalInput.style.display = "none";
            }

            var leftHandler = function () {
                if (!options.leftButtonFn || options.leftButtonFn() !== false) {
                    hideModal();
                }
            };
            modalEls.modalLeftButton.addEventListener("click", leftHandler);

            var rightHandler = function () {
                if (!options.rightButtonFn || options.rightButtonFn() !== false) {
                    hideModal();
                }
            };
            modalEls.modalRightButton.addEventListener("click", rightHandler);

            removeEvents = function () {
                modalEls.modalLeftButton.removeEventListener("click", leftHandler);
                modalEls.modalRightButton.removeEventListener("click", rightHandler);
            };

            return {
                hideModal: hideModal,
                setProgress: setProgress,
                getInputValue: getInputValue,
            };
        }

        function stringToCharCodes(str) {
            return str.split("").map(function (c) {
                return c.charCodeAt(0);
            });
        }

        var emuReady = false;
        function hasEmuModule() {
            return !!emuReady;
        }


        var romBuffer8 = null;
        window.loadRomFromBuffer = function (_romBuffer8, filename) {
            var errorOpts = { title: "Error" };

            if (_romBuffer8.length < 512) {
                gtag("event", "load_tiny_rom_1", {
                    event_label: filename,
                });
                return modal("That file isn't a GBA ROM. (It's too small to be a ROM.)", errorOpts);
            }

            // Check if it's a real rom
            var romCode = String.fromCharCode(
                _romBuffer8[0xAC], _romBuffer8[0xAD], _romBuffer8[0xAE], _romBuffer8[0xAF]
            );
            var gbMagic = [
                _romBuffer8[0x0104], _romBuffer8[0x0105], _romBuffer8[0x0106], _romBuffer8[0x0107],
                _romBuffer8[0x0108], _romBuffer8[0x0109], _romBuffer8[0x010A], _romBuffer8[0x010B],
            ].map(function (v) {
                return v.toString(16);
            }).join();

            if (filename.search(/\.zip$/i) !== -1) {
                gtag("event", "load_zip_rom_1", {
                    event_label: filename,
                });
                return modal("You need to extract the rom file from the zip.", errorOpts);
            }

            if (String.fromCharCode(_romBuffer8[0], _romBuffer8[1]) === "PK") {
                gtag("event", "load_zip_rom_1", {
                    event_label: filename + " (non-dot-zip-file)",
                });
                return modal("You need to extract the rom file.", errorOpts);
            }

            if (filename.search(/\.sav$/i) !== -1) {
                gtag("event", "load_sav_rom_1", {
                    event_label: filename,
                });
                return modal("That's not a ROM, it's a savegame file. GBA ROM files usually end in '.gba'.", errorOpts);
            }

            if (filename.search(/\.smc$/i) !== -1 || filename.search(/\.sfc$/i) !== -1) {
                gtag("event", "load_smc_rom_1", {
                    event_label: filename,
                });
                return modal("That's a SNES ROM, this emulator runs Gameboy Advance ROMs.", errorOpts);
            }

            if (gbMagic === "ce,ed,66,66,cc,d,0,b") {
                gtag("event", "load_gb_rom_1", {
                    event_label: filename,
                });
                var colorMaybe = "";
                if (filename.search(/\.gbc$/i) !== -1) {
                    colorMaybe = "Color ";
                }
                return modal("That's a Gameboy " + colorMaybe + "ROM, this emulator only runs Gameboy Advance ROMs.", errorOpts);
            }

            if (!isPowerOf2(_romBuffer8.length)) {
                // Some roms are actually non-pot, so don't enforce this.
                gtag("event", "non_pot_rom_1", {
                    event_label: stringToCharCodes(romCode) + " " + filename + " " + _romBuffer8.length,
                });
                // Don't return
            }

            function ok() {
                romBuffer8 = _romBuffer8;
                triggerReady("cartridge");
            }

            function waitForEmuLoad() {
                if (hasEmuModule()) {
                    ok();
                } else {
                    gtag("event", "emu_file_missing_at_load_rom_1", {});
                    var interval;
                    var modalOpts = modal("The emulator module isn't loaded yet. Give it a moment.", {
                        title: "Waiting For Emulator Module",
                        leftButtonText: "Back",
                        leftButtonFn: function () {
                            clearInterval(interval);
                        },
                    });
                    function progress() {
                        if (window.emuScriptProgress === -1) {
                            modalOpts.hideModal();
                            return modal("There was an error while loading the emulator module. You'll need to refresh the page.", {
                                title: "Error",
                                leftButtonText: "Ok",
                                leftButtonFn: function () {
                                    clearInterval(interval);
                                },
                            });
                        } else {
                            modalOpts.setProgress(window.emuScriptProgress);
                        }
                    }

                    progress();
                    interval = setInterval(function () {
                        progress();
                        if (window.emuScriptProgress >= 100) {
                            clearInterval(interval);
                            modalOpts.hideModal();
                            ok();
                        }
                    }, 100);
                }
            }

            if (romCode.search(/^[A-Z0-9]{4}$/) && !qs.ignoreInvalidRomCode) {
                gtag("event", "invalid_rom_code_1", {
                    event_label: stringToCharCodes(romCode) + " " + filename,
                });
                return modal("That file doesn't look like a GBA ROM. (Couldn't find a rom code in the file.)", {
                    title: "Error",
                    rightButtonText: "Run it anyway",
                    rightButtonFn: waitForEmuLoad,
                });
            } else {
                waitForEmuLoad();
            }
        };

        window.loadRomFromFile = function (e) {
            var binaryFile = e.currentTarget.files[0];
            var filename = binaryFile.name;
            e.currentTarget.form.reset();
            if (!binaryFile) {
                return;
            }

            var modalOpts = modal(filename, {
                title: "Loading File",
                leftButtonFn: null,
            });

            var fr = new FileReader();
            fr.readAsArrayBuffer(binaryFile);
            fr.onload = function () {
                modalOpts.hideModal();
                var _romBuffer8 = new Uint8Array(fr.result);
                loadRomFromBuffer(_romBuffer8, binaryFile.name);
            };
        };

        window.loadRomFromNetwork = function (url) {
            var xhr = new XMLHttpRequest();

            let loadingModalSettings = {
                title: "Loading",
                leftButtonText: "Cancel",
                leftButtonFn: function () {
                    xhr.abort();
                }
            };
            if (qs.exclusive) {
                loadingModalSettings.leftButtonFn = null;
            }
            var modalOpts = modal("Loading " + url, loadingModalSettings);

            modalOpts.setProgress(0);

            xhr.onload = function (e) {
                modalOpts.hideModal();
                window.loadRomFromBuffer(new Uint8Array(xhr.response), url);
            };
            xhr.onprogress = function (e) {
                modalOpts.setProgress((e.loaded / e.total) * 100);
            };
            xhr.onerror = function (e) {
                modalOpts.hideModal();
                let errorModalSettings = {
                    title: "Error",
                    leftButtonText: "Ok",
                };
                if (qs.exclusive) {
                    errorModalSettings.leftButtonFn = function () {
                        location.reload();
                    }
                    errorModalSettings.leftButtonText = "Reload Page";
                }
                modal("There was an error loading the ROM.", errorModalSettings);
            };
            xhr.open("GET", url);
            xhr.responseType = "arraybuffer";
            xhr.send();
        };



        window.gbaninja = {
            onRuntimeInitialized: function () {
                triggerReady("emu");
            },
        };


        document.addEventListener("DOMContentLoaded", function () {
            triggerReady("document");
        });


        document.addEventListener("mousedown", function () {
            if (window.vbaSound.audioCtx.state === "suspended") {
                window.vbaSound.audioCtx.resume();
            }
        });

        onReady("document", function () {
            if (window.init) {
                window.init();
            } else {
                document.querySelector(".pixels").innerHTML = "<p style='margin: 20px;'>A required file failed to load.</p>";
            }
            if (qs.autorun) {
                loadRomFromNetwork(qs.autorun);
            }
        });

        onReady("emu", function () {
            emuReady = true;
            onReady("document", function () {
                onReady("cartridge", function () {
                    window.start();
                });
            });
        });






        // ------ VBA ENTRY POINTS -------

        var VBAInterface = {};

        VBAInterface.VBA_get_emulating = function () {
            return gbaninja.ccall("VBA_get_emulating", "int", [], []);
        };

        VBAInterface.VBA_start = function () {
            return gbaninja.ccall("VBA_start", "int", [], []);
        };

        VBAInterface.VBA_do_cycles = function (cycles) {
            return gbaninja.ccall("VBA_do_cycles", "int", ["int"], [cycles]);
        };

        VBAInterface.VBA_stop = function () {
            return gbaninja.ccall("VBA_stop", "int", [], []);
        };

        VBAInterface.VBA_get_bios = function () {
            return gbaninja.ccall("VBA_get_bios", "int", [], []);
        };

        VBAInterface.VBA_get_rom = function () {
            return gbaninja.ccall("VBA_get_rom", "int", [], []);
        };

        VBAInterface.VBA_get_internalRAM = function () {
            return gbaninja.ccall("VBA_get_internalRAM", "int", [], []);
        };

        VBAInterface.VBA_get_workRAM = function () {
            return gbaninja.ccall("VBA_get_workRAM", "int", [], []);
        };

        VBAInterface.VBA_get_paletteRAM = function () {
            return gbaninja.ccall("VBA_get_paletteRAM", "int", [], []);
        };

        VBAInterface.VBA_get_vram = function () {
            return gbaninja.ccall("VBA_get_vram", "int", [], []);
        };

        VBAInterface.VBA_get_pix = function () {
            return gbaninja.ccall("VBA_get_pix", "int", [], []);
        };

        VBAInterface.VBA_get_oam = function () {
            return gbaninja.ccall("VBA_get_oam", "int", [], []);
        };

        VBAInterface.VBA_get_ioMem = function () {
            return gbaninja.ccall("VBA_get_ioMem", "int", [], []);
        };

        VBAInterface.VBA_get_systemColorMap16 = function () {
            return gbaninja.ccall("VBA_get_systemColorMap16", "int", [], []);
        };

        VBAInterface.VBA_get_systemColorMap32 = function () {
            return gbaninja.ccall("VBA_get_systemColorMap32", "int", [], []);
        };

        VBAInterface.VBA_get_systemFrameSkip = function () {
            return gbaninja.ccall("VBA_get_systemFrameSkip", "int", [], []);
        };

        VBAInterface.VBA_set_systemFrameSkip = function (n) {
            return gbaninja.ccall("VBA_set_systemFrameSkip", "int", ["int"], [n]);
        };

        VBAInterface.VBA_get_systemSaveUpdateCounter = function () {
            return gbaninja.ccall("VBA_get_systemSaveUpdateCounter", "int", [], []);
        };

        VBAInterface.VBA_reset_systemSaveUpdateCounter = function () {
            return gbaninja.ccall("VBA_reset_systemSaveUpdateCounter", "int", [], []);
        };

        VBAInterface.VBA_emuWriteBattery = function () {
            return gbaninja.ccall("VBA_emuWriteBattery", "int", [], []);
        };

        VBAInterface.VBA_agbPrintFlush = function () {
            return gbaninja.ccall("VBA_agbPrintFlush", "int", [], []);
        };




        // ------- VBA EXIT POINTS --------

        VBAInterface.NYI = function (feature) {
            console.log("Feature is NYI: ", feature);
        };

        VBAInterface.getAudioSampleRate = function () {
            return window.vbaSound.getSampleRate();
        };

        VBAInterface.getRomSize = function (startPointer8) {
            return romBuffer8.byteLength;
        };

        VBAInterface.copyRomToMemory = function (startPointer8) {
            var gbaHeap8 = gbaninja.HEAP8;
            var byteLength = romBuffer8.byteLength;
            for (var i = 0; i < byteLength; i++) {
                gbaHeap8[startPointer8 + i] = romBuffer8[i];
            }
        };

        VBAInterface.renderFrame = function (pixPointer8) {
            window.vbaGraphics.drawGBAFrame(pixPointer8);
        };

        VBAInterface.initSound = function () {
        };

        VBAInterface.pauseSound = function () {
        };

        VBAInterface.resetSound = function () {
            window.vbaSound.resetSound();
        };

        VBAInterface.resumeSound = function () {
        };

        VBAInterface.writeSound = function (pointer8, length16) {
            return window.vbaSound.writeSound(pointer8, length16);
        };

        VBAInterface.setThrottleSound = function (pointer8, length16) {
        };

        VBAInterface.getSaveSize = function () {
            return vbaSaves.getSaveSize();
        };

        VBAInterface.commitFlash = VBAInterface.commitEeprom = function (pointer8, size) {
            return vbaSaves.softCommit(pointer8, size);
        };

        VBAInterface.restoreSaveMemory = function (pointer8, targetBufferSize) {
            return vbaSaves.restoreSaveMemory(pointer8, targetBufferSize);
        };

        VBAInterface.getJoypad = function (joypadNum) {
            return vbaInput.getJoypad(joypadNum);
        };

        VBAInterface.dbgOutput = function (textPointer8, unknownPointer8) {
            return console.log("dbgOutput", textPointer8, unknownPointer8);
        };



        function ajaxScript(url, progressCallback) {
            var xhr = new XMLHttpRequest();
            xhr.onload = function (e) {
                var script = document.createElement('script');
                script.text = xhr.responseText;
                document.head.appendChild(script);
            };
            xhr.onprogress = function (e) {
                progressCallback((e.loaded / e.total) * 100);
            };
            xhr.onerror = function () {
                progressCallback(-1);
                gtag("event", "emu_load_error", {});
            };
            xhr.open("GET", url);
            xhr.responseType = "text";
            xhr.send();
        }

        window.emuScriptProgress = 0;
        ajaxScript("./emu.js", function (progress, text) {
            window.emuScriptProgress = progress;
        });




    </script>
    <script src="./app.js"></script>
</head>

<body>

    <style>
        .ui {
            color: #4d2990;
        }

        .ui-border-1 {
            float: left;
            border: transparent 20px solid;
        }

        .ui-border-2 {
            padding: 20px;
            border: 13px solid #aa17fe;
            background-color: white;
            float: left;
            border-left-width: 5px;
            border-right-width: 5px;
            position: relative;
        }

        section {
            margin: 23px 0px;
        }

        h2 {
            margin: 0;
            font-size: 14px;
            font-style: italic;
            text-decoration: underline;
        }

        table {
            margin: 0;
        }

        p,
        label {
            margin: 0;
            font-size: 14px;
        }

        .gap {
            margin-top: 10px;
        }

        input[type=file] {
            position: absolute;
            left: -1000000px;
        }

        #load-rom-from-url {
            height: 23px;
            border-radius: 3px;
            border: solid 2px black;
            margin-left: 16px;
            padding: 3px 13px;
        }

        .btn {
            display: inline-block;
            border: 1px #9b69b7 solid;
            border-width: 4px 2px;
            padding: 5px 13px;
            background-color: #3a3a3a;
            color: #ece6ff;
            font-weight: 600;
            font-size: 13px;
            min-width: 60px;
            text-align: center;
        }

        .btn:hover {
            cursor: pointer;
            border-color: #b09cbb;
            background-color: #7a7994;
        }

        .empty-table {
            font-size: 11px;
        }

        table {
            border-collapse: collapse;
            font-size: 11px;
        }

        td {
            padding: 3px 11px;
            border: 2px solid #aa17fe;
            border-top: none;
            border-bottom: none;
        }

        a {
            color: #a89be8;
        }

        .report-bug-button {
            font-size: 11px;
        }

        .modal {
            display: none;
        }

        .modal-background {
            position: absolute;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            opacity: 0.8;
            background-color: black;
        }

        .modal-body {
            width: 400px;
            margin: 0 auto;
            position: absolute;

            border: 10px solid #aa17fe;
            background-color: white;
            border-left-width: 3px;
            border-right-width: 3px;

            left: 50%;
            margin-left: -218px;
            /* =(400 + 2*15 + 2*3) / 2 */
            margin-top: 60px;
            padding: 15px;
        }

        .modal-title {
            font-size: 16px;
        }

        .modal-buttons {
            text-align: center;
        }

        .modal-button-left,
        .modal-button-right {
            margin: 6px 4px 3px 4px;
        }

        .modal-progress {
            height: 4px;
            margin-bottom: 10px;
            background-color: #9b69b7;
        }

        .modal-input {
            width: 100%;
            padding: 6px;
            box-sizing: border-box;
            margin: 10px 0;
            border: 2px solid #9b69b7;
            border-radius: 5px;
            font-size: 15px;
            color: #4d2990;
        }

        .perf {
            position: fixed;
            bottom: 0;
            color: white;
            padding: 4px;
            margin: 4px;
            background-color: rgba(0, 0, 0, 0.5);
        }

        .perf-left {
            display: inline-block;
            width: 200px;
        }
    </style>
    <div class="pixels" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0;"></div>
    <div style="display: none;" class="ui">
        <div class="ui-border-1">
            <div class="ui-border-2">
                <img src="./logo.png" style="height: 50px; padding-bottom: 6px; padding-right: 90px;" />
                <section class="load-rom-section">
                    <h2>Load a Gameboy Advance ROM</h2>
                    <div class="gap"></div>
                    <label class="btn" for="load-rom-from-file">From File</label>
                    <div class="gap"></div>
                    <form>
                        <input id="load-rom-from-file" type="file" onchange="window.loadRomFromFile(event);" />
                    </form>
                </section>
                <section class="paused-section" style="display: none;">
                    <h2>Paused</h2>
                    <p style="padding-top: 8px;">
                        Press <span class="unpause-key-prompt"></span> to resume.
                    </p>
                </section>
                <!--
                        <div>
                            <label class="btn" for="load-rom-from-url" onclick="window.loadCartridgeFromURL(event);window.onReady('cartridge', function () {start()});">From URL</label>
                            <input id="load-rom-from-url" type="text"/>
                        </div>
                    -->
                <section class="savegames-section">
                    <h2>Saves</h2>
                    <div class="gap"></div>
                    <div class="saves-list"></div>
                    <div class="gap"></div>
                    <label class="btn" for="import-save-file">Import Save File</label>
                    <form>
                        <input id="import-save-file" type="file"
                            onchange="vbaSaves.onFileImportInputChanged(event, window.vbaUI.reset.bind(vbaUI));" />
                    </form>
                </section>
                <section>
                    <h2>Keyboard Bindings</h2>
                    <div class="gap"></div>
                    <div class="keyboard-bindings"></div>
                    <div class="gap"></div>
                    <button class="btn reset-bindings-button" onclick="window.vbaUI.resetBindings();">Reset
                        Bindings</button>
                </section>
                <div style="position: absolute; right: 10px; bottom: 10px;">
                    <a class="report-bug-button" target="_blank"
                        href="https://github.com/simon-paris/gba.ninja/blob/master/embed.md">embed gba.ninja</a>
                    &nbsp;
                    <a class="report-bug-button" target="_blank"
                        href="https://github.com/simon-paris/gba.ninja/issues">report a bug</a>
                </div>
            </div>
        </div>
    </div>
    <div class="modal">
        <div class="modal-background"></div>
        <div class="modal-body">
            <h2 class="modal-title">Title</h2>
            <div class="gap"></div>
            <p class="modal-text">Text</p>
            <div class="gap"></div>
            <div class="modal-progress"></div>
            <input class="modal-input"></input>
            <div class="modal-buttons">
                <div class="btn modal-button-left">Left</div>
                <div class="btn modal-button-right">Right</div>
            </div>
        </div>
    </div>
    <div class="toast">

    </div>
    <div class="perf" style="display: none;">
        <span class="perf-left">Game</span><span class="perf-right perf-game">-</span><br />
        <span class="perf-left">Speed</span><span class="perf-right perf-percentage">-</span><br />
        <span class="perf-left">On-Time Renders</span><span class="perf-right perf-render-deadlines">-</span><br />
        <span class="perf-left">On-Time Audio Events</span><span class="perf-right perf-audio-deadlines">-</span><br />
        <span class="perf-left">Timesteps/Second</span><span class="perf-right perf-timesteps">-</span><br />
        <span class="perf-left">Audio Lag</span><span class="perf-right perf-audio-lag">-</span><br />
    </div>
</body>

<script>
    window.addEventListener("keydown", (e) => {
        if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
            e.preventDefault();
        }
    }, false);
</script>

</html>