diff --git a/CHANGELOG.md b/CHANGELOG.md
index fae2c540704e0658ad36668682172e4b5998c6d9..4ec17b2c38e3f31b7dc53c87b8c18d785c796444 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 
 * Issue related to STAT interrupt and H-Blank
+* Issue related to overflow in sprite drawing
 
 ## [0.3.0] - 2022-07-11
 
diff --git a/README.md b/README.md
index 007c7aae9d066c15e2d08dec70dd7d603dc8bbce..5462e74fae95515934303cb891a312f6bab9d023 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,16 @@
 
 A Game Boy emulator that is written in Rust 🦀.
 
+**This emulator has been written for educational purposes and shouldn't be taken to seriously.** But yeahh it plays games, which is cool... 🎮
+
+## Deployments
+
+| Provider  | Stable  | URL                                                              |
+| --------- | ------- | ---------------------------------------------------------------- |
+| Cloudfare | `True`  | [boytacean.pages.dev](https://boytacean.pages.dev)               |
+| Cloudfare | `True`  | [prod.boytacean.pages.dev](https://prod.boytacean.pages.dev)     |
+| Cloudfare | `False` | [master.boytacean.pages.dev](https://master.boytacean.pages.dev) |
+
 ## Build
 
 ### WASM for Node.js
diff --git a/examples/sdl/src/util.rs b/examples/sdl/src/util.rs
index 2f613a9def6b695b28513f082ddb3c58318333ad..a2adbb8cc3e15c503d081203bb6ae5b967bba174 100644
--- a/examples/sdl/src/util.rs
+++ b/examples/sdl/src/util.rs
@@ -3,6 +3,9 @@ use sdl2::{
     AudioSubsystem, EventPump, TimerSubsystem, VideoSubsystem,
 };
 
+/// Structure that provide the complete set of Graphics
+/// and Sound syb-system ready to be used by the overall
+/// emulator infrastructure.
 pub struct Graphics {
     pub canvas: Canvas<Window>,
     pub video_subsystem: VideoSubsystem,
diff --git a/examples/web/index.css b/examples/web/index.css
index 850d93f7baf978e698ca0ee83e8f34f6d0722cdd..eb47a643bd0915fff9d2c3982c59cd2b1de03a1a 100644
--- a/examples/web/index.css
+++ b/examples/web/index.css
@@ -308,108 +308,6 @@ p {
     -webkit-transform: scale(1.0, 1.0);
 }
 
-.tiny-button {
-    border-radius: 96px 96px 96px 96px;
-    -o-border-radius: 96px 96px 96px 96px;
-    -ms-border-radius: 96px 96px 96px 96px;
-    -moz-border-radius: 96px 96px 96px 96px;
-    -khtml-border-radius: 96px 96px 96px 96px;
-    -webkit-border-radius: 96px 96px 96px 96px;
-    cursor: pointer;
-    display: inline-block;
-    padding: 0px 8px 0px 8px;
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-.tiny-button.border {
-    border: 1px solid #ffffff;
-}
-
-.tiny-button.padded {
-    padding: 4px 10px 4px 10px;
-}
-
-.tiny-button.padded-large {
-    padding: 4px 14px 4px 14px;
-}
-
-.tiny-button.rounded {
-    padding: 6px 6px 6px 6px;
-}
-
-.tiny-button.enabled {
-    background-color: #50cb93;
-}
-
-.tiny-button.file {
-    position: relative;
-}
-
-.tiny-button:hover {
-    background-color: #50cb93;
-}
-
-.tiny-button.red:hover {
-    background-color: #e63946;
-}
-
-.tiny-button:active {
-    background-color: #2a9d8f;
-}
-
-.tiny-button.red:active {
-    background-color: #bf2a37;
-}
-
-.tiny-button > img {
-    margin-right: 6px;
-    margin-top: 2px;
-    vertical-align: top;
-    width: 13px;
-}
-
-.tiny-button > img.medium {
-    width: 20px;
-}
-
-.tiny-button > img.large {
-    width: 28px;
-}
-
-.tiny-button > img.very-large {
-    width: 38px;
-}
-
-.tiny-button.no-text > img {
-    margin-right: 0px;
-    margin-top: 0px;
-}
-
-.tiny-button.file > input[type="file"] {
-    cursor: pointer;
-    height: 100%;
-    left: 0px;
-    opacity: 0;
-    -o-opacity: 0;
-    -ms-opacity: 0;
-    -moz-opacity: 0;
-    -khtml-opacity: 0;
-    -webkit-opacity: 0;
-    position: absolute;
-    top: 0px;
-    vertical-align: top;
-    width: 100%;
-}
-
-.tiny-button.file > input[type="file"]::-webkit-file-upload-button {
-    cursor: pointer;
-}
-
 .overlay {
     align-items: center;
     background-color: rgba(80, 203, 147, 0.95);
@@ -575,12 +473,12 @@ p {
     -webkit-user-select: none;
 }
 
-.modal-container > .modal .modal-buttons > .tiny-button {
+.modal-container > .modal .modal-buttons > .button.simple {
     margin-right: 12px;
     min-width: 120px;
 }
 
-.modal-container > .modal .modal-buttons > .tiny-button:last-child {
+.modal-container > .modal .modal-buttons > .button.simple:last-child {
     margin-right: 0px;
 }
 
diff --git a/examples/web/index.html b/examples/web/index.html
index 3000b3a8a9e36d5376f86edaa2bd2b1334d4387c..a192760d3f63b79adf38a41e71517cf6c45ad721 100644
--- a/examples/web/index.html
+++ b/examples/web/index.html
@@ -13,6 +13,7 @@
 </head>
 
 <body>
+    <div id="app"></div>
     <div class="main">
         <div class="side-left">
             <div id="canvas-container" class="canvas-container">
@@ -76,16 +77,16 @@
             <div id="section-diag" class="section">
                 <dl class="diag">
                     <dt>Engine</dt>
-                    <dd id="engine" class="tiny-button">-</dd>
+                    <dd id="engine" class="button simple">-</dd>
                     <dt>ROM</dt>
                     <dd id="rom-name">-</dd>
                     <dt>ROM Size</dt>
                     <dd><span id="rom-size">-</span> bytes</dd>
                     <dt>CPU Frequency</dt>
                     <dd>
-                        <span id="logic-frequency-minus" class="tiny-button">-</span>
+                        <span id="logic-frequency-minus" class="button simple">-</span>
                         <span id="logic-frequency">-</span> Hz
-                        <span id="logic-frequency-plus" class="tiny-button">+</span></dd>
+                        <span id="logic-frequency-plus" class="button simple">+</span></dd>
                     <dt>Framerate</dt>
                     <dd><span id="fps-count">-</span> fps</dd>
                 </dl>
@@ -93,31 +94,31 @@
             <div id="separator-diag" class="separator"></div>
             <div class="section">
                 <div class="button-area">
-                    <span id="button-pause" class="tiny-button border padded">
+                    <span id="button-pause" class="button simple border padded">
                         <img src="res/pause.svg" alt="pause" /><span>Pause</span>
                     </span>
-                    <span id="button-reset" class="tiny-button border padded">
+                    <span id="button-reset" class="button simple border padded">
                         <img src="res/reset.svg" alt="reset" /><span>Reset</span>
                     </span>
-                    <span id="button-benchmark" class="tiny-button border padded">
+                    <span id="button-benchmark" class="button simple border padded">
                         <img src="res/bolt.svg" alt="bolt" /><span>Benchmark</span>
                     </span>
-                    <span id="button-fullscreen" class="tiny-button border padded">
+                    <span id="button-fullscreen" class="button simple border padded">
                         <img src="res/maximise.svg" alt="maximise" /><span>Fullscreen</span>
                     </span>
-                    <span id="button-keyboard" class="tiny-button border padded">
+                    <span id="button-keyboard" class="button simple border padded">
                         <img src="res/dialpad.svg" alt="info" /><span>Keyboard</span>
                     </span>
-                    <span id="button-information" class="tiny-button border padded enabled">
+                    <span id="button-information" class="button simple border padded enabled">
                         <img src="res/info.svg" alt="info" /><span>Information</span>
                     </span>
-                    <span id="button-debug" class="tiny-button border padded">
+                    <span id="button-debug" class="button simple border padded">
                         <img src="res/bug.svg" alt="bug" /><span>Debug</span>
                     </span>
-                    <span id="button-theme" class="tiny-button border padded">
+                    <span id="button-theme" class="button simple border padded">
                         <img src="res/marker.svg" alt="marker" /><span>Theme</span>
                     </span>
-                    <span id="button-upload" class="tiny-button border padded file">
+                    <span id="button-upload" class="button simple border padded file">
                         <img src="res/upload.svg" alt="upload" /><span>Load ROM</span>
                         <input type="file" id="button-upload-file" name="button-upload-file" accept=".gb">
                     </span>
@@ -132,15 +133,15 @@
 <div id="modal-container" class="modal-container">
     <div id="modal" class="modal">
         <div class="modal-top-buttons">
-            <span id="modal-close" class="tiny-button rounded no-text">
+            <span id="modal-close" class="button simple rounded no-text">
                 <img class="medium" src="res/close.svg" alt="close" />
             </span>
         </div>
         <h2 id="modal-title" class="modal-title"></h2>
         <p id="modal-text" class="modal-text"></p>
         <div class="modal-buttons">
-            <span id="modal-cancel" class="tiny-button red border padded-large">Cancel</span>
-            <span id="modal-confirm" class="tiny-button border padded-large">Confirm</span>
+            <span id="modal-cancel" class="button simple red border padded-large">Cancel</span>
+            <span id="modal-confirm" class="button simple border padded-large">Confirm</span>
         </div>
     </div>
 </div>
diff --git a/examples/web/index.ts b/examples/web/index.ts
index 3784a69fb6c5d5aa451a19da4cf8bb0e89be84ac..dc36af2ccd5d4a0329c69de529cb84c047c236b6 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -1,6 +1,10 @@
+import { startApp } from "./react/app";
+
 import { default as _wasm, GameBoy, PadKey, PpuMode } from "./lib/boytacean.js";
 import info from "./package.json";
 
+declare const require: any;
+
 const PIXEL_UNSET_COLOR = 0x1b1a17ff;
 
 const LOGIC_HZ = 600;
@@ -16,9 +20,6 @@ const DISPLAY_RATIO = DISPLAY_WIDTH / DISPLAY_HEIGHT;
 
 const SAMPLE_RATE = 2;
 
-const SOUND_DATA =
-    "data:audio/mpeg;base64,SUQzAwAAAAAAJlRQRTEAAAAcAAAAU291bmRKYXkuY29tIFNvdW5kIEVmZmVjdHMA//uSwAAAAAABLBQAAAL6QWkrN1ADDCBAACAQBAQECQD//2c7OmpoX/btmzIxt4R/7tmdKRqBVldEDICeA2szOT5E0ANLDoERvAwYDvXUwGPgUBhQVAiIAGFQb9toDBQAwSGwMLgECIPAUE/7v4YoAwyHQMSh8BgNl0r//5ofWmt///4swTaBg0CgSAgNoClQMSAwCgBAwiA//t9/GRFBlcXORYXAN8ZQggBgCACBH////4WYFjpmaRcLZcYggswUoBgEEgYPBf////////+VwfOBAwA7llUiIABQAAAgAAAEBgUARBzKEVmNPo26GUFGinz0RnZcAARtaVqlvTwGDx8BvHbgkEQMtcYIQgBjzkgaETYGFhuAEeRQ5m4ZcMEAsmKArYXE7qZFkXGOGkI5L4yqTIqRZNK45ociBkoKE6brSDUgMNi8mkJqHfAwaMBz11/t23+yEgox4FicKWLheWtJMWkAYIGpvvKwpgAQBJxVki+QFZOmhfJkQWCICACENuqdNB1Ba39WSI1wxkIsPSalHkFsZloPyHLBoEwssSa3Xf/7ksBnABz9nUn5qoACZTMov7FQAGsyLZRDwG7X+vJcfAjUzWVJMUz/DadX/DPVVPTwxgAAYggAShABbnnd5DQOPbj70zVpiaxayfheoOiDfgbrAYWXYHf90BlMZAYvDQUAYhKOIfxmTyebVJ71qsPaSBSPnR4NTPoOShOniyMyQEMSAScgXMjmnkkTJ71ob1q2rei1TUOy0Ss5w4QYIA0HbOG3Pf//3+j8i6LMiQ0CAFFXbU9Xf//+/mJHJOsyLwYXJ1mr16/1AJZ4ZlMAACAAADEFHpoLU2ytFsJ1sql3c1hG7r4LivRJ06AgAMwNgSDQUFJMGgAAOAXR8a+/8op8Ln/Z5+X/z+4/yc+vLe5V+QXz/52DO8uxhuYWBWA9SESgTZOJpmtaG2rbR2u29NqluNQrUjU4EoAfZG1SNfVX/928+3ccDzJEmgCCQc41Szj/V9S/r+o29Qn1qrhQY9Wg/rb/9fzku8RCoAABQAABKjQCK1VNcqoJHKmjjRanrzeKUiQHJyu63xb0wtDo+TRcFFkPAS68UpPuY2f+v/4/+///+5LAbIATtdU/7HqNwlm0aD2O0bDv9q3qS1nq12Z9yUSRRMBjQF4wHfMidi6aVlt2PVI7a6n11d7ashxpscCbQWBa2qP1tnq22q7VatDVj01aygAkcI0TXnHr1tX2/W+qrqmQ03rwUBNXnK7dvTeRh2VkYwAAKAAANmkNuUCQrNCopStlXHuCRUS6Xmb1FJdyyQKCxhEZZ3xiBiIE5ZJ45VZj9nK/39d7n/5////b0Sx1MW7zwd/89STW8J+EAoCwJcYM2OAvmjE5VzayGr+nvpash5arY4EJIBQOJrNaZL1tUtS9v9uqd08Zl2RSIaASHQ402MXko1etvr+632qPbKLI3F1YDQRecybarX+3qq+o+upVkRCAAAgAAAZGbDPFHmW35hRX4JfLKULFfuWuey1yVKB0FwsZRmlgZgIFCHdUjlw/BVq9h3Cxnzv4Y5659JYr7ortvLj4fn/eR6xq5K3oC4vgc9EKDIAQdSBMspPTXT3+m/tOp1oR0qQtBCwCiw3RPTpb+qvtV6mbzJqGMtZSBTAMIhsaBxUyNXV0GV0l//uSwJkAFGnXPex2rcKXuuf9jtG4L9f0z2nQFK1JqQAUDM681f7/Zf1e82WAioiGUwAAMAAAKBrafL7Ku+qidGFD4nVyacggTALkCEoYIANAGBgXCWBiVFyBp/PgBhGCEAMFAMVk+dH2TBoYrm9BHTe8nCjIANs3I8ixWIx9JAjDVNA6IXAeEUDDEBoBQCAuBTqPtesy39Nt61bVKrZRgnRMDwIQGA4EBFC0aIHUG/9/1P/pUBjTdzhgOgBwDBF1qQrb1Nv/v+tfWok07GBcC4En3VljsdIclUMYgIgAAAAAAAAAAAASAeJK1eXElURk3DcGCI9jsylQ8LhANGAxQ48DSKDgORA0gBiAYAwXjYCQG0TUCwHBzEUHUy2WsrkHMi4kpqDJuxmVE5bNC+GOAYPAailFSeFzgYZQCCf1rIiJtAwuASGAkyNqtKt9Zmmo0NE1npbEqCAAZga6aaQ5YDQMiJm+VzQqiugHAgLRxk7b6x6FDBZX75ZUM+BYBydBk7okIKFC+iTM9m1zp8pB4zfVX1uU2H2I2agtPQdZuiWhqv/7ksC6gBV1o0P1iwADaDro+x9gAEEdFvX///mZ/eT/6Dx8wAyYoAUAAAADAEAFAAAAAAPVTzyO6U2P8w8nM8P6bv+PBRjw07pfb/AciANoiwLBCM1LAysBAFCABgMGhMABswkysR0CIHAMAAMBiAo5JOE9XhikQ4LmBQgtKRMlgyJ74xQblBiMCQEEeCOyis1IcTRb/IEKMJ0FbiyRtCUCGmKBskYnP43B0i4xpidRkB2DlmSRsUTE8ZGTl3/juHAOeOaSQzA/ENHPGXE+oqeicUbFExb/5UKhAzhEiIEXIqViCEoQ0i46x2GSTooqeipSRii3//YliLmBPE4RcmSsQQjP//mQ0nLjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+5LAvgAcldNN2bqASAAAJYOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uSwP+AAAABLAAAAAAAACWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/7ksD/gAAAASwAAAAAAAAlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+5LA/4AAAAEsAAAAAAAAJYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uSwP+AAAABLAAAAAAAACWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/7ksD/gAAAASwAAAAAAAAlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+5LA/4AAAAEsAAAAAAAAJYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uSwP+AAAABLAAAAAAAACWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/7ksD/gAAAASwAAAAAAAAlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAD/+5LA/4AAAAEsAAAAAAAAJYAAAAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAA";
-
 const BACKGROUNDS = [
     "264653",
     "1b1a17",
@@ -40,987 +41,1068 @@ const KEYS: Record<string, number> = {
     s: PadKey.B
 };
 
-// @ts-ignore: ts(2580)
 const ROM_PATH = require("../../res/roms/20y.gb");
 
 // Enumeration that describes the multiple pixel
-// formats and the associated byte size.
+// formats and the associated size in bytes.
 enum PixelFormat {
     RGB = 3,
     RGBA = 4
 }
 
-type State = {
-    gameBoy: GameBoy;
-    engine: string;
-    logicFrequency: number;
-    visualFrequency: number;
-    timerFrequency: number;
-    idleFrequency: number;
-    canvas: HTMLCanvasElement;
-    canvasScaled: HTMLCanvasElement;
-    canvasCtx: CanvasRenderingContext2D;
-    canvasScaledCtx: CanvasRenderingContext2D;
-    image: ImageData;
-    videoBuff: DataView;
-    toastTimeout: number;
-    paused: boolean;
-    background_index: number;
-    nextTickTime: number;
-    fps: number;
-    frameStart: number;
-    frameCount: number;
-    romName: string;
-    romData: Uint8Array;
-    romSize: number;
-};
-
-type Global = {
-    modalCallback: Function;
-};
-
-const state: State = {
-    gameBoy: null,
-    engine: null,
-    logicFrequency: LOGIC_HZ,
-    visualFrequency: VISUAL_HZ,
-    timerFrequency: TIMER_HZ,
-    idleFrequency: IDLE_HZ,
-    canvas: null,
-    canvasScaled: null,
-    canvasCtx: null,
-    canvasScaledCtx: null,
-    image: null,
-    videoBuff: null,
-    toastTimeout: null,
-    paused: false,
-    background_index: 0,
-    nextTickTime: 0,
-    fps: 0,
-    frameStart: new Date().getTime(),
-    frameCount: 0,
-    romName: null,
-    romData: null,
-    romSize: 0
-};
-
-const global: Global = {
-    modalCallback: null
-};
+/**
+ * Top level class that controls the emulator behaviour
+ * and "joins" all the elements together to bring input/output
+ * of the associated machine.
+ */
+class Emulator {
+    /**
+     * The Game Boy engine (probably coming from WASM) that
+     * is going to be used for the emulation.
+     */
+    private gameBoy: GameBoy | null = null;
+
+    /**
+     * The descriptive name of the engine that is currently
+     * in use to emulate the system.
+     */
+    private engine: string | null = null;
+
+    private logicFrequency: number = LOGIC_HZ;
+    private visualFrequency: number = VISUAL_HZ;
+    private timerFrequency: number = TIMER_HZ;
+    private idleFrequency: number = IDLE_HZ;
+
+    private canvas: HTMLCanvasElement | null = null;
+    private canvasScaled: HTMLCanvasElement | null = null;
+    private canvasCtx: CanvasRenderingContext2D | null = null;
+    private canvasScaledCtx: CanvasRenderingContext2D | null = null;
+    private image: ImageData | null = null;
+    private videoBuff: DataView | null = null;
+    private toastTimeout: number | null = null;
+    private paused: boolean = false;
+    private background_index: number = 0;
+    private nextTickTime: number = 0;
+    private fps: number = 0;
+    private frameStart: number = new Date().getTime();
+    private frameCount: number = 0;
+
+    private romName: string | null = null;
+    private romData: Uint8Array | null = null;
+    private romSize: number = 0;
+
+    async main() {
+        // initializes the WASM module, this is required
+        // so that the global symbols become available
+        await wasm();
+
+        // initializes the complete set of sub-systems
+        // and registers the event handlers
+        await this.buildVisuals();
+        await this.init();
+        await this.register();
+
+        // start the emulator subsystem with the initial
+        // ROM retrieved from a remote data source
+        await this.start({ loadRom: true });
+
+        // the counter that controls the overflowing cycles
+        // from tick to tick operation
+        let pending = 0;
+
+        // runs the sequence as an infinite loop, running
+        // the associated CPU cycles accordingly
+        while (true) {
+            // in case the machine is paused we must delay the execution
+            // a little bit until the paused state is recovered
+            if (this.paused) {
+                await new Promise((resolve) => {
+                    setTimeout(resolve, 1000 / this.idleFrequency);
+                });
+                continue;
+            }
 
-const sound = ((data = SOUND_DATA, volume = 0.2) => {
-    const sound = new Audio(data);
-    sound.volume = volume;
-    sound.muted = true;
-    return sound;
-})();
+            // obtains the current time, this value is going
+            // to be used to compute the need for tick computation
+            let currentTime = new Date().getTime();
+
+            try {
+                pending = this.tick(currentTime, pending);
+            } catch (err) {
+                // sets the default error message to be displayed
+                // to the user, this value may be overridden in case
+                // a better and more explicit message can be determined
+                let message = String(err);
+
+                // verifies if the current issue is a panic one
+                // and updates the message value if that's the case
+                const messageNormalized = (err as Error).message.toLowerCase();
+                const isPanic =
+                    messageNormalized.startsWith("unreachable") ||
+                    messageNormalized.startsWith("recursive use of an object");
+                if (isPanic) {
+                    message = "Unrecoverable error, restarting Game Boy";
+                }
 
-const wasm = async () => {
-    await _wasm();
-    GameBoy.set_panic_hook_ws();
-};
+                // displays the error information to both the end-user
+                // and the developer (for diagnostics)
+                this.showToast(message, true, 5000);
+                console.error(err);
+
+                // pauses the machine, allowing the end-user to act
+                // on the error in a proper fashion
+                this.pause();
+
+                // if we're talking about a panic, proper action must be taken
+                // which in this case it means restarting both the WASM sub
+                // system and the machine state (to be able to recover)
+                // also sets the default color on screen to indicate the issue
+                if (isPanic) {
+                    await this.clearCanvas(undefined, {
+                        image: require("./res/storm.png"),
+                        imageScale: 0.2
+                    });
+
+                    await wasm();
+                    await this.start({ restore: false });
+                }
+            }
 
-(window as any).panic = (message: string) => {
-    console.error(message);
-};
+            // calculates the amount of time until the next draw operation
+            // this is the amount of time that is going to be pending
+            currentTime = new Date().getTime();
+            const pendingTime = Math.max(this.nextTickTime - currentTime, 0);
 
-const main = async () => {
-    // initializes the WASM module, this is required
-    // so that the global symbols become available
-    await wasm();
-
-    // initializes the complete set of sub-systems
-    // and registers the event handlers
-    await init();
-    await register();
-
-    // start the emulator subsystem with the initial
-    // ROM retrieved from a remote data source
-    await start({ loadRom: true });
-
-    // the counter that controls the overflowing cycles
-    // from tick to tick operation
-    let pending = 0;
-
-    // runs the sequence as an infinite loop, running
-    // the associated CPU cycles accordingly
-    while (true) {
-        // in case the machin is paused we must delay the execution
-        // a little bit until the paused state is recovered
-        if (state.paused) {
+            // waits a little bit for the next frame to be draw,
+            // this should control the flow of render
             await new Promise((resolve) => {
-                setTimeout(resolve, 1000 / state.idleFrequency);
+                setTimeout(resolve, pendingTime);
             });
-            continue;
         }
+    }
 
-        // obtains the current time, this value is going
-        // to be used to compute the need for tick computation
-        let currentTime = new Date().getTime();
-
-        try {
-            pending = tick(currentTime, pending);
-        } catch (err) {
-            // sets the default error message to be displayed
-            // to the user
-            let message = String(err);
-
-            // verifies if the current issue is a panic one
-            // and updates the message value if that's the case
-            const messageNormalized = (err as Error).message.toLowerCase();
-            const isPanic =
-                messageNormalized.startsWith("unreachable") ||
-                messageNormalized.startsWith("recursive use of an object");
-            if (isPanic) {
-                message = "Unrecoverable error, restarting Game Boy";
-            }
+    tick(currentTime: number, pending: number, cycles: number = 70224) {
+        // in case the time to draw the next frame has not been
+        // reached the flush of the "tick" logic is skipped
+        if (currentTime < this.nextTickTime) return pending;
+
+        // calculates the number of ticks that have elapsed since the
+        // last draw operation, this is critical to be able to properly
+        // operate the clock of the CPU in frame drop situations
+        if (this.nextTickTime === 0) this.nextTickTime = currentTime;
+        let ticks = Math.ceil(
+            (currentTime - this.nextTickTime) /
+                ((1 / this.visualFrequency) * 1000)
+        );
+        ticks = Math.max(ticks, 1);
 
-            // displays the error information to both the end-user
-            // and the developer (for dianostics)
-            showToast(message, true, 5000);
-            console.error(err);
-
-            // pauses the machine, allowing the end-user to act
-            // on the error in a proper fashion
-            pause();
-
-            // if we're talking about a panic proper action must be taken
-            // which in this case it means restarting both the WASM sub
-            // system and the machine state (to be able to recover)
-            // also sets the default color on screen to indicate the issue
-            if (isPanic) {
-                await clearCanvas(undefined, {
-                    // @ts-ignore: ts(2580)
-                    image: require("./res/storm.png"),
-                    imageScale: 0.2
-                });
+        // initializes the counter of cycles with the pending number
+        // of cycles coming from the previous tick
+        let counterCycles = pending;
+
+        let lastFrame = -1;
+
+        while (true) {
+            // limits the number of cycles to the provided
+            // cycle value passed as a parameter
+            if (counterCycles >= cycles) {
+                break;
+            }
 
-                await wasm();
-                await start({ restore: false });
+            // runs the Game Boy clock, this operations should
+            // include the advance of both the CPU and the PPU
+            counterCycles += this.gameBoy!.clock();
+
+            // in case the current PPU mode is VBlank and the
+            // frame is different from the previously rendered
+            // one then it's time to update the canvas
+            if (
+                this.gameBoy!.ppu_mode() == PpuMode.VBlank &&
+                this.gameBoy!.ppu_frame() != lastFrame
+            ) {
+                // updates the canvas object with the new
+                // visual information coming in
+                this.updateCanvas(
+                    this.gameBoy!.frame_buffer_eager(),
+                    PixelFormat.RGB
+                );
+                lastFrame = this.gameBoy!.ppu_frame();
             }
         }
 
-        // calculates the amount of time until the next draw operation
-        // this is the amount of time that is going to be pending
-        currentTime = new Date().getTime();
-        const pendingTime = Math.max(state.nextTickTime - currentTime, 0);
+        // increments the number of frames rendered in the current
+        // section, this value is going to be used to calculate FPS
+        this.frameCount += 1;
+
+        // in case the target number of frames for FPS control
+        // has been reached calculates the number of FPS and
+        // flushes the value to the screen
+        if (this.frameCount === this.visualFrequency * SAMPLE_RATE) {
+            const currentTime = new Date().getTime();
+            const deltaTime = (currentTime - this.frameStart) / 1000;
+            const fps = Math.round(this.frameCount / deltaTime);
+            this.setFps(fps);
+            this.frameCount = 0;
+            this.frameStart = currentTime;
+        }
 
-        // waits a little bit for the next frame to be draw,
-        // this should control the flow of render
-        await new Promise((resolve) => {
-            setTimeout(resolve, pendingTime);
-        });
+        // updates the next update time reference to the, so that it
+        // can be used to control the game loop
+        this.nextTickTime += (1000 / this.visualFrequency) * ticks;
+
+        // calculates the new number of pending (overflow) cycles
+        // that are going to be added to the next iteration
+        return counterCycles - cycles;
     }
-};
 
-const tick = (currentTime: number, pending: number, cycles: number = 70224) => {
-    // in case the time to draw the next frame has not been
-    // reached the flush of the "tick" logic is skiped
-    if (currentTime < state.nextTickTime) return pending;
-
-    // calculates the number of ticks that have elapsed since the
-    // last draw operation, this is critical to be able to properly
-    // operate the clock of the CPU in frame drop situations
-    if (state.nextTickTime === 0) state.nextTickTime = currentTime;
-    let ticks = Math.ceil(
-        (currentTime - state.nextTickTime) /
-            ((1 / state.visualFrequency) * 1000)
-    );
-    ticks = Math.max(ticks, 1);
-
-    // initializes the counter of cycles with the pending number
-    // of cycles coming from the previous tick
-    let counterCycles = pending;
-
-    let lastFrame = -1;
-
-    while (true) {
-        // limits the number of cycles to the provided
-        // cycle value passed as a parameter
-        if (counterCycles >= cycles) {
-            break;
+    /**
+     * Starts the current machine, setting the internal structure in
+     * a proper state to start drawing and receiving input.
+     *
+     * @param options The options that are going to be used in the
+     * starting of the machine.
+     */
+    async start({
+        engine = "neo",
+        restore = true,
+        loadRom = false,
+        romPath = ROM_PATH,
+        romName = null,
+        romData = null
+    }: {
+        engine?: string | null;
+        restore?: boolean;
+        loadRom?: boolean;
+        romPath?: string;
+        romName?: string | null;
+        romData?: Uint8Array | null;
+    } = {}) {
+        // in case a remote ROM loading operation has been
+        // requested then loads it from the remote origin
+        if (loadRom) {
+            [romName, romData] = await this.fetchRom(romPath);
+        } else if (romName === null || romData === null) {
+            [romName, romData] = [this.romName, this.romData];
         }
 
-        // runs the Game Boy clock, this operations should
-        // include the advance of both the CPU and the PPU
-        counterCycles += state.gameBoy.clock();
-
-        // in case the current PPU mode is VBlank and the
-        // fram is different from the previously rendered
-        // one then it's time to update the canvas
-        if (
-            state.gameBoy.ppu_mode() == PpuMode.VBlank &&
-            state.gameBoy.ppu_frame() != lastFrame
-        ) {
-            // updates the canvas object with the new
-            // visual information coming in
-            updateCanvas(state.gameBoy.frame_buffer_eager(), PixelFormat.RGB);
-            lastFrame = state.gameBoy.ppu_frame();
+        // selects the proper engine for execution
+        // and builds a new instance of it
+        switch (engine) {
+            case "neo":
+                this.gameBoy = new GameBoy();
+                break;
+
+            default:
+                if (!this.gameBoy) {
+                    throw new Error("No engine requested");
+                }
+                break;
         }
-    }
 
-    // increments the number of frames rendered in the current
-    // section, this value is going to be used to calculate FPS
-    state.frameCount += 1;
-
-    // in case the target number of frames for FPS control
-    // has been reached calculates the number of FPS and
-    // flushes the value to the screen
-    if (state.frameCount === state.visualFrequency * SAMPLE_RATE) {
-        const currentTime = new Date().getTime();
-        const deltaTime = (currentTime - state.frameStart) / 1000;
-        const fps = Math.round(state.frameCount / deltaTime);
-        setFps(fps);
-        state.frameCount = 0;
-        state.frameStart = currentTime;
+        // resets the Game Boy engine to restore it into
+        // a valid state ready to be used
+        this.gameBoy.reset();
+        this.gameBoy.load_boot_default();
+        const cartridge = this.gameBoy.load_rom_ws(romData!);
+
+        // updates the ROM name in case there's extra information
+        // coming from the cartridge
+        romName = cartridge.title() ? cartridge.title() : romName;
+
+        // updates the name of the currently selected engine
+        // to the one that has been provided (logic change)
+        if (engine) this.engine = engine;
+
+        // updates the complete set of global information that
+        // is going to be displayed
+        this.setEngine(this.engine!);
+        this.setRom(romName!, romData!);
+        this.setLogicFrequency(this.logicFrequency);
+        this.setFps(this.fps);
+
+        // in case the restore (state) flag is set
+        // then resumes the machine execution
+        if (restore) this.resume();
     }
 
-    // updates the next update time reference to the, so that it
-    // can be used to control the game loop
-    state.nextTickTime += (1000 / state.visualFrequency) * ticks;
+    async buildVisuals() {
+        /*     KeyValue.create("ROM", "-", { id: "diag:rom-name" }).mount(".diag");
+        KeyValue.create("ROM Size", "-", { id: "diag:rom-size" }).mount(
+            ".diag"
+        );
+        KeyValue.create("Framerate", "-", { id: "diag:framerate" }).mount(
+            ".diag"
+        );
+        KeyValue.create("ROM Type", "-", { id: "diag:rom-type" }).mount(
+            ".diag"
+        );
+        KeySwitch.create("Tobias", ["1", "2", "3"], {
+            id: "diag:tobias"
+        }).mount(".diag");
 
-    // calculates the new number of pending (overflow) cycles
-    // that are going to be added to the next iteration
-    return counterCycles - cycles;
-};
+        Button.create("Tobias", require("./res/close.svg"))
+            .bind("click", () => alert("Hello World"))
+            .mount(".button-area");*/
+    }
 
-/**
- * Starts the current machine, setting the internal structure in
- * a proper state to start drwaing and receiving input.
- *
- * @param options The options that are going to be used in the
- * starting of the machine.
- */
-const start = async ({
-    engine = "neo",
-    restore = true,
-    loadRom = false,
-    romPath = ROM_PATH,
-    romName = null as string,
-    romData = null as Uint8Array
-} = {}) => {
-    // in case a remote ROM loading operation has been
-    // requested then loads it from the remote origin
-    if (loadRom) {
-        [romName, romData] = await fetchRom(romPath);
-    } else if (romName === null || romData === null) {
-        [romName, romData] = [state.romName, state.romData];
+    // @todo remove this method, or at least most of it
+    async register() {
+        await Promise.all([
+            this.registerDrop(),
+            this.registerKeys(),
+            this.registerButtons(),
+            this.registerKeyboard(),
+            this.registerCanvas(),
+            this.registerToast(),
+            this.registerModal()
+        ]);
     }
 
-    // selects the proper engine for execution
-    // and builds a new instance of it
-    switch (engine) {
-        case "neo":
-            state.gameBoy = new GameBoy();
-            break;
+    async init() {
+        await Promise.all([this.initBase(), this.initCanvas()]);
+    }
 
-        default:
-            if (!state.gameBoy) {
-                throw new Error("No engine requested");
+    registerDrop() {
+        document.addEventListener("drop", async (event) => {
+            if (
+                !event.dataTransfer!.files ||
+                event.dataTransfer!.files.length === 0
+            ) {
+                return;
             }
-            break;
-    }
 
-    // resets the Game Boy engine to restore it into
-    // a valid state ready to be used
-    state.gameBoy.reset();
-    state.gameBoy.load_boot_default();
-    const cartridge = state.gameBoy.load_rom_ws(romData);
-
-    // updates the ROM name in case there's extra information
-    // comming from the cartridge
-    romName = cartridge.title() ? cartridge.title() : romName;
-
-    // updates the name of the currently selected engine
-    // to the one that has been provided (logic change)
-    if (engine) state.engine = engine;
-
-    // updates the complete set of global information that
-    // is going to be displayed
-    setEngine(state.engine);
-    setRom(romName, romData);
-    setLogicFrequency(state.logicFrequency);
-    setFps(state.fps);
-
-    // in case the restore (state) flag is set
-    // then resumes the machine execution
-    if (restore) resume();
-};
+            event.preventDefault();
+            event.stopPropagation();
 
-const register = async () => {
-    await Promise.all([
-        registerDrop(),
-        registerKeys(),
-        registerButtons(),
-        registerKeyboard(),
-        registerCanvas(),
-        registerToast(),
-        registerModal()
-    ]);
-};
+            const overlay = document.getElementById("overlay")!;
+            overlay.classList.remove("visible");
 
-const init = async () => {
-    await Promise.all([initBase(), initCanvas()]);
-};
+            const file = event.dataTransfer!.files[0];
 
-const registerDrop = () => {
-    document.addEventListener("drop", async (event) => {
-        if (
-            !event.dataTransfer.files ||
-            event.dataTransfer.files.length === 0
-        ) {
-            return;
-        }
+            if (!file.name.endsWith(".gb")) {
+                this.showToast(
+                    "This is probably not a Game Boy ROM file!",
+                    true
+                );
+                return;
+            }
 
-        event.preventDefault();
-        event.stopPropagation();
+            const arrayBuffer = await file.arrayBuffer();
+            const romData = new Uint8Array(arrayBuffer);
 
-        const overlay = document.getElementById("overlay");
-        overlay.classList.remove("visible");
+            this.start({ engine: null, romName: file.name, romData: romData });
 
-        const file = event.dataTransfer.files[0];
+            this.showToast(`Loaded ${file.name} ROM successfully!`);
+        });
+        document.addEventListener("dragover", async (event) => {
+            if (!event.dataTransfer!.items || event.dataTransfer!.items[0].type)
+                return;
 
-        if (!file.name.endsWith(".gb")) {
-            showToast("This is probably not a Game Boy ROM file!", true);
-            return;
-        }
+            event.preventDefault();
 
-        const arrayBuffer = await file.arrayBuffer();
-        const romData = new Uint8Array(arrayBuffer);
+            const overlay = document.getElementById("overlay")!;
+            overlay.classList.add("visible");
+        });
+        document.addEventListener("dragenter", async (event) => {
+            if (!event.dataTransfer!.items || event.dataTransfer!.items[0].type)
+                return;
+            const overlay = document.getElementById("overlay")!;
+            overlay.classList.add("visible");
+        });
+        document.addEventListener("dragleave", async (event) => {
+            if (!event.dataTransfer!.items || event.dataTransfer!.items[0].type)
+                return;
+            const overlay = document.getElementById("overlay")!;
+            overlay.classList.remove("visible");
+        });
+    }
 
-        start({ engine: null, romName: file.name, romData: romData });
-
-        showToast(`Loaded ${file.name} ROM successfully!`);
-    });
-    document.addEventListener("dragover", async (event) => {
-        if (!event.dataTransfer.items || event.dataTransfer.items[0].type)
-            return;
-
-        event.preventDefault();
-
-        const overlay = document.getElementById("overlay");
-        overlay.classList.add("visible");
-    });
-    document.addEventListener("dragenter", async (event) => {
-        if (!event.dataTransfer.items || event.dataTransfer.items[0].type)
-            return;
-        const overlay = document.getElementById("overlay");
-        overlay.classList.add("visible");
-    });
-    document.addEventListener("dragleave", async (event) => {
-        if (!event.dataTransfer.items || event.dataTransfer.items[0].type)
-            return;
-        const overlay = document.getElementById("overlay");
-        overlay.classList.remove("visible");
-    });
-};
+    registerKeys() {
+        document.addEventListener("keydown", (event) => {
+            const keyCode = KEYS[event.key];
+            if (keyCode !== undefined) {
+                this.gameBoy!.key_press(keyCode);
+                return;
+            }
 
-const registerKeys = () => {
-    document.addEventListener("keydown", (event) => {
-        const keyCode = KEYS[event.key];
-        if (keyCode !== undefined) {
-            state.gameBoy.key_press(keyCode);
-            return;
-        }
+            switch (event.key) {
+                case "+":
+                    this.setLogicFrequency(
+                        this.logicFrequency + FREQUENCY_DELTA
+                    );
+                    break;
+
+                case "-":
+                    this.setLogicFrequency(
+                        this.logicFrequency - FREQUENCY_DELTA
+                    );
+                    break;
+
+                case "Escape":
+                    this.minimize();
+                    break;
+            }
+        });
 
-        switch (event.key) {
-            case "+":
-                setLogicFrequency(state.logicFrequency + FREQUENCY_DELTA);
-                break;
+        document.addEventListener("keyup", (event) => {
+            const keyCode = KEYS[event.key];
+            if (keyCode !== undefined) {
+                this.gameBoy!.key_lift(keyCode);
+                return;
+            }
+        });
+    }
 
-            case "-":
-                setLogicFrequency(state.logicFrequency - FREQUENCY_DELTA);
-                break;
+    registerButtons() {
+        const engine = document.getElementById("engine")!;
+        engine.addEventListener("click", () => {
+            const name = this.engine == "neo" ? "classic" : "neo";
+            this.start({ engine: name });
+            this.showToast(
+                `Game Boy running in engine "${name.toUpperCase()}" from now on!`
+            );
+        });
 
-            case "Escape":
-                minimize();
-                break;
-        }
-    });
+        const logicFrequencyPlus = document.getElementById(
+            "logic-frequency-plus"
+        )!;
+        logicFrequencyPlus.addEventListener("click", () => {
+            this.setLogicFrequency(this.logicFrequency + FREQUENCY_DELTA);
+        });
 
-    document.addEventListener("keyup", (event) => {
-        const keyCode = KEYS[event.key];
-        if (keyCode !== undefined) {
-            state.gameBoy.key_lift(keyCode);
-            return;
-        }
-    });
-};
+        const logicFrequencyMinus = document.getElementById(
+            "logic-frequency-minus"
+        )!;
+        logicFrequencyMinus.addEventListener("click", () => {
+            this.setLogicFrequency(this.logicFrequency - FREQUENCY_DELTA);
+        });
 
-const registerButtons = () => {
-    const engine = document.getElementById("engine");
-    engine.addEventListener("click", () => {
-        const name = state.engine == "neo" ? "classic" : "neo";
-        start({ engine: name });
-        showToast(
-            `Game Boy running in engine "${name.toUpperCase()}" from now on!`
-        );
-    });
-
-    const logicFrequencyPlus = document.getElementById("logic-frequency-plus");
-    logicFrequencyPlus.addEventListener("click", () => {
-        setLogicFrequency(state.logicFrequency + FREQUENCY_DELTA);
-    });
-
-    const logicFrequencyMinus = document.getElementById(
-        "logic-frequency-minus"
-    );
-    logicFrequencyMinus.addEventListener("click", () => {
-        setLogicFrequency(state.logicFrequency - FREQUENCY_DELTA);
-    });
-
-    const buttonPause = document.getElementById("button-pause");
-    buttonPause.addEventListener("click", () => {
-        toggleRunning();
-    });
-
-    const buttonReset = document.getElementById("button-reset");
-    buttonReset.addEventListener("click", () => {
-        reset();
-    });
-
-    const buttonBenchmark = document.getElementById("button-benchmark");
-    buttonBenchmark.addEventListener("click", async () => {
-        const result = await showModal(
-            "Are you sure you want to start a benchmark?\nThe benchmark is considered an expensive operation!",
-            "Confirm"
-        );
-        if (!result) return;
-        buttonBenchmark.classList.add("enabled");
-        pause();
-        try {
-            const initial = Date.now();
-            const count = 500000000;
-            for (let i = 0; i < count; i++) {
-                state.gameBoy.clock();
-            }
-            const delta = (Date.now() - initial) / 1000;
-            const frequency_mhz = count / delta / 1000 / 1000;
-            showToast(
-                `Took ${delta.toFixed(
-                    2
-                )} seconds to run ${count} ticks (${frequency_mhz.toFixed(
-                    2
-                )} Mhz)!`,
-                undefined,
-                7500
-            );
-        } finally {
-            resume();
-            buttonBenchmark.classList.remove("enabled");
-        }
-    });
-
-    const buttonFullscreen = document.getElementById("button-fullscreen");
-    buttonFullscreen.addEventListener("click", () => {
-        maximize();
-    });
-
-    const buttonKeyboard = document.getElementById("button-keyboard");
-    buttonKeyboard.addEventListener("click", () => {
-        const sectionKeyboard = document.getElementById("section-keyboard");
-        const separatorKeyboard = document.getElementById("separator-keyboard");
-        const sectionNarrative = document.getElementById("section-narrative");
-        const separatorNarrative = document.getElementById(
-            "separator-narrative"
-        );
-        if (buttonKeyboard.classList.contains("enabled")) {
-            sectionKeyboard.style.display = "none";
-            separatorKeyboard.style.display = "none";
-            sectionNarrative.style.display = "block";
-            separatorNarrative.style.display = "block";
-            buttonKeyboard.classList.remove("enabled");
-        } else {
-            sectionKeyboard.style.display = "block";
-            separatorKeyboard.style.display = "block";
-            sectionNarrative.style.display = "none";
-            separatorNarrative.style.display = "none";
-            buttonKeyboard.classList.add("enabled");
-        }
-    });
-
-    const buttonDebug = document.getElementById("button-debug");
-    buttonDebug.addEventListener("click", () => {
-        const sectionDebug = document.getElementById("section-debug");
-        const separatorDebug = document.getElementById("separator-debug");
-        const sectionNarrative = document.getElementById("section-narrative");
-        const separatorNarrative = document.getElementById(
-            "separator-narrative"
-        );
-        if (buttonDebug.classList.contains("enabled")) {
-            sectionDebug.style.display = "none";
-            separatorDebug.style.display = "none";
-            sectionNarrative.style.display = "block";
-            separatorNarrative.style.display = "block";
-            buttonDebug.classList.remove("enabled");
-        } else {
-            sectionDebug.style.display = "block";
-            separatorDebug.style.display = "block";
-            sectionNarrative.style.display = "none";
-            separatorNarrative.style.display = "none";
-            buttonDebug.classList.add("enabled");
-
-            const canvasTiles = document.getElementById(
-                "canvas-tiles"
-            ) as HTMLCanvasElement;
-            const canvasTilesCtx = canvasTiles.getContext("2d");
-
-            const canvasImage = canvasTilesCtx.createImageData(
-                canvasTiles.width,
-                canvasTiles.height
+        const buttonPause = document.getElementById("button-pause")!;
+        buttonPause.addEventListener("click", () => {
+            this.toggleRunning();
+        });
+
+        const buttonReset = document.getElementById("button-reset")!;
+        buttonReset.addEventListener("click", () => {
+            this.reset();
+        });
+
+        const buttonBenchmark = document.getElementById("button-benchmark")!;
+        buttonBenchmark.addEventListener("click", async () => {
+            const result = await this.showModal(
+                "Are you sure you want to start a benchmark?\nThe benchmark is considered an expensive operation!",
+                "Confirm"
             );
-            const videoBuff = new DataView(canvasImage.data.buffer);
-
-            /**
-             * Draws the tile at the given index to the proper
-             * vertical offset in the given context and buffer.
-             *
-             * @param index The index of the sprite to be drawn.
-             * @param format The pixel format of the sprite.
-             */
-            const drawTile = (
-                index: number,
-                context: CanvasRenderingContext2D,
-                buffer: DataView,
-                format: PixelFormat = PixelFormat.RGB
-            ) => {
-                const pixels = state.gameBoy.get_tile_buffer(index);
-                const line = Math.floor(index / 16);
-                const column = index % 16;
-                let offset =
-                    (line * canvasTiles.width * 8 + column * 8) *
-                    PixelFormat.RGBA;
-                let counter = 0;
-                for (let index = 0; index < pixels.length; index += format) {
-                    const color =
-                        (pixels[index] << 24) |
-                        (pixels[index + 1] << 16) |
-                        (pixels[index + 2] << 8) |
-                        (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff);
-                    buffer.setUint32(offset, color);
-
-                    counter++;
-                    if (counter == 8) {
-                        counter = 0;
-                        offset += (canvasTiles.width - 7) * PixelFormat.RGBA;
-                    } else {
-                        offset += PixelFormat.RGBA;
-                    }
+            if (!result) return;
+            buttonBenchmark.classList.add("enabled");
+            this.pause();
+            try {
+                const initial = Date.now();
+                const count = 500000000;
+                for (let i = 0; i < count; i++) {
+                    this.gameBoy!.clock();
                 }
-                context.putImageData(canvasImage, 0, 0);
-            };
+                const delta = (Date.now() - initial) / 1000;
+                const frequency_mhz = count / delta / 1000 / 1000;
+                this.showToast(
+                    `Took ${delta.toFixed(
+                        2
+                    )} seconds to run ${count} ticks (${frequency_mhz.toFixed(
+                        2
+                    )} Mhz)!`,
+                    undefined,
+                    7500
+                );
+            } finally {
+                this.resume();
+                buttonBenchmark.classList.remove("enabled");
+            }
+        });
+
+        const buttonFullscreen = document.getElementById("button-fullscreen")!;
+        buttonFullscreen.addEventListener("click", () => {
+            this.maximize();
+        });
 
-            for (let index = 0; index < 384; index++) {
-                drawTile(index, canvasTilesCtx, videoBuff);
+        const buttonKeyboard = document.getElementById("button-keyboard")!;
+        buttonKeyboard.addEventListener("click", () => {
+            const sectionKeyboard =
+                document.getElementById("section-keyboard")!;
+            const separatorKeyboard =
+                document.getElementById("separator-keyboard")!;
+            const sectionNarrative =
+                document.getElementById("section-narrative")!;
+            const separatorNarrative = document.getElementById(
+                "separator-narrative"
+            )!;
+            if (buttonKeyboard.classList.contains("enabled")) {
+                sectionKeyboard.style.display = "none";
+                separatorKeyboard.style.display = "none";
+                sectionNarrative.style.display = "block";
+                separatorNarrative.style.display = "block";
+                buttonKeyboard.classList.remove("enabled");
+            } else {
+                sectionKeyboard.style.display = "block";
+                separatorKeyboard.style.display = "block";
+                sectionNarrative.style.display = "none";
+                separatorNarrative.style.display = "none";
+                buttonKeyboard.classList.add("enabled");
             }
+        });
+
+        const buttonDebug = document.getElementById("button-debug")!;
+        buttonDebug.addEventListener("click", () => {
+            const sectionDebug = document.getElementById("section-debug")!;
+            const separatorDebug = document.getElementById("separator-debug")!;
+            const sectionNarrative =
+                document.getElementById("section-narrative")!;
+            const separatorNarrative = document.getElementById(
+                "separator-narrative"
+            )!;
+            if (buttonDebug.classList.contains("enabled")) {
+                sectionDebug.style.display = "none";
+                separatorDebug.style.display = "none";
+                sectionNarrative.style.display = "block";
+                separatorNarrative.style.display = "block";
+                buttonDebug.classList.remove("enabled");
+            } else {
+                sectionDebug.style.display = "block";
+                separatorDebug.style.display = "block";
+                sectionNarrative.style.display = "none";
+                separatorNarrative.style.display = "none";
+                buttonDebug.classList.add("enabled");
+
+                const canvasTiles = document.getElementById(
+                    "canvas-tiles"
+                ) as HTMLCanvasElement;
+                const canvasTilesCtx = canvasTiles.getContext("2d")!;
+                canvasTilesCtx.imageSmoothingEnabled = false;
+
+                const canvasImage = canvasTilesCtx.createImageData(
+                    canvasTiles.width,
+                    canvasTiles.height
+                );
+                const videoBuff = new DataView(canvasImage.data.buffer);
+
+                /**
+                 * Draws the tile at the given index to the proper
+                 * vertical offset in the given context and buffer.
+                 *
+                 * @param index The index of the sprite to be drawn.
+                 * @param format The pixel format of the sprite.
+                 */
+                const drawTile = (
+                    index: number,
+                    context: CanvasRenderingContext2D,
+                    buffer: DataView,
+                    format: PixelFormat = PixelFormat.RGB
+                ) => {
+                    const pixels = this.gameBoy!.get_tile_buffer(index);
+                    const line = Math.floor(index / 16);
+                    const column = index % 16;
+                    let offset =
+                        (line * canvasTiles.width * 8 + column * 8) *
+                        PixelFormat.RGBA;
+                    let counter = 0;
+                    for (
+                        let index = 0;
+                        index < pixels.length;
+                        index += format
+                    ) {
+                        const color =
+                            (pixels[index] << 24) |
+                            (pixels[index + 1] << 16) |
+                            (pixels[index + 2] << 8) |
+                            (format == PixelFormat.RGBA
+                                ? pixels[index + 3]
+                                : 0xff);
+                        buffer.setUint32(offset, color);
+
+                        counter++;
+                        if (counter == 8) {
+                            counter = 0;
+                            offset +=
+                                (canvasTiles.width - 7) * PixelFormat.RGBA;
+                        } else {
+                            offset += PixelFormat.RGBA;
+                        }
+                    }
+                    context.putImageData(canvasImage, 0, 0);
+                };
 
-            const vram = state.gameBoy.vram_eager();
-            const step = 16;
-            for (let index = 0; index < vram.length; index += step) {
-                let line = `${(index + 0x8000).toString(16).padStart(4, "0")}`;
-                for (let j = 0; j < step; j++) {
-                    line += ` ${vram[index + j].toString(16).padStart(2, "0")}`;
+                for (let index = 0; index < 384; index++) {
+                    drawTile(index, canvasTilesCtx, videoBuff);
+                }
+
+                const vram = this.gameBoy!.vram_eager();
+                const step = 16;
+                for (let index = 0; index < vram.length; index += step) {
+                    let line = `${(index + 0x8000)
+                        .toString(16)
+                        .padStart(4, "0")}`;
+                    for (let j = 0; j < step; j++) {
+                        line += ` ${vram[index + j]
+                            .toString(16)
+                            .padStart(2, "0")}`;
+                    }
+                    console.info(line);
                 }
-                console.info(line);
             }
-        }
-    });
-
-    const buttonInformation = document.getElementById("button-information");
-    buttonInformation.addEventListener("click", () => {
-        const sectionDiag = document.getElementById("section-diag");
-        const separatorDiag = document.getElementById("separator-diag");
-        if (buttonInformation.classList.contains("enabled")) {
-            sectionDiag.style.display = "none";
-            separatorDiag.style.display = "none";
-            buttonInformation.classList.remove("enabled");
-        } else {
-            sectionDiag.style.display = "block";
-            separatorDiag.style.display = "block";
-            buttonInformation.classList.add("enabled");
-        }
-    });
-
-    const buttonTheme = document.getElementById("button-theme");
-    buttonTheme.addEventListener("click", () => {
-        state.background_index =
-            (state.background_index + 1) % BACKGROUNDS.length;
-        const background = BACKGROUNDS[state.background_index];
-        setBackground(background);
-    });
-
-    const buttonUploadFile = document.getElementById(
-        "button-upload-file"
-    ) as HTMLInputElement;
-    buttonUploadFile.addEventListener("change", async () => {
-        if (!buttonUploadFile.files || buttonUploadFile.files.length === 0) {
-            return;
-        }
+        });
 
-        const file = buttonUploadFile.files[0];
+        const buttonInformation =
+            document.getElementById("button-information")!;
+        buttonInformation.addEventListener("click", () => {
+            const sectionDiag = document.getElementById("section-diag")!;
+            const separatorDiag = document.getElementById("separator-diag")!;
+            if (buttonInformation.classList.contains("enabled")) {
+                sectionDiag.style.display = "none";
+                separatorDiag.style.display = "none";
+                buttonInformation.classList.remove("enabled");
+            } else {
+                sectionDiag.style.display = "block";
+                separatorDiag.style.display = "block";
+                buttonInformation.classList.add("enabled");
+            }
+        });
 
-        const arrayBuffer = await file.arrayBuffer();
-        const romData = new Uint8Array(arrayBuffer);
+        const buttonTheme = document.getElementById("button-theme")!;
+        buttonTheme.addEventListener("click", () => {
+            this.background_index =
+                (this.background_index + 1) % BACKGROUNDS.length;
+            const background = BACKGROUNDS[this.background_index];
+            this.setBackground(background);
+        });
 
-        buttonUploadFile.value = "";
+        const buttonUploadFile = document.getElementById(
+            "button-upload-file"
+        ) as HTMLInputElement;
+        buttonUploadFile.addEventListener("change", async () => {
+            if (
+                !buttonUploadFile.files ||
+                buttonUploadFile.files.length === 0
+            ) {
+                return;
+            }
 
-        start({ engine: null, romName: file.name, romData: romData });
+            const file = buttonUploadFile.files[0];
 
-        showToast(`Loaded ${file.name} ROM successfully!`);
-    });
-};
+            const arrayBuffer = await file.arrayBuffer();
+            const romData = new Uint8Array(arrayBuffer);
 
-const registerKeyboard = () => {
-    const keyboard = document.getElementById("keyboard");
-    const keys = keyboard.getElementsByClassName("key");
+            buttonUploadFile.value = "";
 
-    keyboard.addEventListener("touchstart", function (event) {
-        event.preventDefault();
-        event.stopPropagation();
-    });
+            this.start({ engine: null, romName: file.name, romData: romData });
 
-    keyboard.addEventListener("touchend", function (event) {
-        event.preventDefault();
-        event.stopPropagation();
-    });
+            this.showToast(`Loaded ${file.name} ROM successfully!`);
+        });
+    }
+
+    // @todo this should be converted into a component
+    registerKeyboard() {
+        const keyboard = document.getElementById("keyboard")!;
+        const keys = keyboard.getElementsByClassName("key");
 
-    Array.prototype.forEach.call(keys, (k: Element) => {
-        k.addEventListener("mousedown", function (event) {
-            const keyCode = KEYS[this.textContent.toLowerCase()];
-            //state.gameBoy.key_press_ws(keyCode); @todo
+        keyboard.addEventListener("touchstart", function (event) {
             event.preventDefault();
             event.stopPropagation();
         });
 
-        k.addEventListener("touchstart", function (event) {
-            const keyCode = KEYS[this.textContent.toLowerCase()];
-            //state.gameBoy.key_press_ws(keyCode); @todo
+        keyboard.addEventListener("touchend", function (event) {
             event.preventDefault();
             event.stopPropagation();
         });
 
-        k.addEventListener("mouseup", function (event) {
-            const keyCode = KEYS[this.textContent.toLowerCase()];
-            //state.gameBoy.key_lift_ws(keyCode); @todo
-            event.preventDefault();
-            event.stopPropagation();
+        Array.prototype.forEach.call(keys, (k: Element) => {
+            k.addEventListener(
+                "mousedown",
+                function (this: HTMLElement, event) {
+                    const keyCode = KEYS[this.textContent!.toLowerCase()];
+                    //this.gameBoy.key_press_ws(keyCode); @todo
+                    event.preventDefault();
+                    event.stopPropagation();
+                }
+            );
+
+            k.addEventListener(
+                "touchstart",
+                function (this: HTMLElement, event) {
+                    const keyCode = KEYS[this.textContent!.toLowerCase()];
+                    //this.gameBoy.key_press_ws(keyCode); @todo
+                    event.preventDefault();
+                    event.stopPropagation();
+                }
+            );
+
+            k.addEventListener("mouseup", function (this: HTMLElement, event) {
+                const keyCode = KEYS[this.textContent!.toLowerCase()];
+                //this.gameBoy.key_lift_ws(keyCode); @todo
+                event.preventDefault();
+                event.stopPropagation();
+            });
+
+            k.addEventListener("touchend", function (this: HTMLElement, event) {
+                const keyCode = KEYS[this.textContent!.toLowerCase()];
+                //this.gameBoy.key_lift_ws(keyCode); @todo
+                event.preventDefault();
+                event.stopPropagation();
+            });
         });
+    }
 
-        k.addEventListener("touchend", function (event) {
-            const keyCode = KEYS[this.textContent.toLowerCase()];
-            //state.gameBoy.key_lift_ws(keyCode); @todo
-            event.preventDefault();
-            event.stopPropagation();
+    registerCanvas() {
+        const canvasClose = document.getElementById("canvas-close")!;
+        canvasClose.addEventListener("click", () => {
+            this.minimize();
         });
-    });
-};
+    }
 
-const registerCanvas = () => {
-    const canvasClose = document.getElementById("canvas-close");
-    canvasClose.addEventListener("click", () => {
-        minimize();
-    });
-};
+    registerToast() {
+        const toast = document.getElementById("toast")!;
+        toast.addEventListener("click", () => {
+            toast.classList.remove("visible");
+        });
+    }
 
-const registerToast = () => {
-    const toast = document.getElementById("toast");
-    toast.addEventListener("click", () => {
-        toast.classList.remove("visible");
-    });
-};
+    registerModal() {
+        const modalClose = document.getElementById("modal-close")!;
+        modalClose.addEventListener("click", () => {
+            this.hideModal(false);
+        });
 
-const registerModal = () => {
-    const modalClose = document.getElementById("modal-close");
-    modalClose.addEventListener("click", () => {
-        hideModal(false);
-    });
-
-    const modalCancel = document.getElementById("modal-cancel");
-    modalCancel.addEventListener("click", () => {
-        hideModal(false);
-    });
-
-    const modalConfirm = document.getElementById("modal-confirm");
-    modalConfirm.addEventListener("click", () => {
-        hideModal(true);
-    });
-
-    document.addEventListener("keydown", (event) => {
-        if (event.key === "Escape") {
-            hideModal(false);
-        }
-    });
-};
+        const modalCancel = document.getElementById("modal-cancel")!;
+        modalCancel.addEventListener("click", () => {
+            this.hideModal(false);
+        });
 
-const initBase = async () => {
-    const background = BACKGROUNDS[state.background_index];
-    setBackground(background);
-    setVersion(info.version);
-};
+        const modalConfirm = document.getElementById("modal-confirm")!;
+        modalConfirm.addEventListener("click", () => {
+            this.hideModal(true);
+        });
 
-const initCanvas = async () => {
-    // initializes the off-screen canvas that is going to be
-    // used in the drawing process
-    state.canvas = document.createElement("canvas");
-    state.canvas.width = DISPLAY_WIDTH;
-    state.canvas.height = DISPLAY_HEIGHT;
-    state.canvasCtx = state.canvas.getContext("2d");
-
-    state.canvasScaled = document.getElementById(
-        "engine-canvas"
-    ) as HTMLCanvasElement;
-    state.canvasScaled.width =
-        state.canvasScaled.width * window.devicePixelRatio;
-    state.canvasScaled.height =
-        state.canvasScaled.height * window.devicePixelRatio;
-    state.canvasScaledCtx = state.canvasScaled.getContext("2d");
-
-    state.canvasScaledCtx.scale(
-        state.canvasScaled.width / state.canvas.width,
-        state.canvasScaled.height / state.canvas.height
-    );
-    state.canvasScaledCtx.imageSmoothingEnabled = false;
-
-    state.image = state.canvasCtx.createImageData(
-        state.canvas.width,
-        state.canvas.height
-    );
-    state.videoBuff = new DataView(state.image.data.buffer);
-};
+        document.addEventListener("keydown", (event) => {
+            if (event.key === "Escape") {
+                this.hideModal(false);
+            }
+        });
+    }
 
-const updateCanvas = (
-    pixels: Uint8Array,
-    format: PixelFormat = PixelFormat.RGB
-) => {
-    let offset = 0;
-    for (let index = 0; index < pixels.length; index += format) {
-        const color =
-            (pixels[index] << 24) |
-            (pixels[index + 1] << 16) |
-            (pixels[index + 2] << 8) |
-            (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff);
-        state.videoBuff.setUint32(offset, color);
-        offset += PixelFormat.RGBA;
+    async initBase() {
+        const background = BACKGROUNDS[this.background_index];
+        this.setBackground(background);
+        this.setVersion(info.version);
     }
-    state.canvasCtx.putImageData(state.image, 0, 0);
-    state.canvasScaledCtx.drawImage(state.canvas, 0, 0);
-};
 
-const clearCanvas = async (
-    color = PIXEL_UNSET_COLOR,
-    { image = null as string, imageScale = 1 } = {}
-) => {
-    state.canvasScaledCtx.fillStyle = `#${color.toString(16).toUpperCase()}`;
-    state.canvasScaledCtx.fillRect(
-        0,
-        0,
-        state.canvasScaled.width,
-        state.canvasScaled.height
-    );
-
-    // in case an image was requested then uses that to load
-    // an image at the center of the screen properly scaled
-    if (image) {
-        const img = await new Promise<HTMLImageElement>((resolve) => {
-            const img = new Image();
-            img.onload = () => {
-                resolve(img);
-            };
-            img.src = image;
-        });
-        const [imgWidth, imgHeight] = [
-            img.width * imageScale * window.devicePixelRatio,
-            img.height * imageScale * window.devicePixelRatio
-        ];
-        const [x0, y0] = [
-            state.canvasScaled.width / 2 - imgWidth / 2,
-            state.canvasScaled.height / 2 - imgHeight / 2
-        ];
-        state.canvasScaledCtx.setTransform(1, 0, 0, 1, 0, 0);
-        try {
-            state.canvasScaledCtx.drawImage(img, x0, y0, imgWidth, imgHeight);
-        } finally {
-            state.canvasScaledCtx.scale(
-                state.canvasScaled.width / state.canvas.width,
-                state.canvasScaled.height / state.canvas.height
-            );
+    async initCanvas() {
+        // initializes the off-screen canvas that is going to be
+        // used in the drawing process
+        this.canvas = document.createElement("canvas");
+        this.canvas.width = DISPLAY_WIDTH;
+        this.canvas.height = DISPLAY_HEIGHT;
+        this.canvasCtx = this.canvas.getContext("2d")!;
+
+        this.canvasScaled = document.getElementById(
+            "engine-canvas"
+        ) as HTMLCanvasElement;
+        this.canvasScaled.width =
+            this.canvasScaled.width * window.devicePixelRatio;
+        this.canvasScaled.height =
+            this.canvasScaled.height * window.devicePixelRatio;
+        this.canvasScaledCtx = this.canvasScaled.getContext("2d")!;
+
+        this.canvasScaledCtx.scale(
+            this.canvasScaled.width / this.canvas.width,
+            this.canvasScaled.height / this.canvas.height
+        );
+        this.canvasScaledCtx.imageSmoothingEnabled = false;
+
+        this.image = this.canvasCtx.createImageData(
+            this.canvas.width,
+            this.canvas.height
+        );
+        this.videoBuff = new DataView(this.image.data.buffer);
+    }
+
+    updateCanvas(pixels: Uint8Array, format: PixelFormat = PixelFormat.RGB) {
+        let offset = 0;
+        for (let index = 0; index < pixels.length; index += format) {
+            const color =
+                (pixels[index] << 24) |
+                (pixels[index + 1] << 16) |
+                (pixels[index + 2] << 8) |
+                (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff);
+            this.videoBuff!.setUint32(offset, color);
+            offset += PixelFormat.RGBA;
         }
+        this.canvasCtx!.putImageData(this.image!, 0, 0);
+        this.canvasScaledCtx!.drawImage(this.canvas!, 0, 0);
     }
-};
 
-const showToast = async (message: string, error = false, timeout = 3500) => {
-    const toast = document.getElementById("toast");
-    toast.classList.remove("error");
-    if (error) toast.classList.add("error");
-    toast.classList.add("visible");
-    toast.textContent = message;
-    if (state.toastTimeout) clearTimeout(state.toastTimeout);
-    state.toastTimeout = setTimeout(() => {
-        toast.classList.remove("visible");
-        state.toastTimeout = null;
-    }, timeout);
-};
+    async clearCanvas(
+        color = PIXEL_UNSET_COLOR,
+        {
+            image = null,
+            imageScale = 1
+        }: { image?: string | null; imageScale?: number } = {}
+    ) {
+        this.canvasScaledCtx!.fillStyle = `#${color
+            .toString(16)
+            .toUpperCase()}`;
+        this.canvasScaledCtx!.fillRect(
+            0,
+            0,
+            this.canvasScaled!.width,
+            this.canvasScaled!.height
+        );
 
-const showModal = async (
-    message: string,
-    title = "Alert"
-): Promise<boolean> => {
-    const modalContainer = document.getElementById("modal-container");
-    const modalTitle = document.getElementById("modal-title");
-    const modalText = document.getElementById("modal-text");
-    modalContainer.classList.add("visible");
-    modalTitle.textContent = title;
-    modalText.innerHTML = message.replace(/\n/g, "<br/>");
-    const result = (await new Promise((resolve) => {
-        global.modalCallback = resolve;
-    })) as boolean;
-    return result;
-};
+        // in case an image was requested then uses that to load
+        // an image at the center of the screen properly scaled
+        if (image) {
+            const img = await new Promise<HTMLImageElement>((resolve) => {
+                const img = new Image();
+                img.onload = () => {
+                    resolve(img);
+                };
+                img.src = image;
+            });
+            const [imgWidth, imgHeight] = [
+                img.width * imageScale * window.devicePixelRatio,
+                img.height * imageScale * window.devicePixelRatio
+            ];
+            const [x0, y0] = [
+                this.canvasScaled!.width / 2 - imgWidth / 2,
+                this.canvasScaled!.height / 2 - imgHeight / 2
+            ];
+            this.canvasScaledCtx!.setTransform(1, 0, 0, 1, 0, 0);
+            try {
+                this.canvasScaledCtx!.drawImage(
+                    img,
+                    x0,
+                    y0,
+                    imgWidth,
+                    imgHeight
+                );
+            } finally {
+                this.canvasScaledCtx!.scale(
+                    this.canvasScaled!.width / this.canvas!.width,
+                    this.canvasScaled!.height / this.canvas!.height
+                );
+            }
+        }
+    }
 
-const hideModal = async (result = true) => {
-    const modalContainer = document.getElementById("modal-container");
-    modalContainer.classList.remove("visible");
-    if (global.modalCallback) global.modalCallback(result);
-    global.modalCallback = null;
-};
+    async showToast(message: string, error = false, timeout = 3500) {
+        const toast = document.getElementById("toast")!;
+        toast.classList.remove("error");
+        if (error) toast.classList.add("error");
+        toast.classList.add("visible");
+        toast.textContent = message;
+        if (this.toastTimeout) clearTimeout(this.toastTimeout);
+        this.toastTimeout = setTimeout(() => {
+            toast.classList.remove("visible");
+            this.toastTimeout = null;
+        }, timeout);
+    }
 
-const setVersion = (value: string) => {
-    document.getElementById("version").textContent = value;
-};
+    async showModal(message: string, title = "Alert"): Promise<boolean> {
+        const modalContainer = document.getElementById("modal-container")!;
+        const modalTitle = document.getElementById("modal-title")!;
+        const modalText = document.getElementById("modal-text")!;
+        modalContainer.classList.add("visible");
+        modalTitle.textContent = title;
+        modalText.innerHTML = message.replace(/\n/g, "<br/>");
+        const result = (await new Promise((resolve) => {
+            global.modalCallback = resolve;
+        })) as boolean;
+        return result;
+    }
 
-const setEngine = (name: string, upper = true) => {
-    name = upper ? name.toUpperCase() : name;
-    document.getElementById("engine").textContent = name;
-};
+    async hideModal(result = true) {
+        const modalContainer = document.getElementById("modal-container")!;
+        modalContainer.classList.remove("visible");
+        if (global.modalCallback) global.modalCallback(result);
+        global.modalCallback = null;
+    }
 
-const setRom = (name: string, data: Uint8Array) => {
-    state.romName = name;
-    state.romData = data;
-    state.romSize = data.length;
-    document.getElementById("rom-name").textContent = name;
-    document.getElementById("rom-size").textContent = String(data.length);
-};
+    setVersion(value: string) {
+        document.getElementById("version")!.textContent = value;
+    }
 
-const setLogicFrequency = (value: number) => {
-    if (value < 0) showToast("Invalid frequency value!", true);
-    value = Math.max(value, 0);
-    state.logicFrequency = value;
-    document.getElementById("logic-frequency").textContent = String(value);
-};
+    setEngine(name: string, upper = true) {
+        name = upper ? name.toUpperCase() : name;
+        document.getElementById("engine")!.textContent = name;
+    }
 
-const setFps = (value: number) => {
-    if (value < 0) showToast("Invalid FPS value!", true);
-    value = Math.max(value, 0);
-    state.fps = value;
-    document.getElementById("fps-count").textContent = String(value);
-};
+    setRom(name: string, data: Uint8Array) {
+        this.romName = name;
+        this.romData = data;
+        this.romSize = data.length;
+        //@todo update this one
+        //Component.get<KeyValue>("diag:rom-name").value = name;
+        //Component.get<KeyValue>("diag:rom-size").value = `${data.length} bytes`;
+    }
 
-const setBackground = (value: string) => {
-    document.body.style.backgroundColor = `#${value}`;
-    document.getElementById(
-        "footer-background"
-    ).style.backgroundColor = `#${value}f2`;
-};
+    setLogicFrequency(value: number) {
+        if (value < 0) this.showToast("Invalid frequency value!", true);
+        value = Math.max(value, 0);
+        this.logicFrequency = value;
+        document.getElementById("logic-frequency")!.textContent = String(value);
+    }
 
-const toggleRunning = () => {
-    if (state.paused) {
-        resume();
-    } else {
-        pause();
+    setFps(value: number) {
+        if (value < 0) this.showToast("Invalid FPS value!", true);
+        value = Math.max(value, 0);
+        this.fps = value;
+        //@todo
+        //Component.get<KeyValue>("diag:framerate").value = `${value} FPS`;
     }
-};
 
-const pause = () => {
-    state.paused = true;
-    const buttonPause = document.getElementById("button-pause");
-    const img = buttonPause.getElementsByTagName("img")[0];
-    const span = buttonPause.getElementsByTagName("span")[0];
-    buttonPause.classList.add("enabled");
-    // @ts-ignore: ts(2580)
-    img.src = require("./res/play.svg");
-    span.textContent = "Resume";
-};
+    setBackground(value: string) {
+        document.body.style.backgroundColor = `#${value}`;
+        document.getElementById(
+            "footer-background"
+        )!.style.backgroundColor = `#${value}f2`;
+    }
 
-const resume = () => {
-    state.paused = false;
-    state.nextTickTime = new Date().getTime();
-    const buttonPause = document.getElementById("button-pause");
-    const img = buttonPause.getElementsByTagName("img")[0];
-    const span = buttonPause.getElementsByTagName("span")[0];
-    buttonPause.classList.remove("enabled");
-    // @ts-ignore: ts(2580)
-    img.src = require("./res/pause.svg");
-    span.textContent = "Pause";
-};
+    toggleRunning() {
+        if (this.paused) {
+            this.resume();
+        } else {
+            this.pause();
+        }
+    }
 
-const toggleWindow = () => {
-    maximize();
-};
+    pause() {
+        this.paused = true;
+        const buttonPause = document.getElementById("button-pause")!;
+        const img = buttonPause.getElementsByTagName("img")[0];
+        const span = buttonPause.getElementsByTagName("span")[0];
+        buttonPause.classList.add("enabled");
+        img.src = require("./res/play.svg");
+        span.textContent = "Resume";
+    }
 
-const maximize = () => {
-    const canvasContainer = document.getElementById("canvas-container");
-    canvasContainer.classList.add("fullscreen");
+    resume() {
+        this.paused = false;
+        this.nextTickTime = new Date().getTime();
+        const buttonPause = document.getElementById("button-pause")!;
+        const img = buttonPause.getElementsByTagName("img")[0];
+        const span = buttonPause.getElementsByTagName("span")[0];
+        buttonPause.classList.remove("enabled");
+        img.src = require("./res/pause.svg");
+        span.textContent = "Pause";
+    }
 
-    window.addEventListener("resize", crop);
+    /**
+     * Resets the emulator machine to the start state and loads
+     * the ROM that is currently set in the emulator.
+     */
+    reset() {
+        this.start({ engine: null });
+    }
 
-    crop();
+    toggleWindow() {
+        this.maximize();
+    }
+
+    /**
+     * Maximizes the emulator's viewport taking up all the available
+     * window space. This method is responsible for keeping the aspect
+     * ratio of the emulator canvas according to the width/height ratio.
+     */
+    maximize() {
+        const canvasContainer = document.getElementById("canvas-container")!;
+        canvasContainer.classList.add("fullscreen");
+
+        window.addEventListener("resize", this.crop);
+
+        this.crop();
+    }
+
+    /**
+     * Restore the emulator's viewport to the minimal size, should make all
+     * the other emulator's meta-information (info, buttons, etc.) visible.
+     */
+    minimize() {
+        const canvasContainer = document.getElementById("canvas-container")!;
+        const engineCanvas = document.getElementById("engine-canvas")!;
+        canvasContainer.classList.remove("fullscreen");
+        engineCanvas.style.width = "";
+        engineCanvas.style.height = "";
+        window.removeEventListener("resize", this.crop);
+    }
+
+    crop() {
+        // @todo let's make this more flexible
+        const engineCanvas = document.getElementById("engine-canvas")!;
+
+        // calculates the window ratio as this is fundamental to
+        // determine the proper way to crop the fullscreen
+        const windowRatio = window.innerWidth / window.innerHeight;
+
+        // in case the window is wider (more horizontal than the base ratio)
+        // this means that we must crop horizontally
+        if (windowRatio > DISPLAY_RATIO) {
+            engineCanvas.style.width = `${
+                window.innerWidth * (DISPLAY_RATIO / windowRatio)
+            }px`;
+            engineCanvas.style.height = `${window.innerHeight}px`;
+        } else {
+            engineCanvas.style.width = `${window.innerWidth}px`;
+            engineCanvas.style.height = `${
+                window.innerHeight * (windowRatio / DISPLAY_RATIO)
+            }px`;
+        }
+    }
+
+    async fetchRom(romPath: string): Promise<[string, Uint8Array]> {
+        // extracts the name of the ROM from the provided
+        // path by splitting its structure
+        const romPathS = romPath.split(/\//g);
+        let romName = romPathS[romPathS.length - 1].split("?")[0];
+        const romNameS = romName.split(/\./g);
+        romName = `${romNameS[0]}.${romNameS[romNameS.length - 1]}`;
+
+        // loads the ROM data and converts it into the
+        // target byte array buffer (to be used by WASM)
+        const response = await fetch(ROM_PATH);
+        const blob = await response.blob();
+        const arrayBuffer = await blob.arrayBuffer();
+        const romData = new Uint8Array(arrayBuffer);
+
+        // returns both the name of the ROM and the data
+        // contents as a byte array
+        return [romName, romData];
+    }
+}
+
+type Global = {
+    modalCallback: Function | null;
 };
 
-const minimize = () => {
-    const canvasContainer = document.getElementById("canvas-container");
-    const engineCanvas = document.getElementById("engine-canvas");
-    canvasContainer.classList.remove("fullscreen");
-    engineCanvas.style.width = null;
-    engineCanvas.style.height = null;
-    window.removeEventListener("resize", crop);
+//@todo check if this is really required
+const global: Global = {
+    modalCallback: null
 };
 
-const crop = () => {
-    const engineCanvas = document.getElementById("engine-canvas");
-
-    // calculates the window ratio as this is fundamental to
-    // determine the proper way to crop the fulscreen
-    const windowRatio = window.innerWidth / window.innerHeight;
-
-    // in case the window is wider (more horizontal than the base ratio)
-    // this means that we must crop horizontaly
-    if (windowRatio > DISPLAY_RATIO) {
-        engineCanvas.style.width = `${
-            window.innerWidth * (DISPLAY_RATIO / windowRatio)
-        }px`;
-        engineCanvas.style.height = `${window.innerHeight}px`;
-    } else {
-        engineCanvas.style.width = `${window.innerWidth}px`;
-        engineCanvas.style.height = `${
-            window.innerHeight * (windowRatio / DISPLAY_RATIO)
-        }px`;
+declare global {
+    interface Window {
+        panic: (message: string) => void;
     }
-};
+}
 
-const reset = () => {
-    start({ engine: null });
+window.panic = (message: string) => {
+    console.error(message);
 };
 
-const fetchRom = async (romPath: string): Promise<[string, Uint8Array]> => {
-    // extracts the name of the ROM from the provided
-    // path by splitting its structure
-    const romPathS = romPath.split(/\//g);
-    let romName = romPathS[romPathS.length - 1].split("?")[0];
-    const romNameS = romName.split(/\./g);
-    romName = `${romNameS[0]}.${romNameS[romNameS.length - 1]}`;
-
-    // loads the ROM data and converts it into the
-    // target byte array buffer (to be used by WASM)
-    const response = await fetch(ROM_PATH);
-    const blob = await response.blob();
-    const arrayBuffer = await blob.arrayBuffer();
-    const romData = new Uint8Array(arrayBuffer);
-
-    // returns both the name of the ROM and the data
-    // contents as a byte array
-    return [romName, romData];
+const wasm = async () => {
+    await _wasm();
+    GameBoy.set_panic_hook_ws();
 };
 
 (async () => {
-    await main();
+    startApp("app");
+
+    const emulator = new Emulator();
+    await emulator.main();
 })();
diff --git a/examples/web/package.json b/examples/web/package.json
index 3b25a29a46c25f7a5e97cc0e436f3f5220333503..a956b883f06a58671732d1cff3f19fff1b0274ed 100644
--- a/examples/web/package.json
+++ b/examples/web/package.json
@@ -10,15 +10,20 @@
     "scripts": {
         "build": "parcel build index.html",
         "dev": "parcel index.html",
-        "pretty": "prettier --config .prettierrc \"./**/*.{ts,json}\" --write",
+        "pretty": "prettier --config .prettierrc \"./**/*.{ts,tsx,json}\" --write",
         "start": "npm run build",
         "watch": "parcel watch index.html"
     },
     "source": "index.ts",
     "devDependencies": {
-        "@parcel/transformer-typescript-tsc": "^2.6.2",
-        "parcel": "^2.6.2",
+        "@parcel/transformer-typescript-tsc": "^2.7.0",
+        "@types/react": "^18.0.21",
+        "@types/react-dom": "^18.0.6",
+        "parcel": "^2.7.0",
         "prettier": "^2.7.1",
-        "typescript": "^4.7.4"
+        "process": "^0.11.10",
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0",
+        "typescript": "^4.8.4"
     }
 }
diff --git a/examples/web/react/app.css b/examples/web/react/app.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..27f27484a5077017473cd211b8881337841374fd
--- /dev/null
+++ b/examples/web/react/app.tsx
@@ -0,0 +1,122 @@
+import React, { useState } from "react";
+import ReactDOM from "react-dom/client";
+
+import {
+    Button,
+    ButtonIncrement,
+    ButtonSwitch,
+    Info,
+    Link,
+    Pair,
+    PanelSplit,
+    Paragraph,
+    Section,
+    Title
+} from "./components";
+
+import "./app.css";
+
+export const App = () => {
+    const [count, setCount] = useState(0);
+    const getText = () => `Hello World ${count}`;
+    const onClick = () => setCount(count + 1);
+    return (
+        <>
+            <PanelSplit left={<div>This is the left panel</div>}>
+                <Title
+                    text="Boytacean"
+                    version="0.3.0"
+                    versionUrl="https://gitlab.stage.hive.pt/joamag/boytacean/-/blob/master/CHANGELOG.md"
+                    iconSrc={require("../res/thunder.png")}
+                ></Title>
+                <Section>
+                    <Paragraph>
+                        This is a{" "}
+                        <Link
+                            href="https://en.wikipedia.org/wiki/Game_Boy"
+                            target="_blank"
+                        >
+                            Game Boy
+                        </Link>{" "}
+                        emulator built using the{" "}
+                        <Link href="https://www.rust-lang.org" target="_blank">
+                            Rust Programming Language
+                        </Link>{" "}
+                        and is running inside this browser with the help of{" "}
+                        <Link href="https://webassembly.org/" target="_blank">
+                            WebAssembly
+                        </Link>
+                        .
+                    </Paragraph>
+                    <Paragraph>
+                        You can check the source code of it at{" "}
+                        <Link
+                            href="https://gitlab.stage.hive.pt/joamag/boytacean"
+                            target="_blank"
+                        >
+                            GitLab
+                        </Link>
+                        .
+                    </Paragraph>
+                    <Paragraph>
+                        TIP: Drag and Drop ROM files to the Browser to load the
+                        ROM.
+                    </Paragraph>
+                </Section>
+                <Section>
+                    <Button text={getText()} onClick={onClick} />
+                    <Button
+                        text={getText()}
+                        image={require("../res/pause.svg")}
+                        imageAlt="tobias"
+                        onClick={onClick}
+                    />
+                    <Info>
+                        <Pair
+                            key="tobias"
+                            name={"Tobias"}
+                            value={`Count ${count}`}
+                        />
+                        <Pair key="matias" name={"Matias"} value={"3"} />
+                        <Pair
+                            key="button-tobias"
+                            name={"Button Increment"}
+                            valueNode={
+                                <ButtonIncrement
+                                    value={200}
+                                    delta={100}
+                                    min={0}
+                                    suffix={"Hz"}
+                                />
+                            }
+                        />
+                        <Pair
+                            key="button-cpu"
+                            name={"Button Switch"}
+                            valueNode={
+                                <ButtonSwitch
+                                    options={["NEO", "CLASSIC"]}
+                                    size={"large"}
+                                    style={["simple"]}
+                                    onChange={(v) => alert(v)}
+                                />
+                            }
+                        />
+                    </Info>
+                </Section>
+            </PanelSplit>
+        </>
+    );
+};
+
+export const startApp = (element: string) => {
+    const elementRef = document.getElementById(element);
+    if (!elementRef) {
+        return;
+    }
+
+    const root = ReactDOM.createRoot(elementRef);
+    root.render(<App />);
+};
+
+export default App;
diff --git a/examples/web/react/components/button-increment/button-increment.css b/examples/web/react/components/button-increment/button-increment.css
new file mode 100644
index 0000000000000000000000000000000000000000..57f94577dd1a79df31fcae63964153ab1de88fcc
--- /dev/null
+++ b/examples/web/react/components/button-increment/button-increment.css
@@ -0,0 +1,15 @@
+.button-increment {
+    display: inline-block;
+}
+
+.button-increment > .value {
+    margin: 0px 8px 0px 8px;
+}
+
+.button-increment > .prefix {
+    margin-left: 8px;
+}
+
+.button-increment > .suffix {
+    margin-right: 8px;
+}
diff --git a/examples/web/react/components/button-increment/button-increment.tsx b/examples/web/react/components/button-increment/button-increment.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a4395b771baa97b2319bc081616abf6307b7845d
--- /dev/null
+++ b/examples/web/react/components/button-increment/button-increment.tsx
@@ -0,0 +1,77 @@
+import React, { FC, useState } from "react";
+import Button from "../button/button";
+
+import "./button-increment.css";
+
+type ButtonIncrementProps = {
+    value: number;
+    delta?: number;
+    min?: number;
+    max?: number;
+    prefix?: string;
+    suffix?: string;
+    size?: string;
+    style?: string[];
+    onClick?: () => void;
+    onBeforeChange?: (value: number) => boolean;
+    onChange?: (value: number) => void;
+};
+
+export const ButtonIncrement: FC<ButtonIncrementProps> = ({
+    value,
+    delta = 1,
+    min,
+    max,
+    prefix,
+    suffix,
+    size = "medium",
+    style = ["simple", "border"],
+    onClick,
+    onBeforeChange,
+    onChange
+}) => {
+    const [valueState, setValue] = useState(value);
+    const classes = () => ["button-increment", size, ...style].join(" ");
+    const _onClick = () => {
+        if (onClick) onClick();
+    };
+    const _onMinusClick = () => {
+        const valueNew = valueState - delta;
+        if (onBeforeChange) {
+            if (!onBeforeChange(valueNew)) return;
+        }
+        if (min !== undefined && valueNew < min) return;
+        setValue(valueNew);
+        if (onChange) onChange(valueNew);
+    };
+    const _onPlusClick = () => {
+        const valueNew = valueState + delta;
+        if (onBeforeChange) {
+            if (!onBeforeChange(valueNew)) return;
+        }
+        if (max !== undefined && valueNew > max) return;
+        setValue(valueNew);
+        if (onChange) onChange(valueNew);
+    };
+    return (
+        <span className={classes()} onClick={_onClick}>
+            <Button
+                text={"-"}
+                size={size}
+                style={["simple"]}
+                onClick={_onMinusClick}
+            />
+            {prefix && <span className="prefix">{prefix}</span>}
+            <span className="value">{valueState}</span>
+            {suffix && <span className="suffix">{suffix}</span>}
+            <Button
+                text={"+"}
+                size={size}
+                style={["simple"]}
+                onClick={_onPlusClick}
+            />
+        </span>
+    );
+};
+
+export default ButtonIncrement;
diff --git a/examples/web/react/components/button-switch/button-switch.tsx b/examples/web/react/components/button-switch/button-switch.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..54c5bd272985ad8eb809eeb0aafdcca91de30e85
--- /dev/null
+++ b/examples/web/react/components/button-switch/button-switch.tsx
@@ -0,0 +1,33 @@
+import React, { FC, useState } from "react";
+import Button from "../button/button";
+
+type ButtonSwitchProps = {
+    options: string[];
+    size?: string;
+    style?: string[];
+    onClick?: () => void;
+    onChange?: (value: string, index: number) => void;
+};
+
+export const ButtonSwitch: FC<ButtonSwitchProps> = ({
+    options,
+    size = "small",
+    style = ["simple", "border"],
+    onClick,
+    onChange
+}) => {
+    const [index, setIndex] = useState(0);
+    const text = () => options[index];
+    const _onClick = () => {
+        const indexNew = (index + 1) % options.length;
+        const option = options[indexNew];
+        setIndex(indexNew);
+        if (onClick) onClick();
+        if (onChange) onChange(option, indexNew);
+    };
+    return (
+        <Button text={text()} size={size} style={style} onClick={_onClick} />
+    );
+};
+
+export default ButtonSwitch;
diff --git a/examples/web/react/components/button/button.css b/examples/web/react/components/button/button.css
new file mode 100644
index 0000000000000000000000000000000000000000..67b45263526fd7fb20cc9ac9d4cf02ae6628441c
--- /dev/null
+++ b/examples/web/react/components/button/button.css
@@ -0,0 +1,114 @@
+.button {
+    cursor: pointer;
+    display: inline-flex;
+    vertical-align: middle;
+}
+
+.button.small {
+    font-size: 16px;
+    line-height: 24px;
+}
+
+.button.simple {
+    border-radius: 96px 96px 96px 96px;
+    -o-border-radius: 96px 96px 96px 96px;
+    -ms-border-radius: 96px 96px 96px 96px;
+    -moz-border-radius: 96px 96px 96px 96px;
+    -khtml-border-radius: 96px 96px 96px 96px;
+    -webkit-border-radius: 96px 96px 96px 96px;
+    padding: 0px 8px 0px 8px;
+    user-select: none;
+    -o-user-select: none;
+    -ms-user-select: none;
+    -moz-user-select: none;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+}
+
+.button.simple.border {
+    border: 1px solid #ffffff;
+}
+
+.button.simple.padded {
+    padding: 4px 10px 4px 10px;
+}
+
+.button.simple.padded-large {
+    padding: 4px 14px 4px 14px;
+}
+
+.button.simple.rounded {
+    padding: 6px 6px 6px 6px;
+}
+
+.button.simple.enabled {
+    background-color: #50cb93;
+}
+
+.button.simple.file {
+    position: relative;
+}
+
+.button.simple:hover {
+    background-color: #50cb93;
+}
+
+.button.simple.red:hover {
+    background-color: #e63946;
+}
+
+.button.simple:active {
+    background-color: #2a9d8f;
+}
+
+.button.simple.red:active {
+    background-color: #bf2a37;
+}
+
+.button.simple > img {
+    margin-right: 6px;
+    vertical-align: middle;
+    width: 13px;
+}
+
+.button.simple > img.medium {
+    width: 20px;
+}
+
+.button.simple > img.large {
+    width: 28px;
+}
+
+.button.simple > img.very-large {
+    width: 38px;
+}
+
+.button.simple > span {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.button.simple.no-text > img {
+    margin-right: 0px;
+    margin-top: 0px;
+}
+
+.button.simple.file > input[type="file"] {
+    cursor: pointer;
+    height: 100%;
+    left: 0px;
+    opacity: 0;
+    -o-opacity: 0;
+    -ms-opacity: 0;
+    -moz-opacity: 0;
+    -khtml-opacity: 0;
+    -webkit-opacity: 0;
+    position: absolute;
+    top: 0px;
+    vertical-align: top;
+    width: 100%;
+}
+
+.button.simple.file > input[type="file"]::-webkit-file-upload-button {
+    cursor: pointer;
+}
diff --git a/examples/web/react/components/button/button.tsx b/examples/web/react/components/button/button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7858e7ee1cb1a8d6a50c89201d6921cb7f38b947
--- /dev/null
+++ b/examples/web/react/components/button/button.tsx
@@ -0,0 +1,38 @@
+import React, { FC } from "react";
+
+import "./button.css";
+
+type ButtonProps = {
+    text: string;
+    image?: string;
+    imageAlt?: string;
+    size?: string;
+    style?: string[];
+    onClick?: () => void;
+};
+
+export const Button: FC<ButtonProps> = ({
+    text,
+    image,
+    imageAlt,
+    size = "small",
+    style = ["simple", "border"],
+    onClick
+}) => {
+    const classes = () => ["button", size, ...style].join(" ");
+    const _onClick = () => (onClick ? onClick() : undefined);
+    const buttonSimple = () => (
+        <span className={classes()} onClick={_onClick}>
+            {text}
+        </span>
+    );
+    const buttonImage = () => (
+        <span className={classes()} onClick={_onClick}>
+            <img src={image} alt={imageAlt} />
+            <span>{text}</span>
+        </span>
+    );
+    return image ? buttonImage() : buttonSimple();
+};
+
+export default Button;
diff --git a/examples/web/react/components/index.ts b/examples/web/react/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a9e3046a2fe02117875e4641023a1cc4693a6320
--- /dev/null
+++ b/examples/web/react/components/index.ts
@@ -0,0 +1,10 @@
+export * from "./button/button";
+export * from "./button-increment/button-increment";
+export * from "./button-switch/button-switch";
+export * from "./info/info";
+export * from "./link/link";
+export * from "./pair/pair";
+export * from "./panel-split/panel-split";
+export * from "./paragraph/paragraph";
+export * from "./section/section";
+export * from "./title/title";
diff --git a/examples/web/react/components/info/info.css b/examples/web/react/components/info/info.css
new file mode 100644
index 0000000000000000000000000000000000000000..cf1924233a9aa1055ff043584ced54dac9431830
--- /dev/null
+++ b/examples/web/react/components/info/info.css
@@ -0,0 +1,29 @@
+.info {
+    font-size: 24px;
+    vertical-align: top;
+}
+
+.info > dt {
+    clear: both;
+    float: left;
+    margin-top: 12px;
+}
+
+.info > dt:first-of-type {
+    margin-top: 0px;
+}
+
+.info > dd {
+    float: right;
+    margin-top: 12px;
+}
+
+.info > dd:first-of-type {
+    margin-top: 0px;
+}
+
+.info::after {
+    clear: both;
+    content: '';
+    display: block;
+}
diff --git a/examples/web/react/components/info/info.tsx b/examples/web/react/components/info/info.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a899b91c8e89571c6e58e71819a46ae6ebb20ee0
--- /dev/null
+++ b/examples/web/react/components/info/info.tsx
@@ -0,0 +1,26 @@
+import React, { FC, ReactNode } from "react";
+
+import "./info.css";
+
+type InfoProps = {
+    children: ReactNode;
+    style?: string[];
+};
+
+/**
+ * Builds a new info component with the provided pairs components
+ * setting the style in accordance with the provided list of strings.
+ *
+ * An info component is responsible for the management of multiple
+ * key to "value" pairs.
+ *
+ * @param options The multiple options that are going to be used
+ * to build the info pairs.
+ * @returns The info component with the associated pairs.
+ */
+export const Info: FC<InfoProps> = ({ children, style = [] }) => {
+    const classes = () => ["info", ...style].join(" ");
+    return <dl className={classes()}>{children}</dl>;
+};
+
+export default Info;
diff --git a/examples/web/react/components/link/link.css b/examples/web/react/components/link/link.css
new file mode 100644
index 0000000000000000000000000000000000000000..f04c472e5ad124f21555b1d948ad0350d697573a
--- /dev/null
+++ b/examples/web/react/components/link/link.css
@@ -0,0 +1,5 @@
+.link {
+    border-bottom: 2px dotted #ffffff;
+    color: #ffffff;
+    text-decoration: none;
+}
diff --git a/examples/web/react/components/link/link.tsx b/examples/web/react/components/link/link.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..65046383f1471abf35720c8211e5b8f0019d04d2
--- /dev/null
+++ b/examples/web/react/components/link/link.tsx
@@ -0,0 +1,28 @@
+import React, { ReactNode, FC } from "react";
+
+import "./link.css";
+
+type LinkProps = {
+    children?: ReactNode;
+    text?: string;
+    href?: string;
+    target?: string;
+    style?: string[];
+};
+
+export const Link: FC<LinkProps> = ({
+    children,
+    text,
+    href,
+    target,
+    style = []
+}) => {
+    const classes = () => ["link", ...style].join(" ");
+    return (
+        <a className={classes()} href={href} target={target}>
+            {children || text}
+        </a>
+    );
+};
+
+export default Link;
diff --git a/examples/web/react/components/pair/pair.css b/examples/web/react/components/pair/pair.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/web/react/components/pair/pair.tsx b/examples/web/react/components/pair/pair.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9e032e85bd60d460f2284b28b3b0496a95eb6951
--- /dev/null
+++ b/examples/web/react/components/pair/pair.tsx
@@ -0,0 +1,37 @@
+import React, { FC, ReactNode } from "react";
+
+import "./pair.css";
+
+type PairProps = {
+    name: string;
+    value?: string;
+    valueNode?: ReactNode;
+    style?: string[];
+    onNameClick?: () => void;
+    onValueClick?: () => void;
+};
+
+export const Pair: FC<PairProps> = ({
+    name,
+    value,
+    valueNode,
+    style = [],
+    onNameClick,
+    onValueClick
+}) => {
+    const classes = () => ["pair", ...style].join(" ");
+    const _onNameClick = () => (onNameClick ? onNameClick() : undefined);
+    const _onValueClick = () => (onValueClick ? onValueClick() : undefined);
+    return (
+        <>
+            <dt className={classes()} onClick={_onNameClick}>
+                {name}
+            </dt>
+            <dd className={classes()} onClick={_onValueClick}>
+                {valueNode ?? value ?? ""}
+            </dd>
+        </>
+    );
+};
+
+export default Pair;
diff --git a/examples/web/react/components/panel-split/panel-split.css b/examples/web/react/components/panel-split/panel-split.css
new file mode 100644
index 0000000000000000000000000000000000000000..88edf704fcd6e49a1661e1496a2aa8cb9f632066
--- /dev/null
+++ b/examples/web/react/components/panel-split/panel-split.css
@@ -0,0 +1,17 @@
+.panel-split {
+    display: flex;
+}
+
+.panel-split > .side-left {
+    display: flex;
+    flex: 1 0;
+    justify-content: center;
+    text-align: center;
+}
+
+.panel-split > .side-right {
+    flex: 0;
+    max-width: 100%;
+    min-width: 580px;
+    padding: 0px 24px 0px 24px;
+}
diff --git a/examples/web/react/components/panel-split/panel-split.tsx b/examples/web/react/components/panel-split/panel-split.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f394bbbda51f08f37be90134222bbe77ec4d9004
--- /dev/null
+++ b/examples/web/react/components/panel-split/panel-split.tsx
@@ -0,0 +1,27 @@
+import React, { FC, ReactNode } from "react";
+
+import "./panel-split.css";
+
+type PanelSplitProps = {
+    children?: ReactNode;
+    left?: ReactNode;
+    right?: ReactNode;
+    style?: string[];
+};
+
+export const PanelSplit: FC<PanelSplitProps> = ({
+    children,
+    left,
+    right,
+    style = []
+}) => {
+    const classes = () => ["panel-split", ...style].join(" ");
+    return (
+        <div className={classes()}>
+            <div className="side-left">{left}</div>
+            <div className="side-right">{children || right}</div>
+        </div>
+    );
+};
+
+export default PanelSplit;
diff --git a/examples/web/react/components/paragraph/paragraph.css b/examples/web/react/components/paragraph/paragraph.css
new file mode 100644
index 0000000000000000000000000000000000000000..4d6c5b875a58e6544470c7c904adec5dfac3797a
--- /dev/null
+++ b/examples/web/react/components/paragraph/paragraph.css
@@ -0,0 +1,5 @@
+.paragraph {
+    font-size: 18px;
+    line-height: 24px;
+    margin: 12px 0px 12px 0px;
+}
diff --git a/examples/web/react/components/paragraph/paragraph.tsx b/examples/web/react/components/paragraph/paragraph.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..aeaa51dce2bb318940410a8f276ecd4814618fdc
--- /dev/null
+++ b/examples/web/react/components/paragraph/paragraph.tsx
@@ -0,0 +1,20 @@
+import React, { ReactNode, FC } from "react";
+
+import "./paragraph.css";
+
+type ParagraphProps = {
+    children?: ReactNode;
+    text?: string;
+    style?: string[];
+};
+
+export const Paragraph: FC<ParagraphProps> = ({
+    children,
+    text,
+    style = []
+}) => {
+    const classes = () => ["paragraph", ...style].join(" ");
+    return <p className={classes()}>{children || text}</p>;
+};
+
+export default Paragraph;
diff --git a/examples/web/react/components/section/section.css b/examples/web/react/components/section/section.css
new file mode 100644
index 0000000000000000000000000000000000000000..c5ce8fe250b7b3ba2bc339ae4b90f812200bbb0f
--- /dev/null
+++ b/examples/web/react/components/section/section.css
@@ -0,0 +1,5 @@
+.section > .separator {
+    background: #ffffff;
+    height: 2px;
+    margin: 22px 0px 22px 0px;
+}
diff --git a/examples/web/react/components/section/section.tsx b/examples/web/react/components/section/section.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..91a84184a041858745c50f06805b13b4521d6c25
--- /dev/null
+++ b/examples/web/react/components/section/section.tsx
@@ -0,0 +1,25 @@
+import React, { FC, ReactNode } from "react";
+
+import "./section.css";
+
+type SectionProps = {
+    children: ReactNode;
+    separator?: boolean;
+    style?: string[];
+};
+
+export const Section: FC<SectionProps> = ({
+    children,
+    separator = true,
+    style = []
+}) => {
+    const classes = () => ["section", ...style].join(" ");
+    return (
+        <div className={classes()}>
+            {separator && <div className="separator"></div>}
+            <div className="section-contents">{children}</div>
+        </div>
+    );
+};
+
+export default Section;
diff --git a/examples/web/react/components/title/title.css b/examples/web/react/components/title/title.css
new file mode 100644
index 0000000000000000000000000000000000000000..b10f9b68cd6195975c92f801645d7c0e937e8448
--- /dev/null
+++ b/examples/web/react/components/title/title.css
@@ -0,0 +1,9 @@
+.title > .link {
+    margin-left: 14px;
+}
+
+.title > .icon {
+    margin-left: 14px;
+    vertical-align: middle;
+    width: 32px;
+}
diff --git a/examples/web/react/components/title/title.tsx b/examples/web/react/components/title/title.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c390617521d3a422055466254620d13ef61373bd
--- /dev/null
+++ b/examples/web/react/components/title/title.tsx
@@ -0,0 +1,35 @@
+import React, { FC } from "react";
+import { Link } from "../link/link";
+
+import "./title.css";
+
+type TitleProps = {
+    text: string;
+    version?: string;
+    versionUrl?: string;
+    iconSrc?: string;
+    style?: string[];
+};
+
+export const Title: FC<TitleProps> = ({
+    text,
+    version,
+    versionUrl,
+    iconSrc,
+    style = []
+}) => {
+    const classes = () => ["title", ...style].join(" ");
+    return (
+        <h1 className={classes()}>
+            {text}
+            {version && (
+                <Link href={versionUrl} target="_blank">
+                    {version}
+                </Link>
+            )}
+            {iconSrc && <img className="icon" src={iconSrc} alt="icon" />}
+        </h1>
+    );
+};
+
+export default Title;
diff --git a/examples/web/tsconfig.json b/examples/web/tsconfig.json
index 54b8f23f78f97da010f950ac602328134bcce54a..8f83dd8e31c5ab0b458c75bafaedad1b74ca9698 100644
--- a/examples/web/tsconfig.json
+++ b/examples/web/tsconfig.json
@@ -6,7 +6,14 @@
         "allowSyntheticDefaultImports": true,
         "target": "es6",
         "noImplicitAny": true,
+        "noImplicitThis": true,
+        "alwaysStrict": true,
+        "strictBindCallApply": true,
+        "strictNullChecks": true,
+        "strictFunctionTypes": true,
+        "strictPropertyInitialization": true,
         "sourceMap": true,
+        "jsx": "react",
         "outDir": ".",
         "baseUrl": ".",
         "lib": ["es2017", "dom"],
diff --git a/src/gb.rs b/src/gb.rs
index 0785ea67cd02b915a0de35f04cb2641b3b03ff8e..2750414133440ad4878dbccefafdda3a04301b6b 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -205,11 +205,11 @@ impl GameBoy {
 #[wasm_bindgen]
 extern "C" {
     #[wasm_bindgen(js_namespace = window)]
-    fn panic(s: &str);
+    fn panic(message: &str);
 }
 
 #[cfg(feature = "wasm")]
 pub fn hook_impl(info: &PanicInfo) {
-    let msg = info.to_string();
-    panic(msg.as_str());
+    let message = info.to_string();
+    panic(message.as_str());
 }
diff --git a/src/ppu.rs b/src/ppu.rs
index 1b051ed176221667729f900eed3a2641e25aed7d..df6b83acedf529b7428bfa5b86db1005bfd39d60 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -704,7 +704,7 @@ impl Ppu {
         // index and the DY (scroll Y) divided by 8 (as the tiles are 8x8 pixels),
         // on top of that ensures that the result is modulus 32 meaning that the
         // drawing wraps around the Y axis
-        let row_offset = (((ld + scy) & 0xff) >> 3) % 32;
+        let row_offset = (((ld as usize + scy as usize) & 0xff) >> 3) % 32;
 
         // obtains the base address of the background map using the bg map flag
         // that control which background map is going to be used
@@ -736,7 +736,7 @@ impl Ppu {
 
         // calculates both the current Y and X positions within the tiles
         // using the bitwise and operation as an effective modulus 8
-        let y = ((ld + scy) & 0x07) as usize;
+        let y = (ld as usize + scy as usize) & 0x07;
         let mut x = (scx & 0x07) as usize;
 
         for index in 0..DISPLAY_WIDTH {
@@ -749,7 +749,8 @@ impl Ppu {
                 let pixel = self.tiles[tile_index].get(x, y);
                 let color = self.palette[pixel as usize];
 
-                // updates the pixel in the color buffer
+                // updates the pixel in the color buffer, which stores
+                // the raw pixel color information (unmapped)
                 self.color_buffer[color_offset] = pixel;
 
                 // set the color pixel in the frame buffer
@@ -817,12 +818,16 @@ impl Ppu {
                 self.palette_obj_1
             };
 
-            let mut color_offset = self.ly as usize * DISPLAY_WIDTH + obj.x as usize;
+            // calculates the offset in the color buffer (raw color information
+            // from 0 to 3) for the sprit that is going to be drawn, this value
+            // is kept as a signed integer to allow proper negative number math
+            let mut color_offset = self.ly as i32 * DISPLAY_WIDTH as i32 + obj.x as i32;
 
             // calculates the offset in the frame buffer for the sprite
             // that is going to be drawn, this is going to be the starting
             // point for the draw operation to be performed
-            let mut frame_offset = (self.ly as usize * DISPLAY_WIDTH + obj.x as usize) * RGB_SIZE;
+            let mut frame_offset =
+                (self.ly as i32 * DISPLAY_WIDTH as i32 + obj.x as i32) * RGB_SIZE as i32;
 
             // the relative title offset should range from 0 to 7 in 8x8
             // objects and from 0 to 15 in 8x16 objects
@@ -863,7 +868,7 @@ impl Ppu {
                 if is_contained {
                     // the object is only considered visible if it's a priority
                     // or if the underlying pixel is transparent (zero value)
-                    let is_visible = obj.priority || self.color_buffer[color_offset] == 0;
+                    let is_visible = obj.priority || self.color_buffer[color_offset as usize] == 0;
                     let pixel = tile_row[if obj.xflip { 7 - x } else { x }];
                     if is_visible && pixel != 0 {
                         // obtains the current pixel data from the tile row and
@@ -871,9 +876,9 @@ impl Ppu {
                         let color = palette[pixel as usize];
 
                         // sets the color pixel in the frame buffer
-                        self.frame_buffer[frame_offset] = color[0];
-                        self.frame_buffer[frame_offset + 1] = color[1];
-                        self.frame_buffer[frame_offset + 2] = color[2];
+                        self.frame_buffer[frame_offset as usize] = color[0];
+                        self.frame_buffer[frame_offset as usize + 1] = color[1];
+                        self.frame_buffer[frame_offset as usize + 2] = color[2];
                     }
                 }
 
@@ -883,7 +888,7 @@ impl Ppu {
 
                 // increments the offset of the frame buffer by the
                 // size of an RGB pixel (which is 3 bytes)
-                frame_offset += RGB_SIZE;
+                frame_offset += RGB_SIZE as i32;
             }
         }
     }
diff --git a/src/rom.rs b/src/rom.rs
index b791a353a2fa3446284313d4873141f74d6512b1..42947b9ba6b0df83dd8397924725326103c1c6a3 100644
--- a/src/rom.rs
+++ b/src/rom.rs
@@ -45,9 +45,9 @@ pub enum RomType {
     Unknown = 0xef,
 }
 
-impl Display for RomType {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let str = match self {
+impl RomType {
+    pub fn description(&self) -> &'static str {
+        match self {
             RomType::RomOnly => "ROM Only",
             RomType::Mbc1 => "MBC1",
             RomType::Mbc1Ram => "MBC1 + RAM",
@@ -77,8 +77,13 @@ impl Display for RomType {
             RomType::HuC3 => "HuC3",
             RomType::HuC1RamBattery => "HuC1 + RAM + BATTERY",
             RomType::Unknown => "Unknown",
-        };
-        write!(f, "{}", str)
+        }
+    }
+}
+
+impl Display for RomType {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.description())
     }
 }
 
@@ -97,6 +102,21 @@ pub enum RomSize {
 }
 
 impl RomSize {
+    pub fn description(&self) -> &'static str {
+        match self {
+            RomSize::Size32K => "32 KB",
+            RomSize::Size64K => "64 KB",
+            RomSize::Size128K => "128 KB",
+            RomSize::Size256K => "256 KB",
+            RomSize::Size512K => "512 KB",
+            RomSize::Size1M => "1 MB",
+            RomSize::Size2M => "2 MB",
+            RomSize::Size4M => "4 MB",
+            RomSize::Size8M => "8 MB",
+            RomSize::SizeUnknown => "Unknown",
+        }
+    }
+
     pub fn rom_banks(&self) -> u16 {
         match self {
             RomSize::Size32K => 2,
@@ -115,19 +135,7 @@ impl RomSize {
 
 impl Display for RomSize {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let str = match self {
-            RomSize::Size32K => "32 KB",
-            RomSize::Size64K => "64 KB",
-            RomSize::Size128K => "128 KB",
-            RomSize::Size256K => "256 KB",
-            RomSize::Size512K => "512 KB",
-            RomSize::Size1M => "1 MB",
-            RomSize::Size2M => "2 MB",
-            RomSize::Size4M => "4 MB",
-            RomSize::Size8M => "8 MB",
-            RomSize::SizeUnknown => "Unknown",
-        };
-        write!(f, "{}", str)
+        write!(f, "{}", self.description())
     }
 }
 
@@ -143,6 +151,18 @@ pub enum RamSize {
 }
 
 impl RamSize {
+    pub fn description(&self) -> &'static str {
+        match self {
+            RamSize::NoRam => "No RAM",
+            RamSize::Unused => "Unused",
+            RamSize::Size8K => "8 KB",
+            RamSize::Size32K => "32 KB",
+            RamSize::Size128K => "128 KB",
+            RamSize::Size64K => "64 KB",
+            RamSize::SizeUnknown => "Unknown",
+        }
+    }
+
     pub fn ram_banks(&self) -> u16 {
         match self {
             RamSize::NoRam => 0,
@@ -158,16 +178,7 @@ impl RamSize {
 
 impl Display for RamSize {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let str = match self {
-            RamSize::NoRam => "No RAM",
-            RamSize::Unused => "Unused",
-            RamSize::Size8K => "8 KB",
-            RamSize::Size32K => "32 KB",
-            RamSize::Size128K => "128 KB",
-            RamSize::Size64K => "64 KB",
-            RamSize::SizeUnknown => "Unknown",
-        };
-        write!(f, "{}", str)
+        write!(f, "{}", self.description())
     }
 }
 
@@ -376,6 +387,18 @@ impl Cartridge {
             _ => RamSize::SizeUnknown,
         }
     }
+
+    pub fn rom_type_s(&self) -> String {
+        String::from(self.rom_type().description())
+    }
+
+    pub fn rom_size_s(&self) -> String {
+        String::from(self.rom_size().description())
+    }
+
+    pub fn ram_size_s(&self) -> String {
+        String::from(self.ram_size().description())
+    }
 }
 
 impl Display for Cartridge {