diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c3e07b9e26049c73f5bf89adbf83f93f334083e..bbcc71bbc4b239fee0a1d053d3adfa810e868798 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 *
 
+## [0.4.1] - 2022-11-06
+
+### Added
+
+* Logic frequency control using on click UI and keyboard
+* Support for on screen keyboard for Game Boy
+* Support for remote ROM loading using URL - [#3](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/3)
+
 ## [0.4.0] - 2022-11-01
 
 ### Added
diff --git a/Cargo.toml b/Cargo.toml
index 0d90a81bb287fdf92180c5d9a707f86bf3850f5d..fda46c6004ff25d43384067fad3f1b56f5af0d79 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "boytacean"
 description = "A Game Boy emulator that is written in Rust."
-version = "0.4.0"
+version = "0.4.1"
 authors = ["João Magalhães <joamag@gmail.com>"]
 license = "Apache-2.0"
 repository = "https://gitlab.stage.hive.pt/joamag/boytacean"
diff --git a/README.md b/README.md
index c335a31d80f17ac402dda3c5b528cc264d345419..dd8077ba3b31b08c74609b59f3f9e7363703a131 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,11 @@ cd dist && python3 -m http.server
 
 * [YouTube - The Ultimate Game Boy Talk (33c3)](https://www.youtube.com/watch?v=HyzD8pNlpwI)
 
+### Other
+
+* [GitHub - gbdev/awesome-gbdev](https://github.com/gbdev/awesome-gbdev)
+* [GitHub - Hacktix/Bootix](https://github.com/Hacktix/Bootix)
+
 ## License
 
 Boyacian is currently licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/).
diff --git a/examples/sdl/Cargo.toml b/examples/sdl/Cargo.toml
index 0c50371b7608b756ffeefdf6b45191188bd41829..ed2ae54d57afa13e4df15ed26ac841567cc95d73 100644
--- a/examples/sdl/Cargo.toml
+++ b/examples/sdl/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "boytacean-sdl"
-version = "0.4.0"
+version = "0.4.1"
 authors = ["João Magalhães <joamag@gmail.com>"]
 description = "Game Boy Emulator SDL (Desktop) Application"
 license = "Apache-2.0"
diff --git a/examples/web/index.css b/examples/web/index.css
index d8dd1b1daf19c6ae8772aadec8ad2ac13a9123ab..771be0c42ea9be25aae313c8b59c041e3faa17aa 100644
--- a/examples/web/index.css
+++ b/examples/web/index.css
@@ -24,6 +24,7 @@ a:hover {
 html {
     margin: 0px 0px 0px 0px;
     padding: 0px 0px 0px 0px;
+    touch-action: pan-x pan-y;
 }
 
 body {
diff --git a/examples/web/index.ts b/examples/web/index.ts
index cd8afa06d3e5d20999bd0bc491096e640098a657..4da7b07c62f02fba865a0bfefc4571d69d126b6e 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -1,6 +1,6 @@
 import {
     Emulator,
-    Observable,
+    EmulatorBase,
     PixelFormat,
     RomInfo,
     startApp
@@ -17,11 +17,11 @@ import info from "./package.json";
 
 declare const require: any;
 
-const LOGIC_HZ = 600;
-const VISUAL_HZ = 60;
+const LOGIC_HZ = 4194304;
+const VISUAL_HZ = 59.7275;
 const IDLE_HZ = 10;
 
-const FREQUENCY_DELTA = 60;
+const FREQUENCY_DELTA = 400000;
 
 const SAMPLE_RATE = 2;
 
@@ -46,6 +46,17 @@ const KEYS: Record<string, number> = {
     s: PadKey.B
 };
 
+const KEYS_NAME: Record<string, number> = {
+    ArrowUp: PadKey.Up,
+    ArrowDown: PadKey.Down,
+    ArrowLeft: PadKey.Left,
+    ArrowRight: PadKey.Right,
+    Start: PadKey.Start,
+    Select: PadKey.Select,
+    A: PadKey.A,
+    B: PadKey.B
+};
+
 const ARROW_KEYS: Record<string, boolean> = {
     ArrowUp: true,
     ArrowDown: true,
@@ -60,7 +71,7 @@ const ROM_PATH = require("../../res/roms/20y.gb");
  * and "joins" all the elements together to bring input/output
  * of the associated machine.
  */
-class GameboyEmulator extends Observable implements Emulator {
+class GameboyEmulator extends EmulatorBase implements Emulator {
     /**
      * The Game Boy engine (probably coming from WASM) that
      * is going to be used for the emulation.
@@ -71,7 +82,7 @@ class GameboyEmulator extends Observable implements Emulator {
      * The descriptive name of the engine that is currently
      * in use to emulate the system.
      */
-    private engine: string | null = null;
+    private _engine: string | null = null;
 
     private logicFrequency: number = LOGIC_HZ;
     private visualFrequency: number = VISUAL_HZ;
@@ -89,6 +100,11 @@ class GameboyEmulator extends Observable implements Emulator {
     private cartridge: Cartridge | null = null;
 
     async main() {
+        // parses the current location URL as retrieves
+        // some of the "relevant" GET parameters for logic
+        const params = new URLSearchParams(window.location.search);
+        const romUrl = params.get("url");
+
         // initializes the WASM module, this is required
         // so that the global symbols become available
         await wasm();
@@ -99,7 +115,7 @@ class GameboyEmulator extends Observable implements Emulator {
 
         // boots the emulator subsystem with the initial
         // ROM retrieved from a remote data source
-        await this.boot({ loadRom: true });
+        await this.boot({ loadRom: true, romPath: romUrl || undefined });
 
         // the counter that controls the overflowing cycles
         // from tick to tick operation
@@ -122,7 +138,11 @@ class GameboyEmulator extends Observable implements Emulator {
             let currentTime = new Date().getTime();
 
             try {
-                pending = this.tick(currentTime, pending);
+                pending = this.tick(
+                    currentTime,
+                    pending,
+                    Math.round(this.logicFrequency / this.visualFrequency)
+                );
             } catch (err) {
                 // sets the default error message to be displayed
                 // to the user, this value may be overridden in case
@@ -231,7 +251,7 @@ class GameboyEmulator extends Observable implements Emulator {
         // 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) {
+        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);
@@ -277,7 +297,7 @@ class GameboyEmulator extends Observable implements Emulator {
         // 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);
+            ({ name: romName, data: romData } = await this.fetchRom(romPath));
         } else if (romName === null || romData === null) {
             [romName, romData] = [this.romName, this.romData];
         }
@@ -308,7 +328,7 @@ class GameboyEmulator extends Observable implements Emulator {
 
         // updates the name of the currently selected engine
         // to the one that has been provided (logic change)
-        if (engine) this.engine = engine;
+        if (engine) this._engine = engine;
 
         // updates the complete set of global information that
         // is going to be displayed
@@ -340,11 +360,11 @@ class GameboyEmulator extends Observable implements Emulator {
 
             switch (event.key) {
                 case "+":
-                    this.logicFrequency += FREQUENCY_DELTA;
+                    this.frequency += FREQUENCY_DELTA;
                     break;
 
                 case "-":
-                    this.logicFrequency -= FREQUENCY_DELTA;
+                    this.frequency -= FREQUENCY_DELTA;
                     break;
             }
         });
@@ -367,27 +387,35 @@ class GameboyEmulator extends Observable implements Emulator {
         this.cartridge = cartridge;
     }
 
-    getName() {
+    get name(): string {
         return "Boytacean";
     }
 
-    getDevice(): string {
+    get device(): string {
         return "Game Boy";
     }
 
-    getDeviceUrl(): string {
+    get deviceUrl(): string {
         return "https://en.wikipedia.org/wiki/Game_Boy";
     }
 
-    getVersion() {
+    get engines() {
+        return ["neo"];
+    }
+
+    get engine() {
+        return this._engine;
+    }
+
+    get version(): string {
         return info.version;
     }
 
-    getVersionUrl() {
+    get versionUrl(): string {
         return "https://gitlab.stage.hive.pt/joamag/boytacean/-/blob/master/CHANGELOG.md";
     }
 
-    getPixelFormat(): PixelFormat {
+    get pixelFormat(): PixelFormat {
         return PixelFormat.RGB;
     }
 
@@ -397,11 +425,11 @@ class GameboyEmulator extends Observable implements Emulator {
      *
      * @returns The current pixel data for the emulator display.
      */
-    getImageBuffer(): Uint8Array {
+    get imageBuffer(): Uint8Array {
         return this.gameBoy!.frame_buffer_eager();
     }
 
-    getRomInfo(): RomInfo {
+    get romInfo(): RomInfo {
         return {
             name: this.romName || undefined,
             data: this.romData || undefined,
@@ -414,7 +442,17 @@ class GameboyEmulator extends Observable implements Emulator {
         };
     }
 
-    getFramerate(): number {
+    get frequency(): number {
+        return this.logicFrequency;
+    }
+
+    set frequency(value: number) {
+        value = Math.max(value, 0);
+        this.logicFrequency = value;
+        this.trigger("frequency", value);
+    }
+
+    get framerate(): number {
         return this.fps;
     }
 
@@ -443,6 +481,18 @@ class GameboyEmulator extends Observable implements Emulator {
         this.boot({ engine: null });
     }
 
+    keyPress(key: string) {
+        const keyCode = KEYS_NAME[key];
+        if (!keyCode) return;
+        this.gameBoy!.key_press(keyCode);
+    }
+
+    keyLift(key: string) {
+        const keyCode = KEYS_NAME[key];
+        if (!keyCode) return;
+        this.gameBoy!.key_lift(keyCode);
+    }
+
     benchmark(count = 50000000) {
         let cycles = 0;
         this.pause();
@@ -464,7 +514,9 @@ class GameboyEmulator extends Observable implements Emulator {
         }
     }
 
-    private async fetchRom(romPath: string): Promise<[string, Uint8Array]> {
+    private async fetchRom(
+        romPath: string
+    ): Promise<{ name: string; data: Uint8Array }> {
         // extracts the name of the ROM from the provided
         // path by splitting its structure
         const romPathS = romPath.split(/\//g);
@@ -474,14 +526,17 @@ class GameboyEmulator extends Observable implements Emulator {
 
         // 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 response = await fetch(romPath);
         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];
+        return {
+            name: romName,
+            data: romData
+        };
     }
 }
 
diff --git a/examples/web/package.json b/examples/web/package.json
index 3f3fb0ce3f4a4c3b674407b58fddcf502b685f06..da7ea87072bc57e228782b75098f250e9cbd07b4 100644
--- a/examples/web/package.json
+++ b/examples/web/package.json
@@ -1,6 +1,6 @@
 {
     "name": "boytacean-web",
-    "version": "0.4.0",
+    "version": "0.4.1",
     "description": "The web version of Boytacean",
     "repository": {
         "type": "git",
diff --git a/examples/web/react/app.css b/examples/web/react/app.css
index 4c1786700a5d9be248c7a518c125744177b3972e..d44f28a65d33844a5029ac0909c352465c2cb860 100644
--- a/examples/web/react/app.css
+++ b/examples/web/react/app.css
@@ -14,6 +14,6 @@
 
 @media only screen and (max-width: 1120px) {
     .app .display-container {
-        margin-top: 0px;
+        margin-top: 14px;
     }
 }
diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx
index e8b7269c0320e20342cdce9b3617d380f92f7acd..4111def049b5b342346e685e55fc172ae2103c55 100644
--- a/examples/web/react/app.tsx
+++ b/examples/web/react/app.tsx
@@ -28,7 +28,7 @@ import {
 
 import "./app.css";
 
-export type Callback<T> = (owner: T, params?: Record<string, any>) => void;
+export type Callback<T> = (owner: T, params?: any) => void;
 
 /**
  * Abstract class that implements the basic functionality
@@ -54,7 +54,7 @@ export class Observable {
         this.events[event] = callbacks;
     }
 
-    trigger(event: string, params?: Record<string, any>) {
+    trigger(event: string, params?: any) {
         const callbacks = this.events[event] ?? [];
         callbacks.forEach((c) => c(this, params));
     }
@@ -87,78 +87,77 @@ export interface ObservableI {
  */
 export interface Emulator extends ObservableI {
     /**
-     * Obtains the descriptive name of the emulator.
-     *
-     * @returns The descriptive name of the emulator.
+     * The descriptive name of the emulator.
      */
-    getName(): string;
+    get name(): string;
 
     /**
-     * Obtains the name of the name of the hardware that
-     * is being emulated by the emulator (eg: Super Nintendo).
-     *
-     * @returns The name of the hardware that is being
-     * emulated.
+     * The name of the the hardware that is being emulated
+     * by the emulator (eg: Super Nintendo).
      */
-    getDevice(): string;
+    get device(): string;
 
-    getDeviceUrl?(): string;
+    get deviceUrl(): string | undefined;
 
     /**
-     * Obtains a semantic version string for the current
-     * version of the emulator.
+     * A semantic version string for the current version
+     * of the emulator.
      *
-     * @returns The semantic version string.
      * @see {@link https://semver.org}
      */
-    getVersion(): string;
+    get version(): string;
 
     /**
-     * Obtains a URL to the page describing the current version
-     * of the emulator.
-     *
-     * @returns A URL to the page describing the current version
+     * The URL to the page describing the current version
      * of the emulator.
      */
-    getVersionUrl?(): string;
+    get versionUrl(): string;
+
+    /**
+     * The complete set of engine names that can be used
+     * in the re-boot operation.
+     */
+    get engines(): string[];
+
+    /**
+     * The name of the current execution engine being used
+     * by the emulator.
+     */
+    get engine(): string | null;
 
     /**
-     * Obtains the pixel format of the emulator's display
+     * The pixel format of the emulator's display
      * image buffer (eg: RGB).
-     *
-     * @returns The pixel format used for the emulator's
-     * image buffer.
      */
-    getPixelFormat(): PixelFormat;
+    get pixelFormat(): PixelFormat;
 
     /**
-     * Obtains the complete image buffer as a sequence of
+     * Gets the complete image buffer as a sequence of
      * bytes that respects the current pixel format from
      * `getPixelFormat()`. This method returns an in memory
      * pointer to the heap and not a copy.
-     *
-     * @returns The byte based image buffer that respects
-     * the emulator's pixel format.
      */
-    getImageBuffer(): Uint8Array;
+    get imageBuffer(): Uint8Array;
 
     /**
-     * Obtains information about the ROM that is currently
+     * Gets information about the ROM that is currently
+     * loaded in the emulator, using a structure containing
+     * the information about the ROM that is currently
      * loaded in the emulator.
-     *
-     * @returns Structure containing the information about
-     * the ROM that is currently loaded in the emulator.
      */
-    getRomInfo(): RomInfo;
+    get romInfo(): RomInfo;
 
     /**
-     * Returns the current logic framerate of the running
-     * emulator.
-     *
-     * @returns The current logic framerate of the running
-     * emulator.
+     * The current CPU frequency (logic) of the emulator,
+     * should impact other elements of the emulator.
      */
-    getFramerate(): number;
+    get frequency(): number;
+    set frequency(value: number);
+
+    /**
+     * The current logic framerate of the running emulator.
+     */
+    get framerate(): number;
 
     getTile(index: number): Uint8Array;
 
@@ -187,6 +186,10 @@ export interface Emulator extends ObservableI {
      */
     reset(): void;
 
+    keyPress(key: string): void;
+
+    keyLift(key: string): void;
+
     /**
      * Runs a benchmark operation in the emulator, effectively
      * measuring the performance of it.
@@ -199,6 +202,16 @@ export interface Emulator extends ObservableI {
     benchmark(count?: number): BenchmarkResult;
 }
 
+export class EmulatorBase extends Observable {
+    get deviceUrl(): string | undefined {
+        return undefined;
+    }
+
+    get versionUrl(): string | undefined {
+        return undefined;
+    }
+}
+
 /**
  * Enumeration that describes the multiple pixel
  * formats and the associated size in bytes.
@@ -213,6 +226,10 @@ type AppProps = {
     backgrounds?: string[];
 };
 
+const isTouchDevice = () => {
+    return "ontouchstart" in window || navigator.maxTouchPoints > 0;
+};
+
 export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
     const [paused, setPaused] = useState(false);
     const [fullscreen, setFullscreen] = useState(false);
@@ -226,7 +243,7 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
     const [toastText, setToastText] = useState<string>();
     const [toastError, setToastError] = useState(false);
     const [toastVisible, setToastVisible] = useState(false);
-    const [keyboardVisible, setKeyboardVisible] = useState(false);
+    const [keyboardVisible, setKeyboardVisible] = useState(isTouchDevice());
     const [infoVisible, setInfoVisible] = useState(true);
     const [debugVisible, setDebugVisible] = useState(false);
 
@@ -265,8 +282,7 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
             }
         };
         const onBooted = () => {
-            const romInfo = emulator.getRomInfo();
-            setRomInfo(romInfo);
+            setRomInfo(emulator.romInfo);
             setPaused(false);
         };
         const onMessage = (
@@ -322,7 +338,7 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
         // Game Boy only (using the emulator interface)
         if (!file.name.endsWith(".gb")) {
             showToast(
-                `This is probably not a ${emulator.getDevice()} ROM file!`,
+                `This is probably not a ${emulator.device} ROM file!`,
                 true
             );
             return;
@@ -400,18 +416,33 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
     const onEngineChange = (engine: string) => {
         emulator.boot({ engine: engine.toLowerCase() });
         showToast(
-            `${emulator.getDevice()} running in engine "${engine}" from now on!`
+            `${emulator.device} running in engine "${engine}" from now on!`
         );
     };
+    const onFrequencyChange = (value: number) => {
+        emulator.frequency = value * 1000 * 1000;
+    };
+    const onFrequencyReady = (handler: (value: number) => void) => {
+        emulator.bind("frequency", (emulator: Emulator, frequency: number) => {
+            handler(frequency / 1000000);
+        });
+    };
     const onMinimize = () => {
         setFullscreen(!fullscreen);
     };
+    const onKeyDown = (key: string) => {
+        console.info(key);
+        emulator.keyPress(key);
+    };
+    const onKeyUp = (key: string) => {
+        emulator.keyLift(key);
+    };
     const onDrawHandler = (handler: DrawHandler) => {
         if (frameRef.current) return;
         frameRef.current = true;
         emulator.bind("frame", () => {
-            handler(emulator.getImageBuffer(), PixelFormat.RGB);
-            setFramerate(emulator.getFramerate());
+            handler(emulator.imageBuffer, PixelFormat.RGB);
+            setFramerate(emulator.framerate);
         });
     };
     const onClearHandler = (handler: ClearHandler) => {
@@ -456,28 +487,28 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                     </div>
                 }
             >
+                {keyboardVisible && (
+                    <Section separatorBottom={true}>
+                        <KeyboardGB onKeyDown={onKeyDown} onKeyUp={onKeyUp} />
+                    </Section>
+                )}
                 <Title
-                    text={emulator.getName()}
-                    version={emulator.getVersion()}
+                    text={emulator.name}
+                    version={emulator.version}
                     versionUrl={
-                        emulator.getVersionUrl
-                            ? emulator.getVersionUrl()
-                            : undefined
+                        emulator.versionUrl ? emulator.versionUrl : undefined
                     }
                     iconSrc={require("../res/thunder.png")}
                 ></Title>
                 <Section>
                     <Paragraph>
                         This is a{" "}
-                        {emulator.getDeviceUrl ? (
-                            <Link
-                                href={emulator.getDeviceUrl()}
-                                target="_blank"
-                            >
-                                {emulator.getDevice()}
+                        {emulator.deviceUrl ? (
+                            <Link href={emulator.deviceUrl} target="_blank">
+                                {emulator.device}
                             </Link>
                         ) : (
-                            emulator.getDevice()
+                            emulator.device
                         )}{" "}
                         emulator built using the{" "}
                         <Link href="https://www.rust-lang.org" target="_blank">
@@ -504,11 +535,6 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                         ROM.
                     </Paragraph>
                 </Section>
-                {keyboardVisible && (
-                    <Section>
-                        <KeyboardGB />
-                    </Section>
-                )}
                 {debugVisible && (
                     <Section>
                         <h3>VRAM Tiles</h3>
@@ -526,7 +552,9 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                                 name={"Engine"}
                                 valueNode={
                                     <ButtonSwitch
-                                        options={["NEO", "CLASSIC"]}
+                                        options={emulator.engines.map((e) =>
+                                            e.toUpperCase()
+                                        )}
                                         size={"large"}
                                         style={["simple"]}
                                         onChange={onEngineChange}
@@ -542,7 +570,11 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                                 key="rom-size"
                                 name={"ROM Size"}
                                 value={
-                                    romInfo.name ? `${romInfo.size} bytes` : "-"
+                                    romInfo.size
+                                        ? `${new Intl.NumberFormat().format(
+                                              romInfo.size
+                                          )} bytes`
+                                        : "-"
                                 }
                             />
                             <Pair
@@ -550,11 +582,13 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                                 name={"CPU Frequency"}
                                 valueNode={
                                     <ButtonIncrement
-                                        value={4.19}
-                                        delta={0.1}
+                                        value={emulator.frequency / 1000 / 1000}
+                                        delta={0.4}
                                         min={0}
                                         suffix={"MHz"}
                                         decimalPlaces={2}
+                                        onChange={onFrequencyChange}
+                                        onReady={onFrequencyReady}
                                     />
                                 }
                             />
diff --git a/examples/web/react/components/button-increment/button-increment.tsx b/examples/web/react/components/button-increment/button-increment.tsx
index 62a9071ad02633bc663095c388215871d660fc24..f80609384b0773bcebe46c63187b363725261184 100644
--- a/examples/web/react/components/button-increment/button-increment.tsx
+++ b/examples/web/react/components/button-increment/button-increment.tsx
@@ -1,4 +1,4 @@
-import React, { FC, useState } from "react";
+import React, { FC, useEffect, useState } from "react";
 import Button from "../button/button";
 
 import "./button-increment.css";
@@ -16,6 +16,7 @@ type ButtonIncrementProps = {
     onClick?: () => void;
     onBeforeChange?: (value: number) => boolean;
     onChange?: (value: number) => void;
+    onReady?: (setValue: (value: number) => void) => void;
 };
 
 export const ButtonIncrement: FC<ButtonIncrementProps> = ({
@@ -30,25 +31,31 @@ export const ButtonIncrement: FC<ButtonIncrementProps> = ({
     style = ["simple", "border"],
     onClick,
     onBeforeChange,
-    onChange
+    onChange,
+    onReady
 }) => {
     const [valueState, setValue] = useState(value);
     const classes = () => ["button-increment", size, ...style].join(" ");
+    useEffect(() => {
+        onReady && onReady((value) => setValue(value));
+    }, []);
     const _onMinusClick = () => {
-        const valueNew = valueState - delta;
+        let valueNew = valueState - delta;
         if (onBeforeChange) {
             if (!onBeforeChange(valueNew)) return;
         }
-        if (min !== undefined && valueNew < min) return;
+        if (min !== undefined) valueNew = Math.max(min, valueNew);
+        if (valueNew === valueState) return;
         setValue(valueNew);
         if (onChange) onChange(valueNew);
     };
     const _onPlusClick = () => {
-        const valueNew = valueState + delta;
+        let valueNew = valueState + delta;
         if (onBeforeChange) {
             if (!onBeforeChange(valueNew)) return;
         }
-        if (max !== undefined && valueNew > max) return;
+        if (max !== undefined) valueNew = Math.min(max, valueNew);
+        if (valueNew === valueState) return;
         setValue(valueNew);
         if (onChange) onChange(valueNew);
     };
diff --git a/examples/web/react/components/keyboard-chip8/keyboard-chip8.css b/examples/web/react/components/keyboard-chip8/keyboard-chip8.css
index 01f35fef038dc2cc39305784fdfde7dca61efb1d..60e0d8f80e26b585c403aace668127daf5d66001 100644
--- a/examples/web/react/components/keyboard-chip8/keyboard-chip8.css
+++ b/examples/web/react/components/keyboard-chip8/keyboard-chip8.css
@@ -38,8 +38,9 @@
     height: 48px;
     line-height: 46px;
     margin-right: 14px;
+    min-width: 48px;
+    padding: 0px 6px 0px 6px;
     text-align: center;
-    width: 48px;
 }
 
 .keyboard-chip8 .key:last-child {
diff --git a/examples/web/react/components/keyboard-gb/dpad.svg b/examples/web/react/components/keyboard-gb/dpad.svg
deleted file mode 100644
index 8115b91717cc9d018b30805c4836162b78d9670d..0000000000000000000000000000000000000000
--- a/examples/web/react/components/keyboard-gb/dpad.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="700pt" height="700pt" version="1.1" viewBox="0 0 700 700" fill="#ffffff" stroke="#ffffff"  xmlns="http://www.w3.org/2000/svg"><path d="m338.45 240.62c3.1953 2.8047 7.3008 4.3516 11.551 4.3516s8.3555-1.5469 11.551-4.3516l78.75-70c3.7773-3.3164 5.9414-8.0977 5.9492-13.125v-105c0-4.6406-1.8438-9.0938-5.125-12.375s-7.7344-5.125-12.375-5.125h-157.5c-4.6406 0-9.0938 1.8438-12.375 5.125s-5.125 7.7344-5.125 12.375v105c0.007812 5.0273 2.1719 9.8086 5.9492 13.125zm-49.699-170.62h122.5v79.625l-61.25 54.426-61.25-54.426zm72.801 249.38c-3.1953-2.8047-7.3008-4.3516-11.551-4.3516s-8.3555 1.5469-11.551 4.3516l-78.75 70c-3.7773 3.3164-5.9414 8.0977-5.9492 13.125v105c0 4.6406 1.8438 9.0938 5.125 12.375s7.7344 5.125 12.375 5.125h157.5c4.6406 0 9.0938-1.8438 12.375-5.125s5.125-7.7344 5.125-12.375v-105c-0.007812-5.0273-2.1719-9.8086-5.9492-13.125zm49.699 170.62h-122.5v-79.625l61.25-54.426 61.25 54.426zm-100.62-221.55-70-78.75c-3.3164-3.7773-8.0977-5.9414-13.125-5.9492h-105c-4.6406 0-9.0938 1.8438-12.375 5.125s-5.125 7.7344-5.125 12.375v157.5c0 4.6406 1.8438 9.0938 5.125 12.375s7.7344 5.125 12.375 5.125h105c5.0273-0.007812 9.8086-2.1719 13.125-5.9492l70-78.75c2.8047-3.1953 4.3516-7.3008 4.3516-11.551s-1.5469-8.3555-4.3516-11.551zm-91 72.801h-79.625v-122.5h79.625l54.426 61.25zm357.88-157.5h-105c-5.0273 0.007812-9.8086 2.1719-13.125 5.9492l-70 78.75c-2.8047 3.1953-4.3516 7.3008-4.3516 11.551s1.5469 8.3555 4.3516 11.551l70 78.75c3.3164 3.7773 8.0977 5.9414 13.125 5.9492h105c4.6406 0 9.0938-1.8438 12.375-5.125s5.125-7.7344 5.125-12.375v-157.5c0-4.6406-1.8438-9.0938-5.125-12.375s-7.7344-5.125-12.375-5.125zm-17.5 157.5h-79.625l-54.426-61.25 54.426-61.25h79.625z"/></svg>
diff --git a/examples/web/react/components/keyboard-gb/keyboard-gb.css b/examples/web/react/components/keyboard-gb/keyboard-gb.css
index 56f4b8dacbe21740773829853af511b26e7eb671..2039d4932b21ae19676cbc75571eb7fad2800e81 100644
--- a/examples/web/react/components/keyboard-gb/keyboard-gb.css
+++ b/examples/web/react/components/keyboard-gb/keyboard-gb.css
@@ -38,22 +38,155 @@
     height: 48px;
     line-height: 46px;
     margin-right: 14px;
+    min-width: 48px;
+    padding: 0px 8px 0px 8px;
     text-align: center;
-    width: 48px;
 }
 
 .keyboard-gb .key:last-child {
     margin-right: 0px;
 }
 
-.keyboard-gb .key:hover {
+.keyboard-gb .key.pressed {
     background-color: #50cb93;
 }
 
-.keyboard-gb .key:active {
-    background-color: #2a9d8f;
+.keyboard-gb > .dpad {
+    float: left;
+    text-align: center;
+    width: 180px;
+}
+
+.keyboard-gb > .dpad > .dpad-top {
+    margin-bottom: -3px;
+}
+
+.keyboard-gb > .dpad > .dpad-bottom {
+    margin-top: -3px;
+}
+
+.keyboard-gb > .dpad .key {
+    border: none;
+    padding: 0px 0px 0px 0px;
+    font-size: 24px;
+}
+
+.keyboard-gb > .dpad .key.pressed {
+    background-color: #50cb93;
+}
+
+.keyboard-gb > .dpad .key.up {
+    border-bottom: 3px solid transparent;
+    border-left: 3px solid #ffffff;
+    border-radius: 6px 6px 0px 0px;
+    -o-border-radius: 6px 6px 0px 0px;
+    -ms-border-radius: 6px 6px 0px 0px;
+    -moz-border-radius: 6px 6px 0px 0px;
+    -khtml-border-radius: 6px 6px 0px 0px;
+    -webkit-border-radius: 6px 6px 0px 0px;
+    border-right: 3px solid #ffffff;
+    border-top: 3px solid #ffffff;
+}
+
+.keyboard-gb > .dpad .key.left {
+    border-bottom: 3px solid #ffffff;
+    border-left: 3px solid #ffffff;
+    border-radius: 6px 0px 0px 6px;
+    -o-border-radius: 6px 0px 0px 6px;
+    -ms-border-radius: 6px 0px 0px 6px;
+    -moz-border-radius: 6px 0px 0px 6px;
+    -khtml-border-radius: 6px 0px 0px 6px;
+    -webkit-border-radius: 6px 0px 0px 6px;
+    border-right: 3px solid transparent;
+    border-top: 3px solid #ffffff;
+    margin-right: 44px;
+}
+
+.keyboard-gb > .dpad .key.right {
+    border-bottom: 3px solid #ffffff;
+    border-left: 3px solid transparent;
+    border-radius: 0px 6px 6px 0px;
+    -o-border-radius: 0px 6px 6px 0px;
+    -ms-border-radius: 0px 6px 6px 0px;
+    -moz-border-radius: 0px 6px 6px 0px;
+    -khtml-border-radius: 0px 6px 6px 0px;
+    -webkit-border-radius: 0px 6px 6px 0px;
+    border-right: 3px solid #ffffff;
+    border-top: 3px solid #ffffff;
+    margin-right: 0px;
+    margin-left: -3px;
+}
+
+.keyboard-gb > .dpad .key.center {
+    border-radius: 0px 0px 0px 0px;
+    -o-border-radius: 0px 0px 0px 0px;
+    -ms-border-radius: 0px 0px 0px 0px;
+    -moz-border-radius: 0px 0px 0px 0px;
+    -khtml-border-radius: 0px 0px 0px 0px;
+    -webkit-border-radius: 0px 0px 0px 0px;
+    margin-right: 0px;
+    pointer-events: none;
+}
+
+.keyboard-gb > .dpad .key.down {
+    border-bottom: 3px solid #ffffff;
+    border-left: 3px solid #ffffff;
+    border-radius: 0px 0px 6px 6px;
+    -o-border-radius: 0px 0px 6px 6px;
+    -ms-border-radius: 0px 0px 6px 6px;
+    -moz-border-radius: 0px 0px 6px 6px;
+    -khtml-border-radius: 0px 0px 6px 6px;
+    -webkit-border-radius: 0px 0px 6px 6px;
+    border-right: 3px solid #ffffff;
+    border-top: 3px solid transparent;
+}
+
+.keyboard-gb > .action {
+    float: right;
+    margin-right: 18px;
+}
+
+.keyboard-gb > .action > .key {
+    border-radius: 32px 32px 32px 32px;
+    -o-border-radius: 32px 32px 32px 32px;
+    -ms-border-radius: 32px 32px 32px 32px;
+    -moz-border-radius: 32px 32px 32px 32px;
+    -khtml-border-radius: 32px 32px 32px 32px;
+    -webkit-border-radius: 32px 32px 32px 32px;
+    border-width: 3px;
+    font-size: 30px;
+    height: 58px;
+    line-height: 52px;
+    width: 58px;
+    font-weight: 900;
+}
+
+.keyboard-gb > .action > .key.a {
+    position: relative;
+    right: 0px;
+    top: 10px;
+}
+
+.keyboard-gb > .action > .key.b {
+    position: relative;
+    right: 2px;
+    top: 50px;
+}
+
+.keyboard-gb > .options {
+    margin-top: 32px;
+}
+
+.keyboard-gb > .options > .key {
+    font-size: 14px;
+    height: 30px;
+    line-height: 26px;
+    margin-left: 16px;
+    margin-right: 16px;
+    min-width: 100px;
+    font-weight: 900;
 }
 
-.keyboard-gb .dpad {
-    width: 120px;
+.keyboard-gb > .break {
+    clear: both;
 }
diff --git a/examples/web/react/components/keyboard-gb/keyboard-gb.tsx b/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
index 6358673061284ede95093ec10915277fa9c0681b..dddeaa6c9ffb28c1e5f61d8c2f9b3033db425159 100644
--- a/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
+++ b/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from "react";
+import React, { FC, useState } from "react";
 
 import "./keyboard-gb.css";
 
@@ -7,34 +7,89 @@ declare const require: any;
 type KeyboardGBProps = {
     style?: string[];
     onKeyDown?: (key: string) => void;
+    onKeyUp?: (key: string) => void;
 };
 
-export const KeyboardGB: FC<KeyboardGBProps> = ({ style = [], onKeyDown }) => {
+export const KeyboardGB: FC<KeyboardGBProps> = ({
+    style = [],
+    onKeyDown,
+    onKeyUp
+}) => {
     const classes = () => ["keyboard", "keyboard-gb", ...style].join(" ");
-    const renderKey = (key: string) => {
+    const renderKey = (
+        key: string,
+        keyName?: string,
+        styles: string[] = []
+    ) => {
+        const [pressed, setPressed] = useState(false);
         return (
             <span
-                className="key"
-                key={key}
-                onKeyDown={() => onKeyDown && onKeyDown(key)}
+                className={["key", pressed ? "pressed" : "", ...styles].join(
+                    " "
+                )}
+                key={keyName || key}
+                onMouseDown={(event) => {
+                    setPressed(true);
+                    onKeyDown && onKeyDown(keyName || key);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }}
+                onMouseUp={(event) => {
+                    setPressed(false);
+                    onKeyUp && onKeyUp(keyName || key);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }}
+                onMouseLeave={(event) => {
+                    if (!pressed) return;
+                    setPressed(false);
+                    onKeyUp && onKeyUp(keyName || key);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }}
+                onTouchStart={(event) => {
+                    setPressed(true);
+                    onKeyDown && onKeyDown(keyName || key);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }}
+                onTouchEnd={(event) => {
+                    setPressed(false);
+                    onKeyUp && onKeyUp(keyName || key);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }}
             >
                 {key}
             </span>
         );
     };
     return (
-        <div className={classes()}>
-            <div className="keyboard-line">
-                <img className="dpad" alt="dpad" src={require("./dpad.svg")} />
+        <div
+            className={classes()}
+            onTouchStart={(e) => e.preventDefault()}
+            onTouchEnd={(e) => e.preventDefault()}
+        >
+            <div className="dpad">
+                <div className="dpad-top">
+                    {renderKey("🡑", "ArrowUp", ["up"])}
+                </div>
+                <div>
+                    {renderKey("🡐", "ArrowLeft", ["left"])}
+                    {renderKey("🡒", "ArrowRight", ["right"])}
+                </div>
+                <div className="dpad-bottom">
+                    {renderKey("🡓", "ArrowDown", ["down"])}
+                </div>
             </div>
-            <div className="keyboard-line">
-                {["Q", "W", "E", "R"].map((k) => renderKey(k))}
+            <div className="action">
+                {renderKey("B", "B", ["b"])}
+                {renderKey("A", "A", ["a"])}
             </div>
-            <div className="keyboard-line">
-                {["A", "S", "D", "F"].map((k) => renderKey(k))}
-            </div>
-            <div className="keyboard-line">
-                {["Z", "X", "C", "V"].map((k) => renderKey(k))}
+            <div className="break"></div>
+            <div className="options">
+                {renderKey("START", "Start", ["start"])}
+                {renderKey("SELECT", "Select", ["select"])}
             </div>
         </div>
     );
diff --git a/examples/web/react/components/section/section.tsx b/examples/web/react/components/section/section.tsx
index 91a84184a041858745c50f06805b13b4521d6c25..b6a0df50e36c9890bc33e642f6bcd2923adf9fab 100644
--- a/examples/web/react/components/section/section.tsx
+++ b/examples/web/react/components/section/section.tsx
@@ -5,12 +5,14 @@ import "./section.css";
 type SectionProps = {
     children: ReactNode;
     separator?: boolean;
+    separatorBottom?: boolean;
     style?: string[];
 };
 
 export const Section: FC<SectionProps> = ({
     children,
     separator = true,
+    separatorBottom = false,
     style = []
 }) => {
     const classes = () => ["section", ...style].join(" ");
@@ -18,6 +20,7 @@ export const Section: FC<SectionProps> = ({
         <div className={classes()}>
             {separator && <div className="separator"></div>}
             <div className="section-contents">{children}</div>
+            {separatorBottom && <div className="separator"></div>}
         </div>
     );
 };
diff --git a/examples/web/react/components/tiles/tiles.tsx b/examples/web/react/components/tiles/tiles.tsx
index 8ecc57d3b5442758914256df11f0074b0740aaee..e879f383585faaab2a29889295ec78793182cb5c 100644
--- a/examples/web/react/components/tiles/tiles.tsx
+++ b/examples/web/react/components/tiles/tiles.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from "react";
+import React, { FC, useEffect, useRef } from "react";
 import { PixelFormat } from "../../app";
 import Canvas, { CanvasStructure } from "../canvas/canvas";
 
@@ -18,6 +18,14 @@ export const Tiles: FC<TilesProps> = ({
     style = []
 }) => {
     const classes = () => ["tiles", ...style].join(" ");
+    const intervalsRef = useRef<number>();
+    useEffect(() => {
+        return () => {
+            if (intervalsRef.current) {
+                clearInterval(intervalsRef.current);
+            }
+        };
+    }, []);
     const onCanvas = (structure: CanvasStructure) => {
         const drawTiles = () => {
             for (let index = 0; index < 384; index++) {
@@ -26,7 +34,7 @@ export const Tiles: FC<TilesProps> = ({
             }
         };
         drawTiles();
-        setInterval(() => drawTiles(), interval);
+        intervalsRef.current = setInterval(() => drawTiles(), interval);
     };
     return (
         <div className={classes()}>