From d747a5e8dc585c31679d6593a325cba365e4fada Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Sat, 19 Nov 2022 21:13:28 +0000
Subject: [PATCH] =?UTF-8?q?feat:=20moved=20emukit=20out=20into=20a=20separ?=
 =?UTF-8?q?ate=20lib=20=F0=9F=8F=8B=EF=B8=8F=E2=80=8D=E2=99=82=EF=B8=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 frontends/web/index.ts                        |   4 +-
 frontends/web/package.json                    |  11 +-
 frontends/web/react/app.css                   |  20 -
 frontends/web/react/app.tsx                   | 738 ------------------
 .../button-container/button-container.css     |  12 -
 .../button-container/button-container.tsx     |  18 -
 .../button-increment/button-increment.css     |  15 -
 .../button-increment/button-increment.tsx     |  85 --
 .../button-switch/button-switch.tsx           |  33 -
 .../web/react/components/button/button.css    | 123 ---
 .../web/react/components/button/button.tsx    |  95 ---
 .../web/react/components/canvas/canvas.css    |   0
 .../web/react/components/canvas/canvas.tsx    |  74 --
 .../web/react/components/display/display.css  | 116 ---
 .../web/react/components/display/display.tsx  | 306 --------
 .../web/react/components/display/minimise.svg |   1 -
 .../web/react/components/footer/footer.css    |  24 -
 .../web/react/components/footer/footer.tsx    |  28 -
 frontends/web/react/components/help/help.css  |  53 --
 frontends/web/react/components/help/help.tsx  | 142 ----
 frontends/web/react/components/index.ts       |  23 -
 frontends/web/react/components/info/info.css  |  30 -
 frontends/web/react/components/info/info.tsx  |  26 -
 .../keyboard-chip8/keyboard-chip8.css         |  51 --
 .../keyboard-chip8/keyboard-chip8.tsx         |  99 ---
 .../components/keyboard-gb/keyboard-gb.css    | 257 ------
 .../components/keyboard-gb/keyboard-gb.tsx    | 332 --------
 frontends/web/react/components/link/link.css  |  15 -
 frontends/web/react/components/link/link.tsx  |  28 -
 .../web/react/components/modal/close.svg      |   1 -
 .../web/react/components/modal/modal.css      | 149 ----
 .../web/react/components/modal/modal.tsx      | 114 ---
 .../web/react/components/overlay/overlay.css  |  44 --
 .../web/react/components/overlay/overlay.tsx  |  69 --
 .../react/components/overlay/sunglasses.png   | Bin 4330 -> 0 bytes
 frontends/web/react/components/pair/pair.css  |   0
 frontends/web/react/components/pair/pair.tsx  |  37 -
 .../components/panel-split/panel-split.css    |  30 -
 .../components/panel-split/panel-split.tsx    |  27 -
 .../react/components/panel-tab/panel-tab.css  |  65 --
 .../react/components/panel-tab/panel-tab.tsx  |  47 --
 .../react/components/paragraph/paragraph.css  |   5 -
 .../react/components/paragraph/paragraph.tsx  |  20 -
 .../components/registers-gb/registers-gb.css  |  32 -
 .../components/registers-gb/registers-gb.tsx  |  79 --
 .../web/react/components/section/section.css  |  13 -
 .../web/react/components/section/section.tsx  |  31 -
 .../web/react/components/tiles/tiles.css      |  14 -
 .../web/react/components/tiles/tiles.tsx      |  98 ---
 .../web/react/components/title/title.css      |  10 -
 .../web/react/components/title/title.tsx      |  38 -
 .../web/react/components/toast/toast.css      |  57 --
 .../web/react/components/toast/toast.tsx      |  36 -
 frontends/web/react/structs.ts                | 275 -------
 frontends/web/react/util.ts                   |   3 -
 frontends/web/{ => ts}/gb.ts                  |   8 +-
 frontends/web/ts/index.ts                     |   3 +
 frontends/web/{ => ts}/palettes.ts            |   0
 frontends/web/{ => ts}/util.ts                |   0
 59 files changed, 15 insertions(+), 4049 deletions(-)
 delete mode 100644 frontends/web/react/app.css
 delete mode 100644 frontends/web/react/app.tsx
 delete mode 100644 frontends/web/react/components/button-container/button-container.css
 delete mode 100644 frontends/web/react/components/button-container/button-container.tsx
 delete mode 100644 frontends/web/react/components/button-increment/button-increment.css
 delete mode 100644 frontends/web/react/components/button-increment/button-increment.tsx
 delete mode 100644 frontends/web/react/components/button-switch/button-switch.tsx
 delete mode 100644 frontends/web/react/components/button/button.css
 delete mode 100644 frontends/web/react/components/button/button.tsx
 delete mode 100644 frontends/web/react/components/canvas/canvas.css
 delete mode 100644 frontends/web/react/components/canvas/canvas.tsx
 delete mode 100644 frontends/web/react/components/display/display.css
 delete mode 100644 frontends/web/react/components/display/display.tsx
 delete mode 100644 frontends/web/react/components/display/minimise.svg
 delete mode 100644 frontends/web/react/components/footer/footer.css
 delete mode 100644 frontends/web/react/components/footer/footer.tsx
 delete mode 100644 frontends/web/react/components/help/help.css
 delete mode 100644 frontends/web/react/components/help/help.tsx
 delete mode 100644 frontends/web/react/components/index.ts
 delete mode 100644 frontends/web/react/components/info/info.css
 delete mode 100644 frontends/web/react/components/info/info.tsx
 delete mode 100644 frontends/web/react/components/keyboard-chip8/keyboard-chip8.css
 delete mode 100644 frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx
 delete mode 100644 frontends/web/react/components/keyboard-gb/keyboard-gb.css
 delete mode 100644 frontends/web/react/components/keyboard-gb/keyboard-gb.tsx
 delete mode 100644 frontends/web/react/components/link/link.css
 delete mode 100644 frontends/web/react/components/link/link.tsx
 delete mode 100644 frontends/web/react/components/modal/close.svg
 delete mode 100644 frontends/web/react/components/modal/modal.css
 delete mode 100644 frontends/web/react/components/modal/modal.tsx
 delete mode 100644 frontends/web/react/components/overlay/overlay.css
 delete mode 100644 frontends/web/react/components/overlay/overlay.tsx
 delete mode 100644 frontends/web/react/components/overlay/sunglasses.png
 delete mode 100644 frontends/web/react/components/pair/pair.css
 delete mode 100644 frontends/web/react/components/pair/pair.tsx
 delete mode 100644 frontends/web/react/components/panel-split/panel-split.css
 delete mode 100644 frontends/web/react/components/panel-split/panel-split.tsx
 delete mode 100644 frontends/web/react/components/panel-tab/panel-tab.css
 delete mode 100644 frontends/web/react/components/panel-tab/panel-tab.tsx
 delete mode 100644 frontends/web/react/components/paragraph/paragraph.css
 delete mode 100644 frontends/web/react/components/paragraph/paragraph.tsx
 delete mode 100644 frontends/web/react/components/registers-gb/registers-gb.css
 delete mode 100644 frontends/web/react/components/registers-gb/registers-gb.tsx
 delete mode 100644 frontends/web/react/components/section/section.css
 delete mode 100644 frontends/web/react/components/section/section.tsx
 delete mode 100644 frontends/web/react/components/tiles/tiles.css
 delete mode 100644 frontends/web/react/components/tiles/tiles.tsx
 delete mode 100644 frontends/web/react/components/title/title.css
 delete mode 100644 frontends/web/react/components/title/title.tsx
 delete mode 100644 frontends/web/react/components/toast/toast.css
 delete mode 100644 frontends/web/react/components/toast/toast.tsx
 delete mode 100644 frontends/web/react/structs.ts
 delete mode 100644 frontends/web/react/util.ts
 rename frontends/web/{ => ts}/gb.ts (96%)
 create mode 100644 frontends/web/ts/index.ts
 rename frontends/web/{ => ts}/palettes.ts (100%)
 rename frontends/web/{ => ts}/util.ts (100%)

diff --git a/frontends/web/index.ts b/frontends/web/index.ts
index 25ac5e1c..75a1697c 100644
--- a/frontends/web/index.ts
+++ b/frontends/web/index.ts
@@ -1,5 +1,5 @@
-import { startApp } from "./react/app";
-import { GameboyEmulator } from "./gb";
+import { startApp } from "emukit";
+import { GameboyEmulator } from "./ts";
 
 const BACKGROUNDS = [
     "264653",
diff --git a/frontends/web/package.json b/frontends/web/package.json
index 8611640c..58ff1982 100644
--- a/frontends/web/package.json
+++ b/frontends/web/package.json
@@ -16,14 +16,15 @@
     },
     "source": "index.ts",
     "devDependencies": {
-        "@parcel/transformer-typescript-tsc": "^2.7.0",
-        "@types/react": "^18.0.21",
-        "@types/react-dom": "^18.0.6",
-        "parcel": "^2.7.0",
+        "@parcel/transformer-typescript-tsc": "^2.8.0",
+        "@types/react": "^18.0.25",
+        "@types/react-dom": "^18.0.9",
+        "emukit": "^0.1.1",
+        "parcel": "^2.8.0",
         "prettier": "^2.7.1",
         "process": "^0.11.10",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
-        "typescript": "^4.8.4"
+        "typescript": "^4.9.3"
     }
 }
diff --git a/frontends/web/react/app.css b/frontends/web/react/app.css
deleted file mode 100644
index f21e17fb..00000000
--- a/frontends/web/react/app.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.app {
-    color: #ffffff;
-    font-family: VT323, Roboto, Open Sans, Arial, Helvetica, sans-serif;
-    margin: 0px auto 0px auto;
-    max-width: 1400px;
-}
-
-.app h3 {
-    margin: 0px 0px 16px 0px;
-}
-
-.app .display-container {
-    margin-top: 78px;
-}
-
-@media only screen and (max-width: 1120px) {
-    .app .display-container {
-        margin-top: 14px;
-    }
-}
diff --git a/frontends/web/react/app.tsx b/frontends/web/react/app.tsx
deleted file mode 100644
index e27c152a..00000000
--- a/frontends/web/react/app.tsx
+++ /dev/null
@@ -1,738 +0,0 @@
-import React, { FC, ReactNode, useEffect, useRef, useState } from "react";
-import ReactDOM from "react-dom/client";
-
-declare const require: any;
-
-import {
-    Button,
-    ButtonContainer,
-    ButtonIncrement,
-    ButtonSwitch,
-    ClearHandler,
-    Display,
-    DrawHandler,
-    Footer,
-    Help,
-    Info,
-    KeyboardChip8,
-    KeyboardGB,
-    Link,
-    Modal,
-    Overlay,
-    Pair,
-    PanelSplit,
-    PanelTab,
-    Paragraph,
-    RegistersGB,
-    Section,
-    Tiles,
-    Title,
-    Toast
-} from "./components";
-import {
-    Emulator,
-    Feature,
-    FREQUENCY_DELTA,
-    PixelFormat,
-    RomInfo
-} from "./structs";
-
-import "./app.css";
-
-type EmulatorAppProps = {
-    emulator: Emulator;
-    fullscreen?: boolean;
-    debug?: boolean;
-    keyboard?: boolean;
-    palette?: string;
-    backgrounds?: string[];
-};
-
-const isTouchDevice = () => {
-    return "ontouchstart" in window || navigator.maxTouchPoints > 0;
-};
-
-export const EmulatorApp: FC<EmulatorAppProps> = ({
-    emulator,
-    fullscreen = false,
-    debug = false,
-    keyboard = false,
-    palette,
-    backgrounds = ["264653"]
-}) => {
-    const [paused, setPaused] = useState(false);
-    const [fullscreenState, setFullscreenState] = useState(fullscreen);
-    const [backgroundIndex, setBackgroundIndex] = useState(0);
-    const [romInfo, setRomInfo] = useState<RomInfo>({});
-    const [framerate, setFramerate] = useState(0);
-    const [paletteName, setPaletteName] = useState(emulator.palette);
-    const [keyaction, setKeyaction] = useState<string>();
-    const [modalTitle, setModalTitle] = useState<string>();
-    const [modalText, setModalText] = useState<string>();
-    const [modalContents, setModalContents] = useState<ReactNode>();
-    const [modalVisible, setModalVisible] = useState(false);
-    const [toastText, setToastText] = useState<string>();
-    const [toastError, setToastError] = useState(false);
-    const [toastVisible, setToastVisible] = useState(false);
-    const [keyboardVisible, setKeyboardVisible] = useState(
-        isTouchDevice() || keyboard
-    );
-    const [infoVisible, setInfoVisible] = useState(true);
-    const [debugVisible, setDebugVisible] = useState(debug);
-
-    const toastCounterRef = useRef(0);
-    const frameRef = useRef<boolean>(false);
-    const errorRef = useRef<boolean>(false);
-    const modalCallbackRef =
-        useRef<(value: boolean | PromiseLike<boolean>) => void>();
-
-    useEffect(() => {
-        document.body.style.backgroundColor = `#${getBackground()}`;
-    }, [backgroundIndex]);
-    useEffect(() => {
-        switch (keyaction) {
-            case "Plus":
-                emulator.frequency +=
-                    emulator.frequencyDelta ?? FREQUENCY_DELTA;
-                setKeyaction(undefined);
-                break;
-            case "Minus":
-                emulator.frequency -=
-                    emulator.frequencyDelta ?? FREQUENCY_DELTA;
-                setKeyaction(undefined);
-                break;
-            case "Escape":
-                setFullscreenState(false);
-                setKeyaction(undefined);
-                break;
-            case "Fullscreen":
-                setFullscreenState(!fullscreenState);
-                setKeyaction(undefined);
-                break;
-            case "Keyboard":
-                setKeyboardVisible(!keyboardVisible);
-                setKeyaction(undefined);
-                break;
-            case "Accelerate":
-                emulator.frequency *= 8;
-                break;
-            case "Slowdown":
-                emulator.frequency /= 8;
-                break;
-        }
-    }, [keyaction]);
-    useEffect(() => {
-        if (palette) {
-            emulator.palette = palette;
-        }
-        const onFullChange = (event: Event) => {
-            if (
-                !document.fullscreenElement &&
-                !(document as any).webkitFullscreenElement
-            ) {
-                setFullscreenState(false);
-            }
-        };
-        const onKeyDown = (event: KeyboardEvent) => {
-            switch (event.key) {
-                case "+":
-                    setKeyaction("Plus");
-                    event.stopPropagation();
-                    event.preventDefault();
-                    break;
-                case "-":
-                    setKeyaction("Minus");
-                    event.stopPropagation();
-                    event.preventDefault();
-                    break;
-                case "Escape":
-                    setKeyaction("Escape");
-                    event.stopPropagation();
-                    event.preventDefault();
-                    break;
-            }
-            if (event.ctrlKey === true) {
-                switch (event.key) {
-                    case "f":
-                        setKeyaction("Fullscreen");
-                        event.stopPropagation();
-                        event.preventDefault();
-                        break;
-                    case "k":
-                        setKeyaction("Keyboard");
-                        event.stopPropagation();
-                        event.preventDefault();
-                        break;
-                    case "d":
-                        setKeyaction("Accelerate");
-                        event.stopPropagation();
-                        event.preventDefault();
-                        break;
-                }
-            }
-        };
-        const onKeyUp = (event: KeyboardEvent) => {
-            if (event.ctrlKey === true) {
-                switch (event.key) {
-                    case "d":
-                        setKeyaction("Slowdown");
-                        event.stopPropagation();
-                        event.preventDefault();
-                        break;
-                }
-            }
-        };
-        const onBooted = () => {
-            setRomInfo(emulator.romInfo);
-            setPaused(false);
-        };
-        const onMessage = (
-            emulator: Emulator,
-            params: Record<string, any> = {}
-        ) => {
-            showToast(params.text, params.error, params.timeout);
-        };
-        document.addEventListener("fullscreenchange", onFullChange);
-        document.addEventListener("webkitfullscreenchange", onFullChange);
-        document.addEventListener("keydown", onKeyDown);
-        document.addEventListener("keyup", onKeyUp);
-        emulator.bind("booted", onBooted);
-        emulator.bind("message", onMessage);
-        return () => {
-            document.removeEventListener("fullscreenchange", onFullChange);
-            document.removeEventListener(
-                "webkitfullscreenchange",
-                onFullChange
-            );
-            document.removeEventListener("keydown", onKeyDown);
-            document.removeEventListener("keyup", onKeyUp);
-            emulator.unbind("booted", onBooted);
-            emulator.unbind("message", onMessage);
-        };
-    }, []);
-
-    const getPauseText = () => (paused ? "Resume" : "Pause");
-    const getPauseIcon = () =>
-        paused ? require("../res/play.svg") : require("../res/pause.svg");
-    const getBackground = () => backgrounds[backgroundIndex];
-
-    const showModal = async (
-        title = "Alert",
-        text?: string,
-        contents?: ReactNode
-    ): Promise<boolean> => {
-        setModalTitle(title);
-        setModalText(text);
-        setModalContents(contents);
-        setModalVisible(true);
-        const result = (await new Promise((resolve) => {
-            modalCallbackRef.current = resolve;
-        })) as boolean;
-        return result;
-    };
-    const showHelp = async (title = "Help") => {
-        await showModal(title, undefined, <Help />);
-    };
-    const showToast = async (text: string, error = false, timeout = 3500) => {
-        setToastText(text);
-        setToastError(error);
-        setToastVisible(true);
-        toastCounterRef.current++;
-        const counter = toastCounterRef.current;
-        await new Promise((resolve) => {
-            setTimeout(() => {
-                if (counter !== toastCounterRef.current) return;
-                setToastVisible(false);
-                resolve(true);
-            }, timeout);
-        });
-    };
-    const hasFeature = (feature: Feature) => {
-        return emulator.features.includes(feature);
-    };
-
-    const onFile = async (file: File) => {
-        const fileExtension = file.name.split(".").pop() ?? "";
-        if (!emulator.romExts.includes(fileExtension)) {
-            showToast(
-                `This is probably not a ${emulator.device.text} ROM file!`,
-                true
-            );
-            return;
-        }
-
-        const arrayBuffer = await file.arrayBuffer();
-        const romData = new Uint8Array(arrayBuffer);
-
-        emulator.boot({ engine: null, romName: file.name, romData: romData });
-
-        showToast(`Loaded ${file.name} ROM successfully!`);
-    };
-    const onModalConfirm = () => {
-        if (modalCallbackRef.current) {
-            modalCallbackRef.current(true);
-            modalCallbackRef.current = undefined;
-        }
-        setModalVisible(false);
-    };
-    const onModalCancel = () => {
-        if (modalCallbackRef.current) {
-            modalCallbackRef.current(false);
-            modalCallbackRef.current = undefined;
-        }
-        setModalVisible(false);
-    };
-    const onToastCancel = () => {
-        setToastVisible(false);
-    };
-    const onPauseClick = () => {
-        emulator.toggleRunning();
-        setPaused(!paused);
-    };
-    const onResetClick = () => {
-        emulator.reset();
-    };
-    const onBenchmarkClick = async () => {
-        if (!emulator.benchmark) return;
-        const result = await showModal(
-            "Confirm",
-            "Are you sure you want to start a benchmark?\nThe benchmark is considered an expensive operation!"
-        );
-        if (!result) return;
-        const { delta, count, frequency_mhz } = emulator.benchmark();
-        await showToast(
-            `Took ${delta.toFixed(
-                2
-            )} seconds to run ${count} ticks (${frequency_mhz.toFixed(
-                2
-            )} Mhz)!`,
-            undefined,
-            7500
-        );
-    };
-    const onFullscreenClick = () => {
-        setFullscreenState(!fullscreenState);
-    };
-    const onKeyboardClick = () => {
-        setKeyboardVisible(!keyboardVisible);
-    };
-    const onInformationClick = () => {
-        setInfoVisible(!infoVisible);
-    };
-    const onHelpClick = () => {
-        showHelp();
-    };
-    const onDebugClick = () => {
-        setDebugVisible(!debugVisible);
-    };
-    const onThemeClick = () => {
-        setBackgroundIndex((backgroundIndex + 1) % backgrounds.length);
-    };
-    const onPaletteClick = () => {
-        const palette = emulator.changePalette?.();
-        setPaletteName(palette);
-    };
-    const onUploadFile = async (file: File) => {
-        const arrayBuffer = await file.arrayBuffer();
-        const romData = new Uint8Array(arrayBuffer);
-        emulator.boot({ engine: null, romName: file.name, romData: romData });
-        showToast(`Loaded ${file.name} ROM successfully!`);
-    };
-    const onEngineChange = (engine: string) => {
-        emulator.boot({ engine: engine.toLowerCase() });
-        showToast(
-            `${emulator.device.text} 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 = () => {
-        setFullscreenState(!fullscreenState);
-    };
-    const onKeyDown = (key: string) => {
-        emulator.keyPress(key);
-    };
-    const onKeyUp = (key: string) => {
-        emulator.keyLift(key);
-    };
-    const onGamepad = (id: string, isValid: boolean, connected = true) => {
-        if (connected) {
-            if (isValid) showToast(`🕹️ Gamepad connect ${id}`);
-            else showToast(`😥 Unsupported gamepad connect ${id}`, true);
-        } else if (isValid) {
-            showToast(`🕹️ Gamepad disconnected ${id}`, true);
-        }
-    };
-    const onDrawHandler = (handler: DrawHandler) => {
-        if (frameRef.current) return;
-        frameRef.current = true;
-        emulator.bind("frame", () => {
-            handler(emulator.imageBuffer, PixelFormat.RGB);
-            setFramerate(emulator.framerate);
-        });
-    };
-    const onClearHandler = (handler: ClearHandler) => {
-        if (errorRef.current) return;
-        errorRef.current = true;
-        emulator.bind("error", async () => {
-            await handler(undefined, require("../res/storm.png"), 0.2);
-        });
-    };
-
-    return (
-        <div className="app">
-            <Overlay text={"Drag to load ROM"} onFile={onFile} />
-            <Modal
-                title={modalTitle}
-                text={modalText}
-                contents={modalContents}
-                visible={modalVisible}
-                onConfirm={onModalConfirm}
-                onCancel={onModalCancel}
-            />
-            <Toast
-                text={toastText}
-                error={toastError}
-                visible={toastVisible}
-                onCancel={onToastCancel}
-            />
-            <Footer color={getBackground()}>
-                Built with ❤️ by{" "}
-                <Link href="https://joao.me" target="_blank">
-                    João Magalhães
-                </Link>
-            </Footer>
-            <PanelSplit
-                left={
-                    <div className="display-container">
-                        <Display
-                            fullscreen={fullscreenState}
-                            onDrawHandler={onDrawHandler}
-                            onClearHandler={onClearHandler}
-                            onMinimize={onMinimize}
-                        />
-                    </div>
-                }
-            >
-                <Section visible={keyboardVisible} separatorBottom={true}>
-                    {hasFeature(Feature.KeyboardChip8) && (
-                        <KeyboardChip8
-                            onKeyDown={onKeyDown}
-                            onKeyUp={onKeyUp}
-                        />
-                    )}
-                    {hasFeature(Feature.KeyboardGB) && (
-                        <KeyboardGB
-                            fullscreen={fullscreenState}
-                            onKeyDown={onKeyDown}
-                            onKeyUp={onKeyUp}
-                            onGamepad={onGamepad}
-                        />
-                    )}
-                </Section>
-                <Title
-                    text={emulator.name}
-                    version={emulator.version?.text}
-                    versionUrl={emulator.version?.url}
-                    iconSrc={require("../res/thunder.png")}
-                ></Title>
-                <Section>
-                    <Paragraph>
-                        This is a{" "}
-                        {emulator.device.url ? (
-                            <Link href={emulator.device.url} target="_blank">
-                                {emulator.device.text}
-                            </Link>
-                        ) : (
-                            emulator.device.text
-                        )}{" "}
-                        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>
-                    {emulator.repository && (
-                        <Paragraph>
-                            You can check the source code of it at{" "}
-                            {emulator.repository.url ? (
-                                <Link
-                                    href={emulator.repository.url}
-                                    target="_blank"
-                                >
-                                    {emulator.repository.text}
-                                </Link>
-                            ) : (
-                                <>{emulator.repository.text}</>
-                            )}
-                            .
-                        </Paragraph>
-                    )}
-                    <Paragraph>
-                        TIP: Drag and Drop ROM files to the Browser to load the
-                        ROM.
-                    </Paragraph>
-                </Section>
-                {debugVisible && (
-                    <Section>
-                        <div
-                            style={{
-                                display: "inline-block",
-                                verticalAlign: "top",
-                                marginRight: 32,
-                                width: 256
-                            }}
-                        >
-                            <h3>VRAM Tiles</h3>
-                            <Tiles
-                                getTile={(index) => emulator.getTile(index)}
-                                tileCount={384}
-                                width={"100%"}
-                                contentBox={false}
-                            />
-                        </div>
-                        <div
-                            style={{
-                                display: "inline-block",
-                                verticalAlign: "top"
-                            }}
-                        >
-                            <h3>Registers</h3>
-                            <RegistersGB
-                                getRegisters={() => emulator.registers}
-                            />
-                        </div>
-                    </Section>
-                )}
-                {infoVisible && (
-                    <Section>
-                        <PanelTab
-                            tabs={[
-                                <Info>
-                                    <Pair
-                                        key="button-engine"
-                                        name={"Engine"}
-                                        valueNode={
-                                            <ButtonSwitch
-                                                options={emulator.engines.map(
-                                                    (e) => e.toUpperCase()
-                                                )}
-                                                size={"large"}
-                                                style={["simple"]}
-                                                onChange={onEngineChange}
-                                            />
-                                        }
-                                    />
-                                    <Pair
-                                        key="rom"
-                                        name={"ROM"}
-                                        value={romInfo.name ?? "-"}
-                                    />
-                                    <Pair
-                                        key="rom-size"
-                                        name={"ROM Size"}
-                                        value={
-                                            romInfo.size
-                                                ? `${new Intl.NumberFormat().format(
-                                                      romInfo.size
-                                                  )} bytes`
-                                                : "-"
-                                        }
-                                    />
-                                    <Pair
-                                        key="button-frequency"
-                                        name={"CPU Frequency"}
-                                        valueNode={
-                                            <ButtonIncrement
-                                                value={
-                                                    emulator.frequency /
-                                                    1000 /
-                                                    1000
-                                                }
-                                                delta={
-                                                    (emulator.frequencyDelta ??
-                                                        FREQUENCY_DELTA) /
-                                                    1000 /
-                                                    1000
-                                                }
-                                                min={0}
-                                                suffix={"MHz"}
-                                                decimalPlaces={2}
-                                                onChange={onFrequencyChange}
-                                                onReady={onFrequencyReady}
-                                            />
-                                        }
-                                    />
-                                    <Pair
-                                        key="rom-type"
-                                        name={"ROM Type"}
-                                        value={
-                                            romInfo.extra?.romType
-                                                ? `${romInfo.extra?.romType}`
-                                                : "-"
-                                        }
-                                    />
-                                    <Pair
-                                        key="framerate"
-                                        name={"Framerate"}
-                                        value={`${framerate} fps`}
-                                    />
-                                </Info>,
-                                <Info>
-                                    <Pair
-                                        key="palette"
-                                        name={"Palette"}
-                                        value={paletteName}
-                                    />
-                                </Info>
-                            ]}
-                            tabNames={["General", "Detailed"]}
-                            selectors={true}
-                        />
-                    </Section>
-                )}
-                <Section>
-                    <ButtonContainer>
-                        <Button
-                            text={getPauseText()}
-                            image={getPauseIcon()}
-                            imageAlt="pause"
-                            enabled={paused}
-                            style={["simple", "border", "padded"]}
-                            onClick={onPauseClick}
-                        />
-                        <Button
-                            text={"Reset"}
-                            image={require("../res/reset.svg")}
-                            imageAlt="reset"
-                            style={["simple", "border", "padded"]}
-                            onClick={onResetClick}
-                        />
-                        {hasFeature(Feature.Benchmark) && (
-                            <Button
-                                text={"Benchmark"}
-                                image={require("../res/bolt.svg")}
-                                imageAlt="benchmark"
-                                style={["simple", "border", "padded"]}
-                                onClick={onBenchmarkClick}
-                            />
-                        )}
-                        <Button
-                            text={"Fullscreen"}
-                            image={require("../res/maximise.svg")}
-                            imageAlt="maximise"
-                            style={["simple", "border", "padded"]}
-                            onClick={onFullscreenClick}
-                        />
-                        {hasFeature(Feature.Keyboard) && (
-                            <Button
-                                text={"Keyboard"}
-                                image={require("../res/dialpad.svg")}
-                                imageAlt="keyboard"
-                                enabled={keyboardVisible}
-                                style={["simple", "border", "padded"]}
-                                onClick={onKeyboardClick}
-                            />
-                        )}
-                        <Button
-                            text={"Information"}
-                            image={require("../res/info.svg")}
-                            imageAlt="information"
-                            enabled={infoVisible}
-                            style={["simple", "border", "padded"]}
-                            onClick={onInformationClick}
-                        />
-                        <Button
-                            text={"Help"}
-                            image={require("../res/help.svg")}
-                            imageAlt="help"
-                            style={["simple", "border", "padded"]}
-                            onClick={onHelpClick}
-                        />
-                        {hasFeature(Feature.Debug) && (
-                            <Button
-                                text={"Debug"}
-                                image={require("../res/bug.svg")}
-                                imageAlt="debug"
-                                enabled={debugVisible}
-                                style={["simple", "border", "padded"]}
-                                onClick={onDebugClick}
-                            />
-                        )}
-                        <Button
-                            text={"Theme"}
-                            image={require("../res/marker.svg")}
-                            imageAlt="theme"
-                            style={["simple", "border", "padded"]}
-                            onClick={onThemeClick}
-                        />
-                        {hasFeature(Feature.Palettes) && (
-                            <Button
-                                text={"Palette"}
-                                image={require("../res/brightness.svg")}
-                                imageAlt="palette"
-                                style={["simple", "border", "padded"]}
-                                onClick={onPaletteClick}
-                            />
-                        )}
-                        <Button
-                            text={"Load ROM"}
-                            image={require("../res/upload.svg")}
-                            imageAlt="upload"
-                            file={true}
-                            accept={".gb"}
-                            style={["simple", "border", "padded"]}
-                            onFile={onUploadFile}
-                        />
-                    </ButtonContainer>
-                </Section>
-            </PanelSplit>
-        </div>
-    );
-};
-
-export const startApp = (
-    element: string,
-    {
-        emulator,
-        fullscreen = false,
-        debug = false,
-        keyboard = false,
-        palette,
-        backgrounds = []
-    }: {
-        emulator: Emulator;
-        fullscreen?: boolean;
-        debug?: boolean;
-        keyboard?: boolean;
-        palette?: string;
-        backgrounds: string[];
-    }
-) => {
-    const elementRef = document.getElementById(element);
-    if (!elementRef) return;
-
-    const root = ReactDOM.createRoot(elementRef);
-    root.render(
-        <EmulatorApp
-            emulator={emulator}
-            fullscreen={fullscreen}
-            debug={debug}
-            keyboard={keyboard}
-            palette={palette}
-            backgrounds={backgrounds}
-        />
-    );
-};
-
-export default EmulatorApp;
diff --git a/frontends/web/react/components/button-container/button-container.css b/frontends/web/react/components/button-container/button-container.css
deleted file mode 100644
index 1d1df8f7..00000000
--- a/frontends/web/react/components/button-container/button-container.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.button-container {
-    margin-bottom: -12px;
-}
-
-.button-container > * {
-    margin-bottom: 12px;
-    margin-right: 8px;
-}
-
-.button-container > *:last-child {
-    margin-right: 0px;
-}
diff --git a/frontends/web/react/components/button-container/button-container.tsx b/frontends/web/react/components/button-container/button-container.tsx
deleted file mode 100644
index b11fc1f1..00000000
--- a/frontends/web/react/components/button-container/button-container.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { FC, ReactNode } from "react";
-
-import "./button-container.css";
-
-type ButtonContainerProps = {
-    children: ReactNode;
-    style?: string[];
-};
-
-export const ButtonContainer: FC<ButtonContainerProps> = ({
-    children,
-    style = []
-}) => {
-    const classes = () => ["button-container", ...style].join(" ");
-    return <div className={classes()}>{children}</div>;
-};
-
-export default ButtonContainer;
diff --git a/frontends/web/react/components/button-increment/button-increment.css b/frontends/web/react/components/button-increment/button-increment.css
deleted file mode 100644
index 57f94577..00000000
--- a/frontends/web/react/components/button-increment/button-increment.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.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/frontends/web/react/components/button-increment/button-increment.tsx b/frontends/web/react/components/button-increment/button-increment.tsx
deleted file mode 100644
index f8060938..00000000
--- a/frontends/web/react/components/button-increment/button-increment.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { FC, useEffect, 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;
-    decimalPlaces?: number;
-    size?: string;
-    style?: string[];
-    onClick?: () => void;
-    onBeforeChange?: (value: number) => boolean;
-    onChange?: (value: number) => void;
-    onReady?: (setValue: (value: number) => void) => void;
-};
-
-export const ButtonIncrement: FC<ButtonIncrementProps> = ({
-    value,
-    delta = 1,
-    min,
-    max,
-    prefix,
-    suffix,
-    decimalPlaces,
-    size = "medium",
-    style = ["simple", "border"],
-    onClick,
-    onBeforeChange,
-    onChange,
-    onReady
-}) => {
-    const [valueState, setValue] = useState(value);
-    const classes = () => ["button-increment", size, ...style].join(" ");
-    useEffect(() => {
-        onReady && onReady((value) => setValue(value));
-    }, []);
-    const _onMinusClick = () => {
-        let valueNew = valueState - delta;
-        if (onBeforeChange) {
-            if (!onBeforeChange(valueNew)) return;
-        }
-        if (min !== undefined) valueNew = Math.max(min, valueNew);
-        if (valueNew === valueState) return;
-        setValue(valueNew);
-        if (onChange) onChange(valueNew);
-    };
-    const _onPlusClick = () => {
-        let valueNew = valueState + delta;
-        if (onBeforeChange) {
-            if (!onBeforeChange(valueNew)) return;
-        }
-        if (max !== undefined) valueNew = Math.min(max, valueNew);
-        if (valueNew === valueState) 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">
-                {decimalPlaces ? valueState.toFixed(decimalPlaces) : valueState}
-            </span>
-            {suffix && <span className="suffix">{suffix}</span>}
-            <Button
-                text={"+"}
-                size={size}
-                style={["simple"]}
-                onClick={_onPlusClick}
-            />
-        </span>
-    );
-};
-
-export default ButtonIncrement;
diff --git a/frontends/web/react/components/button-switch/button-switch.tsx b/frontends/web/react/components/button-switch/button-switch.tsx
deleted file mode 100644
index 54c5bd27..00000000
--- a/frontends/web/react/components/button-switch/button-switch.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-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/frontends/web/react/components/button/button.css b/frontends/web/react/components/button/button.css
deleted file mode 100644
index 910de45c..00000000
--- a/frontends/web/react/components/button/button.css
+++ /dev/null
@@ -1,123 +0,0 @@
-.button {
-    cursor: pointer;
-    display: inline-flex;
-    transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    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;
-    -webkit-tap-highlight-color: transparent;
-    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: 0px 10px 0px 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:focus,
-.button.simple:hover {
-    background-color: #50cb93;
-}
-
-.button.simple.red:focus,
-.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.medium > img {
-    width: 20px;
-}
-
-.button.simple.large > img {
-    width: 28px;
-}
-
-.button.simple.very-large > img {
-    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/frontends/web/react/components/button/button.tsx b/frontends/web/react/components/button/button.tsx
deleted file mode 100644
index 5c5a734b..00000000
--- a/frontends/web/react/components/button/button.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { ChangeEvent, FC, useRef } from "react";
-
-import "./button.css";
-
-type ButtonProps = {
-    text: string;
-    image?: string;
-    imageAlt?: string;
-    enabled?: boolean;
-    focusable?: boolean;
-    file?: boolean;
-    accept?: string;
-    size?: string;
-    style?: string[];
-    onClick?: () => void;
-    onFile?: (file: File) => void;
-};
-
-export const Button: FC<ButtonProps> = ({
-    text,
-    image,
-    imageAlt,
-    enabled = false,
-    focusable = true,
-    file = false,
-    accept = ".txt",
-    size = "small",
-    style = ["simple", "border"],
-    onClick,
-    onFile
-}) => {
-    const classes = () =>
-        [
-            "button",
-            size,
-            enabled ? "enabled" : "",
-            file ? "file" : "",
-            ...style
-        ].join(" ");
-    const fileRef = useRef<HTMLInputElement>(null);
-    const onFileChange = (event: ChangeEvent<HTMLInputElement>) => {
-        if (!event.target.files) return;
-        if (event.target.files.length === 0) return;
-        const file = event.target.files[0];
-        onFile && onFile(file);
-        event.target.value = "";
-    };
-    const onMouseDown = (event: React.MouseEvent) => {
-        event.stopPropagation();
-        event.preventDefault();
-    };
-    const onMouseUp = (event: React.MouseEvent) => {
-        event.stopPropagation();
-        event.preventDefault();
-    };
-    const onKeyPress = (event: React.KeyboardEvent) => {
-        if (event.key !== "Enter") return;
-        if (file) fileRef.current?.click();
-        onClick && onClick();
-    };
-    const renderSimple = () => (
-        <span
-            className={classes()}
-            onClick={onClick}
-            onKeyPress={onKeyPress}
-            tabIndex={focusable ? 0 : undefined}
-        >
-            {text}
-        </span>
-    );
-    const renderComplex = () => (
-        <span
-            className={classes()}
-            onClick={onClick}
-            onMouseDown={onMouseDown}
-            onMouseUp={onMouseUp}
-            onKeyPress={onKeyPress}
-            tabIndex={focusable ? 0 : undefined}
-        >
-            {image && <img src={image} alt={imageAlt ?? text ?? "button"} />}
-            {file && (
-                <input
-                    ref={fileRef}
-                    type="file"
-                    accept={accept}
-                    onChange={onFileChange}
-                />
-            )}
-            <span>{text}</span>
-        </span>
-    );
-    return image ? renderComplex() : renderSimple();
-};
-
-export default Button;
diff --git a/frontends/web/react/components/canvas/canvas.css b/frontends/web/react/components/canvas/canvas.css
deleted file mode 100644
index e69de29b..00000000
diff --git a/frontends/web/react/components/canvas/canvas.tsx b/frontends/web/react/components/canvas/canvas.tsx
deleted file mode 100644
index 5cad1d5c..00000000
--- a/frontends/web/react/components/canvas/canvas.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { FC, useEffect, useRef } from "react";
-
-import "./canvas.css";
-
-export type CanvasStructure = {
-    canvas: HTMLCanvasElement;
-    canvasContext: CanvasRenderingContext2D;
-    canvasImage: ImageData;
-    canvasBuffer: DataView;
-};
-
-type CanvasProps = {
-    width: number;
-    height: number;
-    scaledWidth?: number | string;
-    scale?: number;
-    style?: string[];
-    onCanvas?: (structure: CanvasStructure) => void;
-};
-
-export const Canvas: FC<CanvasProps> = ({
-    width,
-    height,
-    scaledWidth,
-    scale = 1,
-    style = [],
-    onCanvas
-}) => {
-    const classes = () => ["canvas", ...style].join(" ");
-    const canvasRef = useRef<HTMLCanvasElement>(null);
-    useEffect(() => {
-        if (canvasRef.current) {
-            const structure = initCanvas(
-                width,
-                height,
-                scale,
-                canvasRef.current
-            );
-            onCanvas && onCanvas(structure);
-        }
-    }, [canvasRef]);
-    return (
-        <canvas
-            ref={canvasRef}
-            className={classes()}
-            style={{ width: scaledWidth ?? width * scale }}
-            width={width}
-            height={height}
-        />
-    );
-};
-
-const initCanvas = (
-    width: number,
-    height: number,
-    scale: number,
-    canvas: HTMLCanvasElement,
-    smoothing = false
-): CanvasStructure => {
-    const canvasContext = canvas.getContext("2d")!;
-    canvasContext.imageSmoothingEnabled = smoothing;
-
-    const canvasImage = canvasContext.createImageData(width, height);
-    const canvasBuffer = new DataView(canvasImage.data.buffer);
-
-    return {
-        canvas: canvas,
-        canvasContext: canvasContext,
-        canvasImage: canvasImage,
-        canvasBuffer: canvasBuffer
-    };
-};
-
-export default Canvas;
diff --git a/frontends/web/react/components/display/display.css b/frontends/web/react/components/display/display.css
deleted file mode 100644
index 8fde45f8..00000000
--- a/frontends/web/react/components/display/display.css
+++ /dev/null
@@ -1,116 +0,0 @@
-.display {
-    max-width: 100%;
-}
-
-.display.fullscreen {
-    align-items: center;
-    background-color: #2d2d2d;
-    display: flex;
-    height: 100%;
-    justify-content: center;
-    left: 0px;
-    position: fixed;
-    top: 0px;
-    width: 100%;
-    z-index: 6;
-}
-
-.display > .magnify-button {
-    cursor: pointer;
-    display: inline-block;
-    transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-}
-
-.display > .magnify-button:hover {
-    transform: scale(1.3, 1.3);
-    -o-transform: scale(1.3, 1.3);
-    -ms-transform: scale(1.3, 1.3);
-    -moz-transform: scale(1.3, 1.3);
-    -khtml-transform: scale(1.3, 1.3);
-    -webkit-transform: scale(1.3, 1.3);
-}
-
-.display > .magnify-button:active {
-    transform: scale(1.0, 1.0);
-    -o-transform: scale(1.0, 1.0);
-    -ms-transform: scale(1.0, 1.0);
-    -moz-transform: scale(1.0, 1.0);
-    -khtml-transform: scale(1.0, 1.0);
-    -webkit-transform: scale(1.0, 1.0);
-}
-
-.display > .display-minimize {
-    bottom: 22px;
-    display: none;
-    position: absolute;
-    right: 22px;
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-@media only screen and (max-width: 1120px) {
-    .display > .display-minimize {
-        bottom: initial;
-        top: 22px;
-    }
-}
-
-.display > .display-minimize > img {
-    height: 32px;
-    width: 32px;
-}
-
-.display.fullscreen > .display-minimize {
-    display: block;
-}
-
-.display > .display-frame {
-    background-color: #1b1a17;
-    border: 2px solid #50cb93;
-    box-sizing: content-box;
-    -o-box-sizing: content-box;
-    -ms-box-sizing: content-box;
-    -moz-box-sizing: content-box;
-    -khtml-box-sizing: content-box;
-    -webkit-box-sizing: content-box;
-    font-size: 0px;
-    padding: 8px 8px 8px 8px;
-    transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-}
-
-.display.fullscreen > .display-frame {
-    border: none;
-    box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    -o-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    -ms-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    -moz-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    -khtml-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    -webkit-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24);
-    margin: 0px 0px 0px 0px;
-    max-width: unset;
-    padding: 0px 0px 0px 0px;
-}
-
-.display > .display-frame > .display-canvas {
-    outline: none;
-    -o-outline: none;
-    -ms-outline: none;
-    -moz-outline: none;
-    -khtml-outline: none;
-    -webkit-outline: none;
-    width: 100%;
-}
diff --git a/frontends/web/react/components/display/display.tsx b/frontends/web/react/components/display/display.tsx
deleted file mode 100644
index 4180efb4..00000000
--- a/frontends/web/react/components/display/display.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-import React, { FC, useState, useRef, useEffect } from "react";
-import { PixelFormat } from "../../structs";
-
-import "./display.css";
-
-const PIXEL_UNSET_COLOR = 0x1b1a17ff;
-
-declare const require: any;
-
-/**
- * Function that handles a draw operation into a
- * certain drawing context.
- */
-export type DrawHandler = (pixels: Uint8Array, format: PixelFormat) => void;
-
-export type ClearHandler = (
-    color?: number,
-    image?: string,
-    imageScale?: number
-) => Promise<void>;
-
-type DisplayOptions = {
-    width: number;
-    height: number;
-    logicWidth: number;
-    logicHeight: number;
-    scale?: number;
-};
-
-type DisplayProps = {
-    options?: DisplayOptions;
-    size?: string;
-    fullscreen?: boolean;
-    nativeFullscreen?: boolean;
-    style?: string[];
-    onDrawHandler?: (caller: DrawHandler) => void;
-    onClearHandler?: (caller: ClearHandler) => void;
-    onMinimize?: () => void;
-};
-
-type CanvasContents = {
-    canvas: HTMLCanvasElement;
-    canvasCtx: CanvasRenderingContext2D;
-    canvasBuffer: HTMLCanvasElement;
-    canvasBufferCtx: CanvasRenderingContext2D;
-    imageData: ImageData;
-    videoBuffer: DataView;
-};
-
-export const Display: FC<DisplayProps> = ({
-    options = { width: 320, height: 288, logicWidth: 160, logicHeight: 144 },
-    size = "small",
-    fullscreen = false,
-    nativeFullscreen = true,
-    style = [],
-    onDrawHandler,
-    onClearHandler,
-    onMinimize
-}) => {
-    options = {
-        ...options,
-        ...{ width: 320, height: 288, logicWidth: 160, logicHeight: 144 }
-    };
-    if (!options.scale) {
-        options.scale = window.devicePixelRatio ? window.devicePixelRatio : 1;
-    }
-
-    const classes = () =>
-        ["display", fullscreen ? "fullscreen" : null, size, ...style].join(" ");
-
-    const [width, setWidth] = useState<number>();
-    const [height, setHeight] = useState<number>();
-    const canvasRef = useRef<HTMLCanvasElement>(null);
-    const canvasContentsRef = useRef<CanvasContents>();
-    const resizeRef = useRef(() => {
-        const [fullWidth, fullHeight] = crop(options.width / options.height);
-        setWidth(fullWidth);
-        setHeight(fullHeight);
-    });
-
-    useEffect(() => {
-        if (canvasRef.current) {
-            canvasContentsRef.current = initCanvas(
-                options.logicWidth,
-                options.logicHeight,
-                canvasRef.current
-            );
-        }
-    }, [canvasRef, options.scale]);
-
-    useEffect(() => {
-        if (fullscreen) {
-            canvasRef.current?.focus();
-            resizeRef.current();
-            document.getElementsByTagName("body")[0].style.overflow = "hidden";
-            window.addEventListener("resize", resizeRef.current);
-
-            // requests the browser to go fullscreen using the
-            // body of the document as the entry HTML element
-            if (nativeFullscreen && document.body.requestFullscreen) {
-                document.body.requestFullscreen().catch(() => {});
-            } else if (
-                nativeFullscreen &&
-                (document.body as any).webkitRequestFullscreen
-            ) {
-                (document.body as any).webkitRequestFullscreen();
-            }
-        } else {
-            setWidth(undefined);
-            setHeight(undefined);
-            document
-                .getElementsByTagName("body")[0]
-                .style.removeProperty("overflow");
-            window.removeEventListener("resize", resizeRef.current);
-
-            // restores the window mode, returning from the
-            // fullscreen browser
-            if (nativeFullscreen && document.exitFullscreen) {
-                document.exitFullscreen().catch(() => {});
-            } else if (
-                nativeFullscreen &&
-                (document as any).webkitExitFullscreen
-            ) {
-                (document as any).webkitExitFullscreen();
-            }
-        }
-        return () => {
-            window.removeEventListener("resize", resizeRef.current);
-        };
-    }, [fullscreen]);
-
-    if (onDrawHandler) {
-        onDrawHandler((pixels, format) => {
-            if (!canvasContentsRef.current) return;
-            updateCanvas(canvasContentsRef.current, pixels, format);
-        });
-    }
-
-    if (onClearHandler) {
-        onClearHandler(async (color, image, imageScale) => {
-            if (!canvasContentsRef.current) return;
-            await clearCanvas(canvasContentsRef.current, color, {
-                image: image,
-                imageScale: imageScale
-            });
-        });
-    }
-
-    return (
-        <div className={classes()}>
-            <span
-                className="magnify-button display-minimize"
-                onClick={onMinimize}
-            >
-                <img
-                    className="large"
-                    src={require("./minimise.svg")}
-                    alt="minimise"
-                />
-            </span>
-            <div
-                className="display-frame"
-                style={{ width: width ?? options.width, height: height }}
-            >
-                <canvas
-                    ref={canvasRef}
-                    tabIndex={-1}
-                    className="display-canvas"
-                    width={options.width * options.scale}
-                    height={options.height * options.scale}
-                ></canvas>
-            </div>
-        </div>
-    );
-};
-
-const initCanvas = (
-    width: number,
-    height: number,
-    canvas: HTMLCanvasElement
-): CanvasContents => {
-    // initializes the off-screen canvas that is going to be
-    // used in the drawing process, this is used essentially for
-    // performance reasons as it provides a way to draw pixels
-    // in the original size instead of the target one
-    const canvasBuffer = document.createElement("canvas");
-    canvasBuffer.width = width;
-    canvasBuffer.height = height;
-    const canvasBufferCtx = canvasBuffer.getContext("2d")!;
-    const imageData = canvasBufferCtx.createImageData(
-        canvasBuffer.width,
-        canvasBuffer.height
-    );
-    const videoBuffer = new DataView(imageData.data.buffer);
-
-    // initializes the visual canvas (where data is going to be written)
-    // with, resetting the transform vector to the identity and re-calculating
-    // the scale of the drawing properly
-    const canvasCtx = canvas.getContext("2d")!;
-    canvasCtx.setTransform(1, 0, 0, 1, 0, 0);
-    canvasCtx.scale(
-        canvas.width / canvasBuffer.width,
-        canvas.height / canvasBuffer.height
-    );
-    canvasCtx.imageSmoothingEnabled = false;
-
-    return {
-        canvas: canvas,
-        canvasCtx: canvasCtx,
-        canvasBuffer: canvasBuffer,
-        canvasBufferCtx: canvasBufferCtx,
-        imageData: imageData,
-        videoBuffer: videoBuffer
-    };
-};
-
-const updateCanvas = (
-    canvasContents: CanvasContents,
-    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);
-        canvasContents.videoBuffer.setUint32(offset, color);
-        offset += PixelFormat.RGBA;
-    }
-    canvasContents.canvasBufferCtx.putImageData(canvasContents.imageData, 0, 0);
-    canvasContents.canvasCtx.drawImage(canvasContents.canvasBuffer, 0, 0);
-};
-
-const clearCanvas = async (
-    canvasContents: CanvasContents,
-    color = PIXEL_UNSET_COLOR,
-    {
-        image = null,
-        imageScale = 1
-    }: { image?: string | null; imageScale?: number } = {}
-) => {
-    // uses the "clear" color to fill a rectangle with the complete
-    // size of the canvas contents
-    canvasContents.canvasCtx.fillStyle = `#${color.toString(16).toUpperCase()}`;
-    canvasContents.canvasCtx.fillRect(
-        0,
-        0,
-        canvasContents.canvas.width,
-        canvasContents.canvas.height
-    );
-
-    // in case an image was requested then uses that to load
-    // an image at the center of the screen properly scaled
-    if (image) {
-        await drawImageCanvas(canvasContents, image, imageScale);
-    }
-};
-
-const drawImageCanvas = async (
-    canvasContents: CanvasContents,
-    image: string,
-    imageScale = 1.0
-) => {
-    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] = [
-        canvasContents.canvas.width / 2 - imgWidth / 2,
-        canvasContents.canvas.height / 2 - imgHeight / 2
-    ];
-    canvasContents.canvasCtx.setTransform(1, 0, 0, 1, 0, 0);
-    try {
-        canvasContents.canvasCtx.drawImage(img, x0, y0, imgWidth, imgHeight);
-    } finally {
-        canvasContents.canvasCtx.scale(
-            canvasContents.canvas.width / canvasContents.canvasBuffer.width,
-            canvasContents.canvas.height / canvasContents.canvasBuffer.height
-        );
-    }
-};
-
-const crop = (ratio: number): [number, number] => {
-    // 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 > ratio) {
-        return [window.innerWidth * (ratio / windowRatio), window.innerHeight];
-    } else {
-        return [window.innerWidth, window.innerHeight * (windowRatio / ratio)];
-    }
-};
-
-export default Display;
diff --git a/frontends/web/react/components/display/minimise.svg b/frontends/web/react/components/display/minimise.svg
deleted file mode 100644
index 3d41ee13..00000000
--- a/frontends/web/react/components/display/minimise.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="minimiseIconTitle" stroke="#ffffff" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="minimiseIconTitle">Minimise View</title> <polyline points="8 3 8 8 3 8"/> <polyline points="21 8 16 8 16 3"/> <polyline points="3 16 8 16 8 21"/> <polyline points="16 21 16 16 21 16"/> </svg>
\ No newline at end of file
diff --git a/frontends/web/react/components/footer/footer.css b/frontends/web/react/components/footer/footer.css
deleted file mode 100644
index 2958e0d4..00000000
--- a/frontends/web/react/components/footer/footer.css
+++ /dev/null
@@ -1,24 +0,0 @@
-.footer > .footer-background {
-    bottom: 0px;
-    filter: blur(1.0rem);
-    -o-filter: blur(1.0rem);
-    -ms-filter: blur(1.0rem);
-    -moz-filter: blur(1.0rem);
-    -khtml-filter: blur(1.0rem);
-    -webkit-filter: blur(1.0rem);
-    height: 40px;
-    left: 0px;
-    position: fixed;
-    width: 100%;
-}
-
-.footer > .footer-contents {
-    bottom: 0px;
-    height: 40px;
-    left: 0px;
-    line-height: 40px;
-    padding: 0px 0px 0px 0px;
-    position: fixed;
-    text-align: center;
-    width: 100%;
-}
diff --git a/frontends/web/react/components/footer/footer.tsx b/frontends/web/react/components/footer/footer.tsx
deleted file mode 100644
index e33c01e5..00000000
--- a/frontends/web/react/components/footer/footer.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React, { FC, ReactNode } from "react";
-
-import "./footer.css";
-
-type FooterProps = {
-    children: ReactNode;
-    color?: string;
-    style?: string[];
-};
-
-export const Footer: FC<FooterProps> = ({
-    children,
-    color = "ffffff",
-    style = []
-}) => {
-    const classes = () => ["footer", ...style].join(" ");
-    return (
-        <div className={classes()}>
-            <div
-                className="footer-background"
-                style={{ backgroundColor: `#${color}f2` }}
-            ></div>
-            <div className="footer-contents">{children}</div>
-        </div>
-    );
-};
-
-export default Footer;
diff --git a/frontends/web/react/components/help/help.css b/frontends/web/react/components/help/help.css
deleted file mode 100644
index 9cfbd320..00000000
--- a/frontends/web/react/components/help/help.css
+++ /dev/null
@@ -1,53 +0,0 @@
-.help {
-    display: flex;
-    flex-direction: column;
-    overflow: auto;
-}
-
-.keyboard-help {
-    font-size: 18px;
-    line-height: 22px;
-    list-style: none;
-    margin: 0px 0px 0px 0px;
-    padding-left: 0px;
-}
-
-.keyboard-help > li {
-    margin: 12px 0px 12px 0px;
-}
-
-.keyboard-help .key-container {
-    display: inline-block;
-    margin-right: 18px;
-    min-width: 86px;
-    text-align: right;
-}
-
-.keyboard-help .key {
-    border: 2px solid #ffffff;
-    border-radius: 6px 6px 6px 6px;
-    -o-border-radius: 6px 6px 6px 6px;
-    -ms-border-radius: 6px 6px 6px 6px;
-    -moz-border-radius: 6px 6px 6px 6px;
-    -khtml-border-radius: 6px 6px 6px 6px;
-    -webkit-border-radius: 6px 6px 6px 6px;
-    display: inline-block;
-    font-size: 16px;
-    margin-right: 8px;
-    min-width: 34px;
-    padding: 1px 8px 1px 8px;
-    text-align: center;
-}
-
-.keyboard-help .key:last-child {
-    margin-right: 0px;
-}
-
-.faqs-help > h3 {
-    margin: 0px 0px 6px 0px;
-}
-
-.faqs-help > p {
-    line-height: 20px;
-    margin: 0px 0px 22px 0px;
-}
diff --git a/frontends/web/react/components/help/help.tsx b/frontends/web/react/components/help/help.tsx
deleted file mode 100644
index 1fc70a63..00000000
--- a/frontends/web/react/components/help/help.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import React, { FC } from "react";
-import Link from "../link/link";
-import PanelTab from "../panel-tab/panel-tab";
-
-import "./help.css";
-
-type HelpProps = {
-    style?: string[];
-};
-
-export const Help: FC<HelpProps> = ({ style = [] }) => {
-    const classes = () => ["help", ...style].join(" ");
-    return (
-        <div className={classes()}>
-            <PanelTab
-                tabs={[keyboardHelp(), faqsHelp()]}
-                tabNames={["Keyboard", "FAQs"]}
-            />
-        </div>
-    );
-};
-
-const keyboardHelp = () => (
-    <ul className="keyboard-help">
-        <li>
-            <span className="key-container">
-                <span className="key">Enter</span>
-            </span>
-            Start button
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">Space</span>
-            </span>
-            Select button
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">A</span>
-            </span>
-            A button
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">S</span>
-            </span>
-            B button
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">←</span>
-                <span className="key">→</span>
-            </span>
-            Horizontal control
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">↑</span>
-                <span className="key">↓</span>
-            </span>
-            Vertical control
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">Escape</span>
-            </span>
-            Exit fullscreen
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">Ctrl + D</span>
-            </span>
-            Turbo speed
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">Ctrl + F</span>
-            </span>
-            Toggle fullscreen
-        </li>
-        <li>
-            <span className="key-container">
-                <span className="key">Ctrl + K</span>
-            </span>
-            Toggle on-screen keyboard
-        </li>
-    </ul>
-);
-
-const faqsHelp = () => (
-    <div className="faqs-help">
-        <h3>Does it play all Game Boy games?</h3>
-        <p>
-            Not really, but it plays the coolest ones. Now seriously it should
-            play around 90% of the Game Boy games.
-        </p>
-        <h3>Why there's no sound?</h3>
-        <p>It's under development, I'm hopping to have it before Christmas.</p>
-        <h3>Can I use my Xbox One game pad?</h3>
-        <p>
-            Yes, just plug it in and press a button.
-            <br />
-            BTW: This uses the{" "}
-            <Link
-                href="https://developer.mozilla.org/docs/Web/API/Gamepad_API/Using_the_Gamepad_API"
-                target="_blank"
-            >
-                Web Gamepad API
-            </Link>{" "}
-            🕹️.
-        </p>
-        <h3>Will it ever play Game Boy Color games?</h3>
-        <p>Eventually...</p>
-        <h3>I've found a bug, where can I report it?</h3>
-        <p>
-            Use the{" "}
-            <Link
-                href="https://github.com/joamag/boytacean/issues"
-                target="_blank"
-            >
-                GitHub issue tracker
-            </Link>
-            .
-        </p>
-        <h3>What's WebAssembly?</h3>
-        <p>
-            The coolest thing that happened to the Web in the latest years,
-            check the{" "}
-            <Link
-                href="https://en.wikipedia.org/wiki/WebAssembly"
-                target="_blank"
-            >
-                Wikipedia page
-            </Link>{" "}
-            for more info.
-        </p>
-        <h3>Why another Game Boy emulator?</h3>
-        <p>Because it's cool, and a challenging problem to solve.</p>
-    </div>
-);
-
-export default Help;
diff --git a/frontends/web/react/components/index.ts b/frontends/web/react/components/index.ts
deleted file mode 100644
index 4c0acbae..00000000
--- a/frontends/web/react/components/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export * from "./button/button";
-export * from "./button-container/button-container";
-export * from "./button-increment/button-increment";
-export * from "./button-switch/button-switch";
-export * from "./canvas/canvas";
-export * from "./display/display";
-export * from "./footer/footer";
-export * from "./help/help";
-export * from "./info/info";
-export * from "./keyboard-chip8/keyboard-chip8";
-export * from "./keyboard-gb/keyboard-gb";
-export * from "./link/link";
-export * from "./modal/modal";
-export * from "./overlay/overlay";
-export * from "./pair/pair";
-export * from "./panel-split/panel-split";
-export * from "./panel-tab/panel-tab";
-export * from "./paragraph/paragraph";
-export * from "./registers-gb/registers-gb";
-export * from "./section/section";
-export * from "./tiles/tiles";
-export * from "./title/title";
-export * from "./toast/toast";
diff --git a/frontends/web/react/components/info/info.css b/frontends/web/react/components/info/info.css
deleted file mode 100644
index 936980db..00000000
--- a/frontends/web/react/components/info/info.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.info {
-    font-size: 24px;
-    margin: 0px 0px 0px 0px;
-    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/frontends/web/react/components/info/info.tsx b/frontends/web/react/components/info/info.tsx
deleted file mode 100644
index a899b91c..00000000
--- a/frontends/web/react/components/info/info.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-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/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css b/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css
deleted file mode 100644
index a9552b23..00000000
--- a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css
+++ /dev/null
@@ -1,51 +0,0 @@
-.keyboard-chip8 {
-    font-size: 0px;
-    text-align: center;
-    touch-callout: none !important;
-    -o-touch-callout: none !important;
-    -ms-touch-callout: none !important;
-    -moz-touch-callout: none !important;
-    -khtml-touch-callout: none !important;
-    -webkit-touch-callout: none !important;
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-.keyboard-chip8 > .keyboard-line {
-    margin-bottom: 12px;
-}
-
-.keyboard-chip8 > .keyboard-line:last-child {
-    margin-bottom: 0px;
-}
-
-.keyboard-chip8 .key {
-    border: 2px solid #ffffff;
-    border-radius: 5px 5px 5px 5px;
-    -o-border-radius: 5px 5px 5px 5px;
-    -ms-border-radius: 5px 5px 5px 5px;
-    -moz-border-radius: 5px 5px 5px 5px;
-    -khtml-border-radius: 5px 5px 5px 5px;
-    -webkit-border-radius: 5px 5px 5px 5px;
-    cursor: pointer;
-    display: inline-block;
-    font-size: 38px;
-    height: 48px;
-    line-height: 46px;
-    margin-right: 14px;
-    min-width: 48px;
-    padding: 0px 6px 0px 6px;
-    text-align: center;
-}
-
-.keyboard-chip8 .key:last-child {
-    margin-right: 0px;
-}
-
-.keyboard-chip8 .key.pressed {
-    background-color: #50cb93;
-}
diff --git a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx b/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx
deleted file mode 100644
index 2208c1c1..00000000
--- a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, { FC, useState } from "react";
-
-import "./keyboard-chip8.css";
-
-type KeyboardChip8Props = {
-    focusable?: boolean;
-    style?: string[];
-    onKeyDown?: (key: string) => void;
-    onKeyUp?: (key: string) => void;
-};
-
-export const KeyboardChip8: FC<KeyboardChip8Props> = ({
-    focusable = true,
-    style = [],
-    onKeyDown,
-    onKeyUp
-}) => {
-    const classes = () => ["keyboard", "keyboard-chip8", ...style].join(" ");
-    const renderKey = (key: string, styles: string[] = []) => {
-        const [pressed, setPressed] = useState(false);
-        const classes = ["key", pressed ? "pressed" : "", ...styles].join(" ");
-        return (
-            <span
-                className={classes}
-                key={key}
-                tabIndex={focusable ? 0 : undefined}
-                onKeyDown={(event) => {
-                    if (event.key !== "Enter") return;
-                    setPressed(true);
-                    onKeyDown && onKeyDown(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onKeyUp={(event) => {
-                    if (event.key !== "Enter") return;
-                    setPressed(false);
-                    onKeyUp && onKeyUp(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onBlur={(event) => {
-                    setPressed(false);
-                    onKeyUp && onKeyUp(key);
-                }}
-                onMouseDown={(event) => {
-                    setPressed(true);
-                    onKeyDown && onKeyDown(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onMouseUp={(event) => {
-                    setPressed(false);
-                    onKeyUp && onKeyUp(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onMouseLeave={(event) => {
-                    if (!pressed) return;
-                    setPressed(false);
-                    onKeyUp && onKeyUp(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onTouchStart={(event) => {
-                    setPressed(true);
-                    onKeyDown && onKeyDown(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onTouchEnd={(event) => {
-                    setPressed(false);
-                    onKeyUp && onKeyUp(key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-            >
-                {key}
-            </span>
-        );
-    };
-    return (
-        <div className={classes()}>
-            <div className="keyboard-line">
-                {["1", "2", "3", "4"].map((k) => renderKey(k))}
-            </div>
-            <div className="keyboard-line">
-                {["Q", "W", "E", "R"].map((k) => renderKey(k))}
-            </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>
-        </div>
-    );
-};
-
-export default KeyboardChip8;
diff --git a/frontends/web/react/components/keyboard-gb/keyboard-gb.css b/frontends/web/react/components/keyboard-gb/keyboard-gb.css
deleted file mode 100644
index 29ea8df0..00000000
--- a/frontends/web/react/components/keyboard-gb/keyboard-gb.css
+++ /dev/null
@@ -1,257 +0,0 @@
-.keyboard-container.fullscreen {
-    bottom: 30px;
-    left: 0px;
-    pointer-events: none;
-    position: fixed;
-    width: 100%;
-    z-index: 10;
-}
-
-@media only screen and (max-width: 1120px) {
-    .keyboard-container.fullscreen {
-        bottom: 0px;
-    }
-}
-
-.keyboard-gb {
-    font-size: 0px;
-    -webkit-tap-highlight-color: transparent;
-    text-align: center;
-    touch-callout: none !important;
-    -o-touch-callout: none !important;
-    -ms-touch-callout: none !important;
-    -moz-touch-callout: none !important;
-    -khtml-touch-callout: none !important;
-    -webkit-touch-callout: none !important;
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-.keyboard-gb.fullscreen {
-    background: rgba(0, 0, 0, 0.3);
-    border-radius: 24px 24px 24px 24px;
-    -o-border-radius: 24px 24px 24px 24px;
-    -ms-border-radius: 24px 24px 24px 24px;
-    -moz-border-radius: 24px 24px 24px 24px;
-    -khtml-border-radius: 24px 24px 24px 24px;
-    -webkit-border-radius: 24px 24px 24px 24px;
-    margin: 0px auto 0px auto;
-    max-width: 600px;
-    padding: 18px 0px 18px 0px;
-    pointer-events: initial;
-}
-
-@media only screen and (max-width: 600px) {
-    .keyboard-gb.fullscreen {
-        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;
-        padding: 12px 0px 12px 0px;
-    }
-}
-
-.keyboard-gb > .keyboard-line {
-    border-radius: 24px 24px 24px 24px;
-    -o-border-radius: 24px 24px 24px 24px;
-    -ms-border-radius: 24px 24px 24px 24px;
-    -moz-border-radius: 24px 24px 24px 24px;
-    -khtml-border-radius: 24px 24px 24px 24px;
-    -webkit-border-radius: 24px 24px 24px 24px;
-    margin-bottom: 12px;
-}
-
-.keyboard-gb > .keyboard-line:last-child {
-    margin-bottom: 0px;
-}
-
-.keyboard-gb .key {
-    border: 2px solid #ffffff;
-    border-radius: 5px 5px 5px 5px;
-    -o-border-radius: 5px 5px 5px 5px;
-    -ms-border-radius: 5px 5px 5px 5px;
-    -moz-border-radius: 5px 5px 5px 5px;
-    -khtml-border-radius: 5px 5px 5px 5px;
-    -webkit-border-radius: 5px 5px 5px 5px;
-    cursor: pointer;
-    display: inline-block;
-    font-size: 38px;
-    height: 48px;
-    line-height: 46px;
-    margin-right: 14px;
-    min-width: 48px;
-    padding: 0px 8px 0px 8px;
-    text-align: center;
-}
-
-.keyboard-gb .key:last-child {
-    margin-right: 0px;
-}
-
-.keyboard-gb .key.pressed {
-    background-color: #50cb93;
-}
-
-.keyboard-gb > .dpad {
-    float: left;
-    text-align: center;
-    width: 172px;
-}
-
-@media only screen and (max-width: 600px) {
-    .keyboard-gb > .dpad {
-        width: 162px;
-    }
-}
-
-.keyboard-gb > .dpad > .dpad-top {
-    margin-bottom: -3px;
-}
-
-.keyboard-gb > .dpad > .dpad-bottom {
-    margin-top: -3px;
-}
-
-.keyboard-gb > .dpad .key {
-    border: none;
-    font-size: 24px;
-    padding: 0px 0px 0px 0px;
-}
-
-.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-left: -3px;
-    margin-right: 0px;
-}
-
-.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;
-}
-
-@media only screen and (max-width: 600px) {
-    .keyboard-gb > .action {
-        margin-right: 8px;
-    }
-}
-
-.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;
-    font-weight: 900;
-    height: 58px;
-    line-height: 52px;
-    width: 58px;
-}
-
-.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;
-}
-
-@media only screen and (max-width: 600px) {
-    .keyboard-gb > .options {
-        margin-top: 22px;
-    }
-}
-
-.keyboard-gb > .options > .key {
-    font-size: 14px;
-    font-weight: 900;
-    height: 30px;
-    line-height: 26px;
-    margin-left: 16px;
-    margin-right: 16px;
-    min-width: 100px;
-}
-
-.keyboard-gb > .break {
-    clear: both;
-}
diff --git a/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx b/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx
deleted file mode 100644
index 282cb400..00000000
--- a/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx
+++ /dev/null
@@ -1,332 +0,0 @@
-import React, { FC, useEffect, useRef, useState } from "react";
-import { isAndroid } from "../../util";
-
-import "./keyboard-gb.css";
-
-const KEYS: Record<string, string> = {
-    ArrowUp: "ArrowUp",
-    ArrowDown: "ArrowDown",
-    ArrowLeft: "ArrowLeft",
-    ArrowRight: "ArrowRight",
-    Enter: "Start",
-    " ": "Select",
-    a: "A",
-    s: "B"
-};
-
-const KEYS_XBOX: Record<number, string> = {
-    12: "ArrowUp",
-    102: "ArrowUp",
-    13: "ArrowDown",
-    103: "ArrowDown",
-    14: "ArrowLeft",
-    100: "ArrowLeft",
-    15: "ArrowRight",
-    101: "ArrowRight",
-    9: "Start",
-    8: "Select",
-    1: "A",
-    0: "B"
-};
-
-const PREVENT_KEYS: Record<string, boolean> = {
-    ArrowUp: true,
-    ArrowDown: true,
-    ArrowLeft: true,
-    ArrowRight: true,
-    " ": true
-};
-
-declare const require: any;
-
-enum Gamepad {
-    Unknown = 1,
-    Xbox,
-    Playstation,
-    Switch
-}
-
-type KeyboardGBProps = {
-    focusable?: boolean;
-    fullscreen?: boolean;
-    physical?: boolean;
-    selectedKeys?: string[];
-    style?: string[];
-    onKeyDown?: (key: string) => void;
-    onKeyUp?: (key: string) => void;
-    onGamepad?: (id: string, isValid: boolean, connected?: boolean) => void;
-};
-
-/**
- * The sequence of game pads that are considered
- * supported by the current implementation.
- */
-const SUPPORTED_PADS = [Gamepad.Xbox];
-
-export const KeyboardGB: FC<KeyboardGBProps> = ({
-    focusable = true,
-    fullscreen = false,
-    physical = true,
-    selectedKeys = [],
-    style = [],
-    onKeyDown,
-    onKeyUp,
-    onGamepad
-}) => {
-    const containerClasses = () =>
-        ["keyboard-container", fullscreen ? "fullscreen" : ""].join(" ");
-    const recordRef =
-        useRef<Record<string, React.Dispatch<React.SetStateAction<boolean>>>>();
-    const classes = () =>
-        [
-            "keyboard",
-            "keyboard-gb",
-            fullscreen ? "fullscreen" : "",
-            ...style
-        ].join(" ");
-    useEffect(() => {
-        if (!physical) return;
-        const getGamepadType = (gamepad: globalThis.Gamepad): Gamepad => {
-            let gamepadType = Gamepad.Unknown;
-            const isXbox = gamepad.id.includes("Xbox");
-            if (isXbox) gamepadType = Gamepad.Xbox;
-            return gamepadType;
-        };
-        const _onKeyDown = (event: KeyboardEvent) => {
-            const keyCode = KEYS[event.key];
-            const isPrevent = PREVENT_KEYS[event.key] ?? false;
-            if (isPrevent) event.preventDefault();
-            if (keyCode !== undefined) {
-                const records = recordRef.current ?? {};
-                const setter = records[keyCode];
-                setter(true);
-                onKeyDown && onKeyDown(keyCode);
-                return;
-            }
-        };
-        const _onKeyUp = (event: KeyboardEvent) => {
-            const keyCode = KEYS[event.key];
-            const isPrevent = PREVENT_KEYS[event.key] ?? false;
-            if (isPrevent) event.preventDefault();
-            if (keyCode !== undefined) {
-                const records = recordRef.current ?? {};
-                const setter = records[keyCode];
-                setter(false);
-                onKeyUp && onKeyUp(keyCode);
-                return;
-            }
-        };
-        const onGamepadConnected = (event: GamepadEvent) => {
-            const gamepad = event.gamepad;
-            let gamepadType = getGamepadType(gamepad);
-            const isValid = SUPPORTED_PADS.includes(gamepadType);
-
-            onGamepad && onGamepad(gamepad.id, isValid);
-
-            let keySolver: Record<number, string>;
-            switch (gamepadType) {
-                case Gamepad.Xbox:
-                    keySolver = KEYS_XBOX;
-                    break;
-            }
-
-            const buttonStates: Record<number, boolean> = {};
-
-            const updateStatus = () => {
-                const _gamepad = navigator.getGamepads()[gamepad.index];
-                if (!_gamepad) return;
-
-                handleButton(100, _gamepad.axes[0] < -0.5);
-                handleButton(101, _gamepad.axes[0] > 0.5);
-                handleButton(102, _gamepad.axes[1] < -0.5);
-                handleButton(103, _gamepad.axes[1] > 0.5);
-
-                _gamepad.buttons.forEach((button, index) => {
-                    const pressed = button.pressed;
-                    handleButton(index, pressed);
-                });
-
-                requestAnimationFrame(updateStatus);
-            };
-
-            const handleButton = (index: number, pressed: boolean) => {
-                const keyCode = keySolver[index];
-                if (keyCode === undefined) return;
-                const state = buttonStates[index] ?? false;
-                const keyDown = pressed && !state;
-                const keyUp = !pressed && state;
-
-                if (keyDown) {
-                    const records = recordRef.current ?? {};
-                    const setter = records[keyCode];
-                    setter(true);
-                    onKeyDown && onKeyDown(keyCode);
-                }
-
-                if (keyUp) {
-                    const records = recordRef.current ?? {};
-                    const setter = records[keyCode];
-                    setter(false);
-                    onKeyUp && onKeyUp(keyCode);
-                }
-
-                buttonStates[index] = pressed;
-            };
-
-            requestAnimationFrame(updateStatus);
-        };
-        const onGamepadDisconnected = (event: GamepadEvent) => {
-            const gamepad = event.gamepad;
-            let gamepadType = getGamepadType(gamepad);
-            const isValid = SUPPORTED_PADS.includes(gamepadType);
-
-            onGamepad && onGamepad(gamepad.id, isValid, false);
-        };
-        document.addEventListener("keydown", _onKeyDown);
-        document.addEventListener("keyup", _onKeyUp);
-        window.addEventListener("gamepadconnected", onGamepadConnected);
-        window.addEventListener("gamepaddisconnected", onGamepadDisconnected);
-        return () => {
-            document.removeEventListener("keydown", _onKeyDown);
-            document.removeEventListener("keyup", _onKeyUp);
-            window.removeEventListener("gamepadconnected", onGamepadConnected);
-            window.removeEventListener(
-                "gamepadconnected",
-                onGamepadDisconnected
-            );
-        };
-    }, []);
-    const renderKey = (
-        key: string,
-        keyName?: string,
-        selected = false,
-        styles: string[] = []
-    ) => {
-        const [pressed, setPressed] = useState(selected);
-        const classes = ["key", pressed ? "pressed" : "", ...styles].join(" ");
-        const records = recordRef.current ?? {};
-        records[keyName ?? key ?? "undefined"] = setPressed;
-        recordRef.current = records;
-        return (
-            <span
-                className={classes}
-                key={keyName ?? key}
-                tabIndex={focusable ? 0 : undefined}
-                onKeyDown={(event) => {
-                    if (event.key !== "Enter") return;
-                    setPressed(true);
-                    onKeyDown && onKeyDown(keyName ?? key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onKeyUp={(event) => {
-                    if (event.key !== "Enter") return;
-                    setPressed(false);
-                    onKeyUp && onKeyUp(keyName ?? key);
-                    event.stopPropagation();
-                    event.preventDefault();
-                }}
-                onBlur={(event) => {
-                    setPressed(false);
-                    onKeyUp && onKeyUp(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={containerClasses()}>
-            <div
-                className={classes()}
-                onTouchStart={(e) => e.preventDefault()}
-                onTouchEnd={(e) => e.preventDefault()}
-            >
-                <div className="dpad">
-                    <div className="dpad-top">
-                        {renderKey(
-                            isAndroid() ? "â–²" : "â–²",
-                            "ArrowUp",
-                            selectedKeys.includes("ArrowUp"),
-                            ["up"]
-                        )}
-                    </div>
-                    <div>
-                        {renderKey(
-                            isAndroid() ? "â—€" : "â—„",
-                            "ArrowLeft",
-                            selectedKeys.includes("ArrowLeft"),
-                            ["left"]
-                        )}
-                        {renderKey(
-                            isAndroid() ? "â–¶" : "â–º",
-                            "ArrowRight",
-                            selectedKeys.includes("ArrowRight"),
-                            ["right"]
-                        )}
-                    </div>
-                    <div className="dpad-bottom">
-                        {renderKey(
-                            isAndroid() ? "â–¼" : "â–¼",
-                            "ArrowDown",
-                            selectedKeys.includes("ArrowDown"),
-                            ["down"]
-                        )}
-                    </div>
-                </div>
-                <div className="action">
-                    {renderKey("B", "B", selectedKeys.includes("B"), ["b"])}
-                    {renderKey("A", "A", selectedKeys.includes("A"), ["a"])}
-                </div>
-                <div className="break"></div>
-                <div className="options">
-                    {renderKey(
-                        "SELECT",
-                        "Select",
-                        selectedKeys.includes("Select"),
-                        ["select"]
-                    )}
-                    {renderKey(
-                        "START",
-                        "Start",
-                        selectedKeys.includes("Start"),
-                        ["start"]
-                    )}
-                </div>
-            </div>
-        </div>
-    );
-};
-
-export default KeyboardGB;
diff --git a/frontends/web/react/components/link/link.css b/frontends/web/react/components/link/link.css
deleted file mode 100644
index 30a552d4..00000000
--- a/frontends/web/react/components/link/link.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.link {
-    border-bottom: 2px dotted #ffffff;
-    color: #ffffff;
-    text-decoration: none;
-    transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1);
-}
-
-.link:hover {
-    border-bottom: 2px solid #ffffff;
-}
diff --git a/frontends/web/react/components/link/link.tsx b/frontends/web/react/components/link/link.tsx
deleted file mode 100644
index ee5b134b..00000000
--- a/frontends/web/react/components/link/link.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-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/frontends/web/react/components/modal/close.svg b/frontends/web/react/components/modal/close.svg
deleted file mode 100644
index aeac9982..00000000
--- a/frontends/web/react/components/modal/close.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="closeIconTitle" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="closeIconTitle">Close</title> <path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"/> </svg>
\ No newline at end of file
diff --git a/frontends/web/react/components/modal/modal.css b/frontends/web/react/components/modal/modal.css
deleted file mode 100644
index 0c0ea90e..00000000
--- a/frontends/web/react/components/modal/modal.css
+++ /dev/null
@@ -1,149 +0,0 @@
-.modal {
-    align-items: center;
-    background-color: rgba(20, 20, 20, 0.95);
-    display: flex;
-    height: 100%;
-    justify-content: center;
-    left: 0px;
-    opacity: 0;
-    -o-opacity: 0;
-    -ms-opacity: 0;
-    -moz-opacity: 0;
-    -khtml-opacity: 0;
-    -webkit-opacity: 0;
-    padding: 12px 12px 12px 12px;
-    pointer-events: none;
-    position: fixed;
-    text-align: center;
-    top: 0px;
-    transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
-    width: 100%;
-    z-index: 10;
-}
-
-.modal.visible {
-    opacity: 1.0;
-    -o-opacity: 1.0;
-    -ms-opacity: 1.0;
-    -moz-opacity: 1.0;
-    -khtml-opacity: 1.0;
-    -webkit-opacity: 1.0;
-    pointer-events: initial;
-    transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-}
-
-.modal > .modal-window {
-    background-color: #264653;
-    border-radius: 6px 6px 6px 6px;
-    -o-border-radius: 6px 6px 6px 6px;
-    -ms-border-radius: 6px 6px 6px 6px;
-    -moz-border-radius: 6px 6px 6px 6px;
-    -khtml-border-radius: 6px 6px 6px 6px;
-    -webkit-border-radius: 6px 6px 6px 6px;
-    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    -o-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    -ms-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    -khtml-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
-    display: flex;
-    flex-direction: column;
-    margin-top: 30px;
-    max-height: 100%;
-    max-width: 100%;
-    outline: none;
-    -o-outline: none;
-    -ms-outline: none;
-    -moz-outline: none;
-    -khtml-outline: none;
-    -webkit-outline: none;
-    overflow-y: auto;
-    padding: 24px 24px 24px 24px;
-    text-align: left;
-    transform: scale(0.96);
-    -o-transform: scale(0.96);
-    -ms-transform: scale(0.96);
-    -moz-transform: scale(0.96);
-    -khtml-transform: scale(0.96);
-    -webkit-transform: scale(0.96);
-    transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    width: 480px;
-}
-
-.modal.visible > .modal-window {
-    margin-top: 0px;
-    pointer-events: all;
-    transform: scale(1);
-    -o-transform: scale(1);
-    -ms-transform: scale(1);
-    -moz-transform: scale(1);
-    -khtml-transform: scale(1);
-    -webkit-transform: scale(1);
-    transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-}
-
-.modal > .modal-window > .modal-top-buttons {
-    position: absolute;
-    right: 14px;
-    top: 14px;
-}
-
-.modal > .modal-window > .modal-title {
-    font-size: 32px;
-    margin-top: 0px;
-    text-align: left;
-}
-
-.modal > .modal-window > .modal-contents {
-    display: flex;
-    flex-direction: column;
-    overflow: auto;
-}
-
-.modal > .modal-window > .modal-text {
-    font-size: 20px;
-    line-height: 22px;
-    overflow: auto;
-}
-
-.modal > .modal-window > .modal-buttons {
-    font-size: 22px;
-    margin-top: 24px;
-    text-align: center;
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-.modal > .modal-window > .modal-buttons > .button.simple {
-    display: inline-block;
-    margin-right: 12px;
-    min-width: 120px;
-}
-
-.modal > .modal-window > .modal-buttons > .button.simple:last-child {
-    margin-right: 0px;
-}
diff --git a/frontends/web/react/components/modal/modal.tsx b/frontends/web/react/components/modal/modal.tsx
deleted file mode 100644
index 2f2573b9..00000000
--- a/frontends/web/react/components/modal/modal.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React, { FC, ReactNode, useEffect, useRef } from "react";
-import Button from "../button/button";
-
-import "./modal.css";
-
-declare const require: any;
-
-type ModalProps = {
-    title?: string;
-    text?: string;
-    contents?: ReactNode;
-    visible?: boolean;
-    buttons?: boolean;
-    overlayClose?: boolean;
-    style?: string[];
-    onConfirm?: () => void;
-    onCancel?: () => void;
-};
-
-export const Modal: FC<ModalProps> = ({
-    title = "Alert",
-    text,
-    contents,
-    visible = false,
-    buttons,
-    overlayClose = true,
-    style = [],
-    onConfirm,
-    onCancel
-}) => {
-    const classes = () =>
-        ["modal", visible ? "visible" : "", ...style].join(" ");
-    text =
-        text ??
-        (contents ? undefined : "Do you confirm the following operation?");
-    buttons = buttons ?? (contents ? false : true);
-    const modalRef = useRef<HTMLDivElement>(null);
-    useEffect(() => {
-        const onKeyDown = (event: KeyboardEvent) => {
-            if (event.key === "Escape") {
-                onCancel && onCancel();
-            }
-        };
-        document.addEventListener("keydown", onKeyDown);
-        return () => {
-            document.removeEventListener("keydown", onKeyDown);
-        };
-    }, []);
-    useEffect(() => {
-        if (visible) {
-            modalRef.current?.focus();
-        }
-    }, [visible]);
-    const getTextHtml = (separator = /\n/g) =>
-        text
-            ? {
-                  __html: text.replace(separator, "<br/>")
-              }
-            : undefined;
-    const onWindowClick = (
-        event: React.MouseEvent<HTMLDivElement, MouseEvent>
-    ) => {
-        if (!overlayClose) return;
-        event.stopPropagation();
-    };
-    return (
-        <div className={classes()} onClick={onCancel}>
-            <div
-                ref={modalRef}
-                className="modal-window"
-                onClick={onWindowClick}
-                tabIndex={-1}
-            >
-                <div className="modal-top-buttons">
-                    <Button
-                        text={""}
-                        size={"medium"}
-                        style={["simple", "rounded", "no-text"]}
-                        image={require("./close.svg")}
-                        imageAlt="close"
-                        onClick={onCancel}
-                    />
-                </div>
-                <h2 className="modal-title">{title}</h2>
-                {contents ? (
-                    <div className="modal-contents">{contents}</div>
-                ) : (
-                    <p
-                        className="modal-text"
-                        dangerouslySetInnerHTML={getTextHtml()}
-                    ></p>
-                )}
-                {buttons && (
-                    <div className="modal-buttons">
-                        <Button
-                            text={"Cancel"}
-                            size={"medium"}
-                            style={["simple", "red", "border", "padded-large"]}
-                            onClick={onCancel}
-                        />
-                        <Button
-                            text={"Confirm"}
-                            size={"medium"}
-                            style={["simple", "border", "padded-large"]}
-                            onClick={onConfirm}
-                        />
-                    </div>
-                )}
-            </div>
-        </div>
-    );
-};
-
-export default Modal;
diff --git a/frontends/web/react/components/overlay/overlay.css b/frontends/web/react/components/overlay/overlay.css
deleted file mode 100644
index 14ad1c9b..00000000
--- a/frontends/web/react/components/overlay/overlay.css
+++ /dev/null
@@ -1,44 +0,0 @@
-.overlay {
-    align-items: center;
-    background-color: rgba(80, 203, 147, 0.95);
-    display: flex;
-    font-size: 48px;
-    height: 100%;
-    justify-content: center;
-    left: 0px;
-    opacity: 0.0;
-    -o-opacity: 0.0;
-    -ms-opacity: 0.0;
-    -moz-opacity: 0.0;
-    -khtml-opacity: 0.0;
-    -webkit-opacity: 0.0;
-    pointer-events: none;
-    position: fixed;
-    text-align: center;
-    top: 0px;
-    transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
-    width: 100%;
-    z-index: 10;
-}
-
-.overlay.visible {
-    opacity: 1.0;
-    -o-opacity: 1.0;
-    -ms-opacity: 1.0;
-    -moz-opacity: 1.0;
-    -khtml-opacity: 1.0;
-    -webkit-opacity: 1.0;
-}
-
-.overlay .overlay-image {
-    margin-top: 16px;
-}
-
-.overlay .overlay-image > img {
-    width: 64px;
-}
diff --git a/frontends/web/react/components/overlay/overlay.tsx b/frontends/web/react/components/overlay/overlay.tsx
deleted file mode 100644
index 201d6d4b..00000000
--- a/frontends/web/react/components/overlay/overlay.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { FC, useEffect, useState } from "react";
-
-import "./overlay.css";
-
-declare const require: any;
-
-type OverlayProps = {
-    text?: string;
-    style?: string[];
-    onFile?: (file: File) => void;
-};
-
-export const Overlay: FC<OverlayProps> = ({ text, style = [], onFile }) => {
-    const [visible, setVisible] = useState(false);
-    const classes = () =>
-        ["overlay", visible ? "visible" : "", ...style].join(" ");
-    useEffect(() => {
-        const onDrop = async (event: DragEvent) => {
-            if (!event.dataTransfer?.items) return;
-            if (event.dataTransfer?.items[0].type) return;
-
-            setVisible(false);
-
-            const file = event.dataTransfer?.files[0];
-            onFile && onFile(file);
-
-            event.preventDefault();
-            event.stopPropagation();
-        };
-        const onDragOver = async (event: DragEvent) => {
-            if (!event.dataTransfer?.items) return;
-            if (event.dataTransfer?.items[0].type) return;
-            setVisible(true);
-            event.preventDefault();
-        };
-        const onDragEnter = async (event: DragEvent) => {
-            if (!event.dataTransfer?.items) return;
-            if (event.dataTransfer?.items[0].type) return;
-            setVisible(true);
-        };
-        const onDragLeave = async (event: DragEvent) => {
-            if (!event.dataTransfer?.items) return;
-            if (event.dataTransfer?.items[0].type) return;
-            setVisible(false);
-        };
-        document.addEventListener("drop", onDrop);
-        document.addEventListener("dragover", onDragOver);
-        document.addEventListener("dragenter", onDragEnter);
-        document.addEventListener("dragleave", onDragLeave);
-        return () => {
-            document.removeEventListener("drop", onDrop);
-            document.removeEventListener("dragover", onDragOver);
-            document.removeEventListener("dragenter", onDragEnter);
-            document.removeEventListener("dragleave", onDragLeave);
-        };
-    }, []);
-    return (
-        <div className={classes()}>
-            <div className="overlay-container">
-                {text && <div className="overlay-text">{text}</div>}
-                <div className="overlay-image">
-                    <img alt="sunglasses" src={require("./sunglasses.png")} />
-                </div>
-            </div>
-        </div>
-    );
-};
-
-export default Overlay;
diff --git a/frontends/web/react/components/overlay/sunglasses.png b/frontends/web/react/components/overlay/sunglasses.png
deleted file mode 100644
index 98b0bba649db0699a170016e18bc4b7a9ce3b70c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4330
zcmcgvc~BEs9&S(vkBW$}xPSt~v51PuGF);b3W$JmD##g?BOHMM3gHfpJIJAA5h2PE
zkSv0NARq+f2r3Zd5CORjNHP!wBqkw1BuQ_(TeVX&>#popIe&D$*YCZmuJ3r?@7Jjg
zc2=<M^4kFbfLWikZ~_2vXethD{SF$IqhEglfL+zr7RSy+Pkfk&xt>3p(8iuN){X0(
zBEYJRJlnRdd{U`v)Pcdyhh>$0FLvd)<}t&w=0?|n@G}VD;4YaA<$2`{nF$q#5}R9F
z2g<xi$1RT`Y;~+QC9eUpO2KiXR7G73ScH&IDXL+V_O0^;vu^km0~Xhe)r(!F9ZHfs
zogUa{USK}l+1Yt-@Z9~`t5x{yPWNtJ#LzYS-eS{!ZnuP_Z6?;zVr8Kf<9#5Tq`0q8
zAFe$MbHvw6DjFCN4h$z(%t#Z*J@Emew~o#}BMzFVS!BXUyM;?1w>jK(M6IW0z(#%g
z4n6+VYtF>lIkUZ)33Ne-F{hdx+p0P52r7Y&X)_hxg()droH6&Fnu+yNt;xMoF%}jN
z&%`AJ1O%je**p5>Bo)suEP4!%?IP&w=RDB}iix~jF_q7(Of7Esd9lUCD|X2n{?dQv
z8(+Qs#cAqAZG2-tv<f^7UF@&5y{w!RkFe-{yj$k%i`_sHME+&~P=*FE0N4ru2xtZg
zvkUmz)`pHZd35ZSam8R*PfJqcqa5*Ye2TDC-&AjP$1rIznLgfHtJtID2@i2X^&6z~
zhmg-NUG@0Rb$nY#w%d>55(q47)=jY|Emp8cIPi0UYdngTni5c}kjCtuoXTiYoN^dr
zP~OuM>I;R}Xjs2m7f)Y<H5ech%yq5tFGqy0;vQS%)SXMEa}543i2o#JLyWYAx>j#~
ze`;xo=v+mRwAaJ2=~=6$^hY8>>_jrE<Thn#&q1HQJZ*Ikuu;M_<e9j~tLfdwlo97K
z#&koBN+nXXbiMUutGcx}!jw36q<KcqSf`+O_uqy_hq2)niI*PmC26?f_Hbn55wFda
zIOU7N1buO}N6Vdc>^9ZBn|<Jn-#XfuTW*T=7j7aPRS0a@;;{IMtDvl$YGdTC8bL{x
z{;!HEL$Ys3(cgOG7k1h(SvHxHtxZjFE{)!1e;yWO$ri2|TkSJr!as9RdK(_enoGHB
zHB^vPP}L~@edcp!{fV5CJa|K(N-LJxBupqsuyyL~=b+CEM|f~;j$&6@{Ku>(v4^`<
zoAibSlqbxahU^7GiANF)C(WLsO>Mhkx+L%mW;`tnwb2JN%2#d-$wtz0``p7F9s93%
zd~3~<AkLz^)Mr0hdu7ed#v}$j;8V3fagOc4AN_ecZX%g^?>0On$g2Gn>AhWBhO3tt
zVz-szpge3F{L0k$5yjCd9mA%oTFeU~vA~bV=H(d(BQ36%m`J#F&$Q552&kAtwtAoj
z+*>}~zD0#5j*d6H7}kgtPcmI>UKnjR$HUa~aNufbhc)%_bUv@DL2WoVYzF|WJQ^5?
z<%}u=Kv5Fp;Q!@L|0)T-9s!9%2Q1Cju%klMXN=wu#zh3Mxs6yqKU}Ih<rcCBa4-xN
z`&y9v>Z8Bd=`*_<awNq|%%PjKC<C++3B0Hk?;LcrEb~PBmHTu3hDAoQy`lV%;pRr^
zBRO~jM;~*|2YpIlY@KHMy-qx?rNlk0v%8Yg8g$7>UZI8-)$H{lHXUrL4=uNBX=O5Z
zVxW<zaoYp$Ud*6nH%>;q_PI!}#a-1>t?Pm_I3x3{svgvYHuQ#juYggkOju)?;Kw+&
z_=TAEk6l)HD4MUMt2Ah=9Za&NESoOf4}%w6OuQ7_^OGCW6|?kE0!U-rpsovH03egO
zQN+K!gP(c&(%QdCAJt^jJrkl;Vwezae<rU+jSI5h&z$3bvTIV#yzgAv$vS+y5G~3A
z1-|Hs2Ay$%oP<7gS(vvYPE;6s!BBtau;<p)Q-x-x{>%NR^g$FJG+!L6Rvi~4z(@ax
zG_KUO2{cQjvo)?U>d1qMA~jeNCjZsGp2PV!(V|x)xxmQ)(cK;Nr8t(IK8{`7!9Mj2
zSnJ$I3m1|9^oJDzGO=eee04fB?0dBc4n2$Upd2(A7sS-gbv}t)xY<Wpm=In}#M##H
z7rvurNgxJ20Jk}nzZ3_RMQ>6rSaHF&J^ZF-hiv+2^6a5w;iq#=G&2l2UB29Dg}v9)
zwcT^kA`eV!p!8pR6W3Wv+?{s_Xc4y`Uby^evN@*qv@&qAghGKYHL{4l+<v3={02~C
z)mTbq@R`q^PAxJGA}1AT_LS!u_U-Qq<dU(-Y!xMef&F4ej09ON5A152i>8lfLTI9W
z&U#`$tlsRy`OWrN_k)C;A^7n^3?vprd!{yJt($O^UL?$gv|zFH+il^~cRm9(g@AgH
zqr3*Wpaf-9PGWKBHaZc5x(gR=Zj<7=Gg4w6zL=x5#{LipRJgSq4W;l_o)JMy>g9Ga
zWom(%WFgxdbYn~Hpcf8Yu6l00>FsRxK?d|duCq73Z!EiyHgo)Bm)1sllK&7T${W2{
zO^5Sd)7FK~cJJIx8cQQy5up}C(=xd3R(6k@bK2EHY?z+xON?#(8m2<=M%`6L)CHBH
zQoW8_)-c)PB4R?IM>ChWpq*{beB3#e@V=5#DC`Mqt_ecb@F%gaeJ(efIaV>HHRq7w
zOUx*LO#=i5%ZhPRgxl!hUffRzf4;@pGZol!X^Iz2ze`QN*@sN*OAPPc=0*0^mB_gl
z3xoWD5Zm^xi8xO#KPT=a_r_4Fs&WES`1+<ip9s8dQJHsqh*d>&l-jsJR~Bo9Bnz)W
z73MplTn4-;@pKKpr)pf=&ZxB~b$=PkH59)fq8Ipbao|UNF@)3a<ZP&NLQMoRKgNOK
zmX81M$t8{N!{DJWAHoEnyC$4^;qjp!dvU&T7YwFzv7PjvT<bOR;599Zm<l=GVF!oW
zQdd$_Ek}Vr<&msm#xtTSPAC?H%s#6GrzO#;xHvr;Z`w)AQnO}&?cS=@A;`ji7&8lw
z<#Nz-X$srrfcvNC=Npyriz8>a;PP^7!%DN}RH}62d*aizr!7|@i-b0uw>e}~*MD3s
zLR3@7ers<4m2m3W_0B+zNO1pxk|Cvx+4g%leepr%O2iL(vFoI<8Wp8@Bx+jVHk8+Y
zH1GmdPD+Xw>s>zVvzSFR*2%=r#khdeFZa6^SKqSayHdP0QKunJIq#yq1B1FIocDr`
zNOU@5XR!7nAMBX)VUIki9y{ukZql!+a&h6L={yV6O(qW)uS}!jedDx(>IBjhds94g
zWWDav2`XnyCl{n3t6tVS`4)0AGX10=!tKEqq+6D83#IU?ySoS6g=<`>I0poZ-gW$7
zKp@?pAyFS{l?3cZDVdVp(^f{D&g9uThH#!LNwMEad(eL(kTvptrIRJ~F{Y5qpQpKj
zX~A27I5WhPPlq$QFm`(2GhGd8Hm-&&Ew<(HznW=^FfD<;oE5j!>ko?`9Y1NleNz$?
zA^#y=zOuEU9_Ampd`JCcDVc<QnlCq8E4yzvJT{F_^5LP;D)RCN2VB~DNJENghSw{9
zRoT}J`;Z$b?>8=Ej4FLXalRjkQD^c(<<QY53G4aVkKg3+Bt1h6wgW4dlP<4%6KAU{
z%BZ*(<kd9W{_&m@`^yeu;*6JKqs=ei!E1yM(x}8bW@QELt#OpOPUDsh_7Y!W<4bE_
zY9r5&LWT6og0{A4)YzM#^<t9o%l&jVrRhR3yJ_@cXh`UR$T3)lZe)b{n}*Bt_4`OX
zn5Bs*M%Vp>N{{R0=f6Yov<Fq?tlShq&_Tyj2A)UMbMi-43FEz2;Pvt{oNEZ%-vb3f
axM#E1gr=bQ3>UHoU~Oq<QF;9QkADZ#7ywQH

diff --git a/frontends/web/react/components/pair/pair.css b/frontends/web/react/components/pair/pair.css
deleted file mode 100644
index e69de29b..00000000
diff --git a/frontends/web/react/components/pair/pair.tsx b/frontends/web/react/components/pair/pair.tsx
deleted file mode 100644
index 9e032e85..00000000
--- a/frontends/web/react/components/pair/pair.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-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/frontends/web/react/components/panel-split/panel-split.css b/frontends/web/react/components/panel-split/panel-split.css
deleted file mode 100644
index 8b927d8a..00000000
--- a/frontends/web/react/components/panel-split/panel-split.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.panel-split {
-    display: flex;
-}
-
-@media only screen and (max-width: 1120px) {
-    .panel-split {
-        flex-direction: column;
-    }
-}
-
-.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;
-}
-
-@media only screen and (max-width: 1120px) {
-    .panel-split > .side-right {
-        min-width: unset;
-        padding: 0px 0px 0px 0px;
-    }
-}
diff --git a/frontends/web/react/components/panel-split/panel-split.tsx b/frontends/web/react/components/panel-split/panel-split.tsx
deleted file mode 100644
index 10020f2f..00000000
--- a/frontends/web/react/components/panel-split/panel-split.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-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/frontends/web/react/components/panel-tab/panel-tab.css b/frontends/web/react/components/panel-tab/panel-tab.css
deleted file mode 100644
index 98460c1f..00000000
--- a/frontends/web/react/components/panel-tab/panel-tab.css
+++ /dev/null
@@ -1,65 +0,0 @@
-.panel-tab {
-    display: flex;
-    flex-direction: column;
-    overflow: auto;
-}
-
-.panel-tab > .tab-selectors {
-    line-height: 30px;
-    margin-bottom: 26px;
-}
-
-.panel-tab > .tab-selectors > .tab-selector {
-    border-bottom: 2px solid transparent;
-    cursor: pointer;
-    display: inline-block;
-    font-size: 22px;
-    margin-right: 18px;
-    opacity: 0.8;
-    -o-opacity: 0.8;
-    -ms-opacity: 0.8;
-    -moz-opacity: 0.8;
-    -khtml-opacity: 0.8;
-    -webkit-opacity: 0.8;
-    transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
-    user-select: none;
-    -o-user-select: none;
-    -ms-user-select: none;
-    -moz-user-select: none;
-    -khtml-user-select: none;
-    -webkit-user-select: none;
-}
-
-.panel-tab > .tab-selectors > .tab-selector:last-child {
-    margin-right: 0px;
-}
-
-.panel-tab > .tab-selectors > .tab-selector:hover {
-    opacity: 1.0;
-    -o-opacity: 1.0;
-    -ms-opacity: 1.0;
-    -moz-opacity: 1.0;
-    -khtml-opacity: 1.0;
-    -webkit-opacity: 1.0;
-}
-
-.panel-tab > .tab-selectors > .tab-selector.selected {
-    border-bottom: 2px solid #ffffff;
-    opacity: 1.0;
-    -o-opacity: 1.0;
-    -ms-opacity: 1.0;
-    -moz-opacity: 1.0;
-    -khtml-opacity: 1.0;
-    -webkit-opacity: 1.0;
-}
-
-.panel-tab > .tab-container {
-    display: flex;
-    flex-direction: column;
-    overflow: auto;
-}
diff --git a/frontends/web/react/components/panel-tab/panel-tab.tsx b/frontends/web/react/components/panel-tab/panel-tab.tsx
deleted file mode 100644
index 43fef5e6..00000000
--- a/frontends/web/react/components/panel-tab/panel-tab.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React, { FC, ReactNode, useState } from "react";
-
-import "./panel-tab.css";
-
-type PanelTabProps = {
-    tabs: ReactNode[];
-    tabNames: string[];
-    tabIndex?: number;
-    selectors?: boolean;
-    style?: string[];
-};
-
-export const PanelTab: FC<PanelTabProps> = ({
-    tabs,
-    tabNames,
-    tabIndex = 0,
-    selectors = true,
-    style = []
-}) => {
-    const classes = () => ["panel-tab", ...style].join(" ");
-    const [tabIndexState, setTabIndexState] = useState(tabIndex);
-    return (
-        <div className={classes()}>
-            {selectors && (
-                <div className="tab-selectors">
-                    {tabNames.map((tabName, tabIndex) => {
-                        const classes = [
-                            "tab-selector",
-                            tabIndex === tabIndexState ? "selected" : ""
-                        ].join(" ");
-                        return (
-                            <span
-                                className={classes}
-                                onClick={() => setTabIndexState(tabIndex)}
-                            >
-                                {tabName}
-                            </span>
-                        );
-                    })}
-                </div>
-            )}
-            <div className="tab-container">{tabs[tabIndexState]}</div>
-        </div>
-    );
-};
-
-export default PanelTab;
diff --git a/frontends/web/react/components/paragraph/paragraph.css b/frontends/web/react/components/paragraph/paragraph.css
deleted file mode 100644
index 4d6c5b87..00000000
--- a/frontends/web/react/components/paragraph/paragraph.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.paragraph {
-    font-size: 18px;
-    line-height: 24px;
-    margin: 12px 0px 12px 0px;
-}
diff --git a/frontends/web/react/components/paragraph/paragraph.tsx b/frontends/web/react/components/paragraph/paragraph.tsx
deleted file mode 100644
index 6d1efdb5..00000000
--- a/frontends/web/react/components/paragraph/paragraph.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-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/frontends/web/react/components/registers-gb/registers-gb.css b/frontends/web/react/components/registers-gb/registers-gb.css
deleted file mode 100644
index 0801939b..00000000
--- a/frontends/web/react/components/registers-gb/registers-gb.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.registers-gb > .section {
-    display: inline-block;
-    margin-right: 32px;
-    vertical-align: top;
-}
-
-.registers-gb > .section:last-child {
-    margin-right: 0px;
-}
-
-.registers-gb > .section > h4 {
-    font-size: 22px;
-    margin: 0px 0px 8px 0px;
-}
-
-.registers-gb > .section > .register {
-    font-size: 0px;
-    line-height: 22px;
-}
-
-.registers-gb > .section > .register > .register-key {
-    display: inline-block;
-    font-size: 20px;
-    width: 40px;
-}
-
-.registers-gb > .section > .register > .register-value {
-    display: inline-block;
-    font-size: 20px;
-    text-align: right;
-    width: 66px;
-}
diff --git a/frontends/web/react/components/registers-gb/registers-gb.tsx b/frontends/web/react/components/registers-gb/registers-gb.tsx
deleted file mode 100644
index 988ba20e..00000000
--- a/frontends/web/react/components/registers-gb/registers-gb.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, { FC, useEffect, useRef, useState } from "react";
-
-import "./registers-gb.css";
-
-type RegistersGBProps = {
-    getRegisters: () => Record<string, string | number>;
-    interval?: number;
-    style?: string[];
-};
-
-export const RegistersGB: FC<RegistersGBProps> = ({
-    getRegisters,
-    interval = 50,
-    style = []
-}) => {
-    const classes = () => ["registers-gb", ...style].join(" ");
-    const [registers, setRegisters] = useState<Record<string, string | number>>(
-        {}
-    );
-    const intervalsRef = useRef<number>();
-    useEffect(() => {
-        const updateRegisters = () => {
-            const registers = getRegisters();
-            setRegisters(registers);
-        };
-        setInterval(() => updateRegisters(), interval);
-        updateRegisters();
-        return () => {
-            if (intervalsRef.current) {
-                clearInterval(intervalsRef.current);
-            }
-        };
-    }, []);
-    const renderRegister = (
-        key: string,
-        value?: number,
-        size = 2,
-        styles: string[] = []
-    ) => {
-        const classes = ["register", ...styles].join(" ");
-        const valueS =
-            value?.toString(16).toUpperCase().padStart(size, "0") ?? value;
-        return (
-            <div className={classes}>
-                <span className="register-key">{key}</span>
-                <span className="register-value">
-                    {valueS ? `0x${valueS}` : "-"}
-                </span>
-            </div>
-        );
-    };
-    return (
-        <div className={classes()}>
-            <div className="section">
-                <h4>CPU</h4>
-                {renderRegister("PC", registers.pc as number, 4)}
-                {renderRegister("SP", registers.sp as number, 4)}
-                {renderRegister("A", registers.a as number)}
-                {renderRegister("B", registers.b as number)}
-                {renderRegister("C", registers.c as number)}
-                {renderRegister("D", registers.d as number)}
-                {renderRegister("E", registers.e as number)}
-                {renderRegister("H", registers.h as number)}
-                {renderRegister("L", registers.l as number)}
-            </div>
-            <div className="section">
-                <h4>PPU</h4>
-                {renderRegister("SCY", registers.scy as number)}
-                {renderRegister("SCX", registers.scx as number)}
-                {renderRegister("WY", registers.wy as number)}
-                {renderRegister("WX", registers.wx as number)}
-                {renderRegister("LY", registers.ly as number)}
-                {renderRegister("LYC", registers.lyc as number)}
-            </div>
-        </div>
-    );
-};
-
-export default RegistersGB;
diff --git a/frontends/web/react/components/section/section.css b/frontends/web/react/components/section/section.css
deleted file mode 100644
index afe6f3de..00000000
--- a/frontends/web/react/components/section/section.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.section {
-    display: none;
-}
-
-.section.visible {
-    display: block;
-}
-
-.section > .separator {
-    background: #ffffff;
-    height: 2px;
-    margin: 22px 0px 22px 0px;
-}
diff --git a/frontends/web/react/components/section/section.tsx b/frontends/web/react/components/section/section.tsx
deleted file mode 100644
index 4958a17b..00000000
--- a/frontends/web/react/components/section/section.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React, { FC, ReactNode } from "react";
-
-import "./section.css";
-
-type SectionProps = {
-    children: ReactNode;
-    visible?: boolean;
-    separator?: boolean;
-    separatorBottom?: boolean;
-    style?: string[];
-};
-
-export const Section: FC<SectionProps> = ({
-    children,
-    visible = true,
-    separator = true,
-    separatorBottom = false,
-    style = []
-}) => {
-    const classes = () =>
-        ["section", visible ? "visible" : "", ...style].join(" ");
-    return (
-        <div className={classes()}>
-            {separator && <div className="separator"></div>}
-            <div className="section-contents">{children}</div>
-            {separatorBottom && <div className="separator"></div>}
-        </div>
-    );
-};
-
-export default Section;
diff --git a/frontends/web/react/components/tiles/tiles.css b/frontends/web/react/components/tiles/tiles.css
deleted file mode 100644
index 030a9575..00000000
--- a/frontends/web/react/components/tiles/tiles.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.tiles > .canvas {
-    background-color: #1b1a17;
-    border: 2px solid #50cb93;
-    padding: 8px 8px 8px 8px;
-}
-
-.tiles > .canvas.content-box {
-    box-sizing: content-box;
-    -o-box-sizing: content-box;
-    -ms-box-sizing: content-box;
-    -moz-box-sizing: content-box;
-    -khtml-box-sizing: content-box;
-    -webkit-box-sizing: content-box;
-}
diff --git a/frontends/web/react/components/tiles/tiles.tsx b/frontends/web/react/components/tiles/tiles.tsx
deleted file mode 100644
index c1b6f7b5..00000000
--- a/frontends/web/react/components/tiles/tiles.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import React, { FC, useEffect, useRef } from "react";
-import { PixelFormat } from "../../structs";
-import Canvas, { CanvasStructure } from "../canvas/canvas";
-
-import "./tiles.css";
-
-type TilesProps = {
-    getTile: (index: number) => Uint8Array;
-    tileCount: number;
-    width?: number | string;
-    contentBox?: boolean;
-    interval?: number;
-    style?: string[];
-};
-
-export const Tiles: FC<TilesProps> = ({
-    getTile,
-    tileCount,
-    width,
-    contentBox = true,
-    interval = 1000,
-    style = []
-}) => {
-    const classes = () =>
-        ["tiles", contentBox ? "content-box" : "", ...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++) {
-                const pixels = getTile(index);
-                drawTile(index, pixels, structure);
-            }
-        };
-        drawTiles();
-        intervalsRef.current = setInterval(() => drawTiles(), interval);
-    };
-    return (
-        <div className={classes()}>
-            <Canvas
-                width={128}
-                height={192}
-                scale={2}
-                scaledWidth={width}
-                onCanvas={onCanvas}
-            />
-        </div>
-    );
-};
-
-/**
- * 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 pixels Buffer of pixels that contains the RGB data
- * that is going to be drawn.
- * @param structure The canvas context to which the tile is
- * growing to be drawn.
- * @param format The pixel format of the sprite.
- */
-const drawTile = (
-    index: number,
-    pixels: Uint8Array,
-    structure: CanvasStructure,
-    format: PixelFormat = PixelFormat.RGB
-) => {
-    const line = Math.floor(index / 16);
-    const column = index % 16;
-    let offset =
-        (line * structure.canvas.width * 8 + column * 8) * PixelFormat.RGBA;
-    let counter = 0;
-    for (let i = 0; i < pixels.length; i += format) {
-        const color =
-            (pixels[i] << 24) |
-            (pixels[i + 1] << 16) |
-            (pixels[i + 2] << 8) |
-            (format === PixelFormat.RGBA ? pixels[i + 3] : 0xff);
-        structure.canvasBuffer.setUint32(offset, color);
-
-        counter++;
-        if (counter === 8) {
-            counter = 0;
-            offset += (structure.canvas.width - 7) * PixelFormat.RGBA;
-        } else {
-            offset += PixelFormat.RGBA;
-        }
-    }
-    structure.canvasContext.putImageData(structure.canvasImage, 0, 0);
-};
-
-export default Tiles;
diff --git a/frontends/web/react/components/title/title.css b/frontends/web/react/components/title/title.css
deleted file mode 100644
index 7989a40d..00000000
--- a/frontends/web/react/components/title/title.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.title > .link,
-.title > .label {
-    margin-left: 14px;
-}
-
-.title > .icon {
-    margin-left: 14px;
-    vertical-align: middle;
-    width: 32px;
-}
diff --git a/frontends/web/react/components/title/title.tsx b/frontends/web/react/components/title/title.tsx
deleted file mode 100644
index ac312ce7..00000000
--- a/frontends/web/react/components/title/title.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-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 &&
-                (versionUrl ? (
-                    <Link href={versionUrl} target="_blank">
-                        {version}
-                    </Link>
-                ) : (
-                    <span className="label">{version}</span>
-                ))}
-            {iconSrc && <img className="icon" src={iconSrc} alt="icon" />}
-        </h1>
-    );
-};
-
-export default Title;
diff --git a/frontends/web/react/components/toast/toast.css b/frontends/web/react/components/toast/toast.css
deleted file mode 100644
index 8bf52332..00000000
--- a/frontends/web/react/components/toast/toast.css
+++ /dev/null
@@ -1,57 +0,0 @@
-.toast {
-    background-color: black;
-    height: 0px;
-    left: 0px;
-    padding: 0px 24px 0px 24px;
-    pointer-events: none;
-    position: fixed;
-    text-align: center;
-    top: 0px;
-    width: 100%;
-    z-index: 8;
-}
-
-.toast > .toast-text {
-    background-color: #2a9d8f;
-    border-radius: 4px 4px 4px 4px;
-    -o-border-radius: 4px 4px 4px 4px;
-    -ms-border-radius: 4px 4px 4px 4px;
-    -moz-border-radius: 4px 4px 4px 4px;
-    -khtml-border-radius: 4px 4px 4px 4px;
-    -webkit-border-radius: 4px 4px 4px 4px;
-    cursor: pointer;
-    display: inline-block;
-    font-size: 20px;
-    line-height: 22px;
-    opacity: 0.0;
-    -o-opacity: 0.0;
-    -ms-opacity: 0.0;
-    -moz-opacity: 0.0;
-    -khtml-opacity: 0.0;
-    -webkit-opacity: 0.0;
-    padding: 12px 18px 12px 18px;
-    position: relative;
-    top: -46px;
-    transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -o-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -ms-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -moz-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -khtml-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    -webkit-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1);
-    width: fit-content;
-}
-
-.toast.error > .toast-text {
-    background-color: #e63946;
-}
-
-.toast.visible > .toast-text {
-    opacity: 1.0;
-    -o-opacity: 1.0;
-    -ms-opacity: 1.0;
-    -moz-opacity: 1.0;
-    -khtml-opacity: 1.0;
-    -webkit-opacity: 1.0;
-    pointer-events: all;
-    top: 24px;
-}
diff --git a/frontends/web/react/components/toast/toast.tsx b/frontends/web/react/components/toast/toast.tsx
deleted file mode 100644
index 3c1f3831..00000000
--- a/frontends/web/react/components/toast/toast.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, { FC } from "react";
-
-import "./toast.css";
-
-type ToastProps = {
-    text?: string;
-    error?: boolean;
-    visible?: boolean;
-    style?: string[];
-    onCancel?: () => void;
-};
-
-export const Toast: FC<ToastProps> = ({
-    text = "",
-    error = false,
-    visible = false,
-    style = [],
-    onCancel
-}) => {
-    const classes = () =>
-        [
-            "toast",
-            error ? "error" : "",
-            visible ? "visible" : "",
-            ...style
-        ].join(" ");
-    return (
-        <div className={classes()}>
-            <div className="toast-text" onClick={onCancel}>
-                {text}
-            </div>
-        </div>
-    );
-};
-
-export default Toast;
diff --git a/frontends/web/react/structs.ts b/frontends/web/react/structs.ts
deleted file mode 100644
index d2719b88..00000000
--- a/frontends/web/react/structs.ts
+++ /dev/null
@@ -1,275 +0,0 @@
-export const FREQUENCY_DELTA = 100000;
-
-export type Callback<T> = (owner: T, params?: any) => void;
-
-export type RomInfo = {
-    name?: string;
-    data?: Uint8Array;
-    size?: number;
-    extra?: Record<string, string | undefined>;
-};
-
-export type BenchmarkResult = {
-    delta: number;
-    count: number;
-    cycles: number;
-    frequency_mhz: number;
-};
-
-export type Entry = {
-    text: string;
-    url?: string;
-};
-
-/**
- * Enumeration to be used to describe the set of
- * features that a certain emulator supports, this
- * is going to condition its runtime execution.
- */
-export enum Feature {
-    Debug = 1,
-    Palettes,
-    Benchmark,
-    Keyboard,
-    KeyboardChip8,
-    KeyboardGB
-}
-
-/**
- * Enumeration that describes the multiple pixel
- * formats and the associated size in bytes.
- */
-export enum PixelFormat {
-    RGB = 3,
-    RGBA = 4
-}
-
-export interface ObservableI {
-    bind(event: string, callback: Callback<this>): void;
-    unbind(event: string, callback: Callback<this>): void;
-    trigger(event: string): void;
-}
-
-/**
- * Top level interface that declares the main abstract
- * interface of an emulator structured entity.
- * Should allow typical hardware operations to be performed.
- */
-export interface Emulator extends ObservableI {
-    /**
-     * The descriptive name of the emulator.
-     */
-    get name(): string;
-
-    /**
-     * The information on the hardware that is being emulated
-     * by the emulator (eg: Super Nintendo), can contain a URL
-     * that describes the device that is being emulated by
-     * the emulator (eg: Wikipedia link).
-     */
-    get device(): Entry;
-
-    /**
-     * A semantic version string for the current version
-     * of the emulator, can include a URL pointing to a
-     * changelog or equivalent document.
-     *
-     * @see {@link https://semver.org}
-     */
-    get version(): Entry | undefined;
-
-    /**
-     * Information about the source code repository where
-     * the emulator source code is being stored.
-     */
-    get repository(): Entry | undefined;
-
-    /**
-     * The features available and compatible with the emulator,
-     * these values will influence the associated GUIs.
-     */
-    get features(): Feature[];
-
-    /**
-     * 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;
-
-    /**
-     * The complete set of file extensions that this emulator
-     * supports.
-     */
-    get romExts(): string[];
-
-    /**
-     * The pixel format of the emulator's display
-     * image buffer (eg: RGB).
-     */
-    get pixelFormat(): PixelFormat;
-
-    /**
-     * 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.
-     */
-    get imageBuffer(): Uint8Array;
-
-    /**
-     * 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.
-     */
-    get romInfo(): RomInfo;
-
-    /**
-     * The current CPU frequency (logic) of the emulator,
-     * should impact other elements of the emulator.
-     */
-    get frequency(): number;
-    set frequency(value: number);
-
-    /**
-     * The recommended frequency delta in hertz for scale up
-     * and scale down operations in the CPU frequency.
-     */
-    get frequencyDelta(): number | null;
-
-    /**
-     * The current logic framerate of the running emulator.
-     */
-    get framerate(): number;
-
-    /**
-     * A dictionary that contains the register names associated
-     * with their value either as strings or numbers.
-     */
-    get registers(): Record<string, string | number>;
-
-    /**
-     * The palette as a string name that is currently
-     * set in the emulator for display.
-     */
-    get palette(): string | undefined;
-    set palette(value: string | undefined);
-
-    /**
-     * Obtains the pixel buffer for the VRAM tile at the given
-     * index.
-     *
-     * @param index The index of the tile to obtain pixel buffer.
-     * @returns The pixel buffer of the tile at the given index.
-     */
-    getTile(index: number): Uint8Array;
-
-    /**
-     * Boot (or reboots) the emulator according to the provided
-     * set of options.
-     *
-     * @param options The options that are going to be used for
-     * the booting operation of the emulator.
-     */
-    boot(options: any): void;
-
-    /**
-     * Toggle the running state of the emulator between paused
-     * and running, prevents consumers from the need to access
-     * the current running state of the emulator to implement
-     * a logic toggle.
-     */
-    toggleRunning(): void;
-    pause(): void;
-    resume(): void;
-
-    /**
-     * Resets the emulator machine to the start state and
-     * re-loads the ROM that is currently set in the emulator.
-     */
-    reset(): void;
-
-    keyPress(key: string): void;
-
-    keyLift(key: string): void;
-
-    /**
-     * Changes the palette of the emulator to the "next" one,
-     * the order in which the palette is chosen is defined by
-     * the concrete emulator implementation.
-     *
-     * @returns The name of the palette that has been selected.
-     */
-    changePalette?: { (): string };
-
-    /**
-     * Runs a benchmark operation in the emulator, effectively
-     * measuring the performance of it.
-     *
-     * @param count The number of benchmark iterations to be
-     * run, increasing this value will make the benchmark take
-     * more time to be executed.
-     * @returns The result metrics from the benchmark run.
-     */
-    benchmark?: { (count?: number): BenchmarkResult };
-}
-
-/**
- * Abstract class that implements the basic functionality
- * part of the definition of the Observer pattern.
- *
- * @see {@link https://en.wikipedia.org/wiki/Observer_pattern}
- */
-export class Observable {
-    private events: Record<string, [Callback<this>]> = {};
-
-    bind(event: string, callback: Callback<this>) {
-        const callbacks = this.events[event] ?? [];
-        if (callbacks.includes(callback)) return;
-        callbacks.push(callback);
-        this.events[event] = callbacks;
-    }
-
-    unbind(event: string, callback: Callback<this>) {
-        const callbacks = this.events[event] ?? [];
-        if (!callbacks.includes(callback)) return;
-        const index = callbacks.indexOf(callback);
-        callbacks.splice(index, 1);
-        this.events[event] = callbacks;
-    }
-
-    trigger(event: string, params?: any) {
-        const callbacks = this.events[event] ?? [];
-        callbacks.forEach((c) => c(this, params));
-    }
-}
-
-export class EmulatorBase extends Observable {
-    get version(): Entry | undefined {
-        return undefined;
-    }
-
-    get repository(): Entry | undefined {
-        return undefined;
-    }
-
-    get features(): Feature[] {
-        return [];
-    }
-
-    get frequencyDelta(): number | null {
-        return FREQUENCY_DELTA;
-    }
-
-    get palette(): string | undefined {
-        return undefined;
-    }
-
-    set palette(value: string | undefined) {}
-}
diff --git a/frontends/web/react/util.ts b/frontends/web/react/util.ts
deleted file mode 100644
index ba258354..00000000
--- a/frontends/web/react/util.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const isAndroid = () => {
-    return navigator.userAgent.match(/Android/i);
-};
diff --git a/frontends/web/gb.ts b/frontends/web/ts/gb.ts
similarity index 96%
rename from frontends/web/gb.ts
rename to frontends/web/ts/gb.ts
index d3103b8a..5ddc731f 100644
--- a/frontends/web/gb.ts
+++ b/frontends/web/ts/gb.ts
@@ -5,7 +5,7 @@ import {
     Feature,
     PixelFormat,
     RomInfo
-} from "./react/structs";
+} from "emukit";
 import { PALETTES, PALETTES_MAP } from "./palettes";
 
 import {
@@ -14,8 +14,8 @@ import {
     GameBoy,
     PadKey,
     PpuMode
-} from "./lib/boytacean.js";
-import info from "./package.json";
+} from "../lib/boytacean.js";
+import info from "../package.json";
 import { base64ToBuffer, bufferToBase64 } from "./util";
 
 declare const require: any;
@@ -45,7 +45,7 @@ const KEYS_NAME: Record<string, number> = {
     B: PadKey.B
 };
 
-const ROM_PATH = require("../../res/roms/pocket.gb");
+const ROM_PATH = require("../../../res/roms/pocket.gb");
 
 /**
  * Top level class that controls the emulator behaviour
diff --git a/frontends/web/ts/index.ts b/frontends/web/ts/index.ts
new file mode 100644
index 00000000..f9401867
--- /dev/null
+++ b/frontends/web/ts/index.ts
@@ -0,0 +1,3 @@
+export * from "./gb";
+export * from "./palettes";
+export * from "./util";
diff --git a/frontends/web/palettes.ts b/frontends/web/ts/palettes.ts
similarity index 100%
rename from frontends/web/palettes.ts
rename to frontends/web/ts/palettes.ts
diff --git a/frontends/web/util.ts b/frontends/web/ts/util.ts
similarity index 100%
rename from frontends/web/util.ts
rename to frontends/web/ts/util.ts
-- 
GitLab