diff --git a/README.md b/README.md
index ed27bc0eff944d5601db2425fd4b3dbe31597de1..3c2257f43b22138c0e5423fcaaa45fabad53791e 100644
--- a/README.md
+++ b/README.md
@@ -25,13 +25,20 @@ The work of this emulator was inspired/started by [jc-chip8](https://github.com/
 
 ## Build
 
-### WASM
+### WASM for Node.js
 
 ```bash
 cargo install wasm-pack
 wasm-pack build --release --target=nodejs -- --features wasm
 ```
 
+### WASM for Web
+
+```bash
+cargo install wasm-pack
+wasm-pack build --release --target=web -- --features wasm
+```
+
 ## Reason
 
 And... yes this is the real inspiration behind the emulator's name:
diff --git a/examples/web/chip_ahoyto.d.ts b/examples/web/chip_ahoyto.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..967df317c8fd3731d6413322a1c09cb5f23bac35
--- /dev/null
+++ b/examples/web/chip_ahoyto.d.ts
@@ -0,0 +1,57 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+*/
+export class Chip8Classic {
+  free(): void;
+/**
+*/
+  constructor();
+}
+/**
+*/
+export class Chip8Neo {
+  free(): void;
+/**
+*/
+  constructor();
+/**
+* @param {Uint8Array} rom
+*/
+  load_rom_ws(rom: Uint8Array): void;
+/**
+*/
+  reset_ws(): void;
+/**
+*/
+  reset_hard_ws(): void;
+/**
+*/
+  clock_ws(): void;
+}
+
+export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
+
+export interface InitOutput {
+  readonly memory: WebAssembly.Memory;
+  readonly __wbg_chip8neo_free: (a: number) => void;
+  readonly chip8neo_new: () => number;
+  readonly chip8neo_load_rom_ws: (a: number, b: number, c: number) => void;
+  readonly chip8neo_reset_ws: (a: number) => void;
+  readonly chip8neo_reset_hard_ws: (a: number) => void;
+  readonly chip8neo_clock_ws: (a: number) => void;
+  readonly __wbg_chip8classic_free: (a: number) => void;
+  readonly chip8classic_new: () => number;
+  readonly __wbindgen_malloc: (a: number) => number;
+  readonly __wbindgen_exn_store: (a: number) => void;
+}
+
+/**
+* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
+* for everything else, calls `WebAssembly.instantiate` directly.
+*
+* @param {InitInput | Promise<InitInput>} module_or_path
+*
+* @returns {Promise<InitOutput>}
+*/
+export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
diff --git a/examples/web/chip_ahoyto.js b/examples/web/chip_ahoyto.js
new file mode 100644
index 0000000000000000000000000000000000000000..792feab78e86a274080bc876ada195cce5e91bff
--- /dev/null
+++ b/examples/web/chip_ahoyto.js
@@ -0,0 +1,312 @@
+
+let wasm;
+
+const heap = new Array(32).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+    if (idx < 36) return;
+    heap[idx] = heap_next;
+    heap_next = idx;
+}
+
+function takeObject(idx) {
+    const ret = getObject(idx);
+    dropObject(idx);
+    return ret;
+}
+
+function addHeapObject(obj) {
+    if (heap_next === heap.length) heap.push(heap.length + 1);
+    const idx = heap_next;
+    heap_next = heap[idx];
+
+    heap[idx] = obj;
+    return idx;
+}
+
+const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+let cachegetUint8Memory0 = null;
+function getUint8Memory0() {
+    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachegetUint8Memory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+let WASM_VECTOR_LEN = 0;
+
+function passArray8ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 1);
+    getUint8Memory0().set(arg, ptr / 1);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+function handleError(f, args) {
+    try {
+        return f.apply(this, args);
+    } catch (e) {
+        wasm.__wbindgen_exn_store(addHeapObject(e));
+    }
+}
+
+function getArrayU8FromWasm0(ptr, len) {
+    return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
+}
+/**
+*/
+export class Chip8Classic {
+
+    static __wrap(ptr) {
+        const obj = Object.create(Chip8Classic.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    __destroy_into_raw() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        return ptr;
+    }
+
+    free() {
+        const ptr = this.__destroy_into_raw();
+        wasm.__wbg_chip8classic_free(ptr);
+    }
+    /**
+    */
+    constructor() {
+        const ret = wasm.chip8classic_new();
+        return Chip8Classic.__wrap(ret);
+    }
+}
+/**
+*/
+export class Chip8Neo {
+
+    static __wrap(ptr) {
+        const obj = Object.create(Chip8Neo.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    __destroy_into_raw() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        return ptr;
+    }
+
+    free() {
+        const ptr = this.__destroy_into_raw();
+        wasm.__wbg_chip8neo_free(ptr);
+    }
+    /**
+    */
+    constructor() {
+        const ret = wasm.chip8neo_new();
+        return Chip8Neo.__wrap(ret);
+    }
+    /**
+    * @param {Uint8Array} rom
+    */
+    load_rom_ws(rom) {
+        const ptr0 = passArray8ToWasm0(rom, wasm.__wbindgen_malloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.chip8neo_load_rom_ws(this.ptr, ptr0, len0);
+    }
+    /**
+    */
+    reset_ws() {
+        wasm.chip8neo_reset_ws(this.ptr);
+    }
+    /**
+    */
+    reset_hard_ws() {
+        wasm.chip8neo_reset_hard_ws(this.ptr);
+    }
+    /**
+    */
+    clock_ws() {
+        wasm.chip8neo_clock_ws(this.ptr);
+    }
+}
+
+async function load(module, imports) {
+    if (typeof Response === 'function' && module instanceof Response) {
+        if (typeof WebAssembly.instantiateStreaming === 'function') {
+            try {
+                return await WebAssembly.instantiateStreaming(module, imports);
+
+            } catch (e) {
+                if (module.headers.get('Content-Type') != 'application/wasm') {
+                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        const bytes = await module.arrayBuffer();
+        return await WebAssembly.instantiate(bytes, imports);
+
+    } else {
+        const instance = await WebAssembly.instantiate(module, imports);
+
+        if (instance instanceof WebAssembly.Instance) {
+            return { instance, module };
+
+        } else {
+            return instance;
+        }
+    }
+}
+
+async function init(input) {
+    if (typeof input === 'undefined') {
+        input = new URL('chip_ahoyto_bg.wasm', import.meta.url);
+    }
+    const imports = {};
+    imports.wbg = {};
+    imports.wbg.__wbg_process_e56fd54cf6319b6c = function(arg0) {
+        const ret = getObject(arg0).process;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_is_object = function(arg0) {
+        const val = getObject(arg0);
+        const ret = typeof(val) === 'object' && val !== null;
+        return ret;
+    };
+    imports.wbg.__wbg_versions_77e21455908dad33 = function(arg0) {
+        const ret = getObject(arg0).versions;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+        takeObject(arg0);
+    };
+    imports.wbg.__wbg_node_0dd25d832e4785d5 = function(arg0) {
+        const ret = getObject(arg0).node;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_is_string = function(arg0) {
+        const ret = typeof(getObject(arg0)) === 'string';
+        return ret;
+    };
+    imports.wbg.__wbg_static_accessor_NODE_MODULE_26b231378c1be7dd = function() {
+        const ret = module;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_require_0db1598d9ccecb30 = function() { return handleError(function (arg0, arg1, arg2) {
+        const ret = getObject(arg0).require(getStringFromWasm0(arg1, arg2));
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_crypto_b95d7173266618a9 = function(arg0) {
+        const ret = getObject(arg0).crypto;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_msCrypto_5a86d77a66230f81 = function(arg0) {
+        const ret = getObject(arg0).msCrypto;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_getRandomValues_b14734aa289bc356 = function() { return handleError(function (arg0, arg1) {
+        getObject(arg0).getRandomValues(getObject(arg1));
+    }, arguments) };
+    imports.wbg.__wbg_randomFillSync_91e2b39becca6147 = function() { return handleError(function (arg0, arg1, arg2) {
+        getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2));
+    }, arguments) };
+    imports.wbg.__wbg_newnoargs_e23b458e372830de = function(arg0, arg1) {
+        const ret = new Function(getStringFromWasm0(arg0, arg1));
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_call_ae78342adc33730a = function() { return handleError(function (arg0, arg1) {
+        const ret = getObject(arg0).call(getObject(arg1));
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
+        const ret = getObject(arg0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_self_99737b4dcdf6f0d8 = function() { return handleError(function () {
+        const ret = self.self;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_window_9b61fbbf3564c4fb = function() { return handleError(function () {
+        const ret = window.window;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_globalThis_8e275ef40caea3a3 = function() { return handleError(function () {
+        const ret = globalThis.globalThis;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_global_5de1e0f82bddcd27 = function() { return handleError(function () {
+        const ret = global.global;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbindgen_is_undefined = function(arg0) {
+        const ret = getObject(arg0) === undefined;
+        return ret;
+    };
+    imports.wbg.__wbg_buffer_7af23f65f6c64548 = function(arg0) {
+        const ret = getObject(arg0).buffer;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_new_cc9018bd6f283b6f = function(arg0) {
+        const ret = new Uint8Array(getObject(arg0));
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_set_f25e869e4565d2a2 = function(arg0, arg1, arg2) {
+        getObject(arg0).set(getObject(arg1), arg2 >>> 0);
+    };
+    imports.wbg.__wbg_length_0acb1cf9bbaf8519 = function(arg0) {
+        const ret = getObject(arg0).length;
+        return ret;
+    };
+    imports.wbg.__wbg_newwithlength_8f0657faca9f1422 = function(arg0) {
+        const ret = new Uint8Array(arg0 >>> 0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_subarray_da527dbd24eafb6b = function(arg0, arg1, arg2) {
+        const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+        throw new Error(getStringFromWasm0(arg0, arg1));
+    };
+    imports.wbg.__wbindgen_memory = function() {
+        const ret = wasm.memory;
+        return addHeapObject(ret);
+    };
+
+    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+        input = fetch(input);
+    }
+
+
+
+    const { instance, module } = await load(await input, imports);
+
+    wasm = instance.exports;
+    init.__wbindgen_wasm_module = module;
+
+    return wasm;
+}
+
+export default init;
+
diff --git a/examples/web/chip_ahoyto_bg.wasm b/examples/web/chip_ahoyto_bg.wasm
new file mode 100644
index 0000000000000000000000000000000000000000..e5178df226f9cd406c380e432a15724c30a377ba
Binary files /dev/null and b/examples/web/chip_ahoyto_bg.wasm differ
diff --git a/examples/web/chip_ahoyto_bg.wasm.d.ts b/examples/web/chip_ahoyto_bg.wasm.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f8c42c5ff436515adc414943c66d06a032d7112
--- /dev/null
+++ b/examples/web/chip_ahoyto_bg.wasm.d.ts
@@ -0,0 +1,13 @@
+/* tslint:disable */
+/* eslint-disable */
+export const memory: WebAssembly.Memory;
+export function __wbg_chip8neo_free(a: number): void;
+export function chip8neo_new(): number;
+export function chip8neo_load_rom_ws(a: number, b: number, c: number): void;
+export function chip8neo_reset_ws(a: number): void;
+export function chip8neo_reset_hard_ws(a: number): void;
+export function chip8neo_clock_ws(a: number): void;
+export function __wbg_chip8classic_free(a: number): void;
+export function chip8classic_new(): number;
+export function __wbindgen_malloc(a: number): number;
+export function __wbindgen_exn_store(a: number): void;
diff --git a/examples/web/index.html b/examples/web/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..b50d7ca094ab34a9da9f5c4a71fb09587700089e
--- /dev/null
+++ b/examples/web/index.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CHIP-Ahoyto</title>
+    </head>
+    <body>
+        <script type="module" src="./index.js"></script>
+    </body>
+</html>
diff --git a/examples/web/index.js b/examples/web/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f35c2539cf474dc994ae36e950f48e326c69d66a
--- /dev/null
+++ b/examples/web/index.js
@@ -0,0 +1,14 @@
+import { default as wasm, Chip8Neo } from "./chip_ahoyto.js";
+
+(async () => {
+    // initializes the WASM module, this is required
+    // so that the global symbols become available
+    await wasm();
+
+    console.info("LOADED");
+
+    const chip8 = new Chip8Neo();
+    chip8.clock_ws();
+
+    console.info("CLOCK");
+})();
diff --git a/src/chip8_neo.rs b/src/chip8_neo.rs
index fcf1a761393603e08f8d37fb2cd85905f03307c6..730a15ae5418c2ca4ddef11e3a09dd10ce59b383 100644
--- a/src/chip8_neo.rs
+++ b/src/chip8_neo.rs
@@ -75,6 +75,89 @@ impl Chip8 for Chip8Neo {
         self.reset();
     }
 
+    fn beep(&self) -> bool {
+        self.st > 0
+    }
+
+    fn pc(&self) -> u16 {
+        self.pc
+    }
+
+    fn sp(&self) -> u8 {
+        self.sp
+    }
+
+    fn ram(&self) -> Vec<u8> {
+        self.ram.to_vec()
+    }
+
+    fn vram(&self) -> Vec<u8> {
+        self.vram.to_vec()
+    }
+
+    fn get_state(&self) -> Vec<u8> {
+        let mut buffer: Vec<u8> = Vec::new();
+        buffer.extend(self.ram.iter());
+        buffer.extend(self.vram.iter());
+        buffer.extend(self.stack.map(|v| v.to_le_bytes()).iter().flatten());
+        buffer.extend(self.regs.iter());
+        buffer.extend(self.pc.to_le_bytes().iter());
+        buffer.extend(self.i.to_le_bytes().iter());
+        buffer.extend(self.sp.to_le_bytes().iter());
+        buffer.extend(self.dt.to_le_bytes().iter());
+        buffer.extend(self.st.to_le_bytes().iter());
+        buffer.extend(self.keys.map(|v| v as u8).iter());
+        buffer.extend(self.last_key.to_le_bytes().iter());
+        buffer
+    }
+
+    fn set_state(&mut self, state: &[u8]) {
+        let mut u8_buffer = [0u8; 1];
+        let mut u16_buffer = [0u8; 2];
+        let mut regs_buffer = [0u8; REGISTERS_SIZE * 2];
+        let mut keys_buffer = [0u8; KEYS_SIZE];
+
+        let mut cursor = Cursor::new(state.to_vec());
+
+        cursor.read_exact(&mut self.ram).unwrap();
+        cursor.read_exact(&mut self.vram).unwrap();
+        cursor.read_exact(&mut regs_buffer).unwrap();
+        self.stack.clone_from_slice(
+            regs_buffer
+                .chunks(2)
+                .map(|v| {
+                    u16_buffer.clone_from_slice(&v[0..2]);
+                    u16::from_le_bytes(u16_buffer)
+                })
+                .collect::<Vec<u16>>()
+                .as_slice(),
+        );
+        cursor.read_exact(&mut self.regs).unwrap();
+        cursor.read_exact(&mut u16_buffer).unwrap();
+        self.pc = u16::from_le_bytes(u16_buffer);
+        cursor.read_exact(&mut u16_buffer).unwrap();
+        self.i = u16::from_le_bytes(u16_buffer);
+        cursor.read_exact(&mut u8_buffer).unwrap();
+        self.sp = u8::from_le_bytes(u8_buffer);
+        cursor.read_exact(&mut u8_buffer).unwrap();
+        self.dt = u8::from_le_bytes(u8_buffer);
+        cursor.read_exact(&mut u8_buffer).unwrap();
+        self.st = u8::from_le_bytes(u8_buffer);
+        cursor.read_exact(&mut keys_buffer).unwrap();
+        self.keys.clone_from_slice(
+            keys_buffer
+                .map(|v| if v == 1 { true } else { false })
+                .iter()
+                .as_slice(),
+        );
+        cursor.read_exact(&mut u8_buffer).unwrap();
+        self.last_key = u8::from_le_bytes(u8_buffer);
+    }
+
+    fn load_rom(&mut self, rom: &[u8]) {
+        self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom);
+    }
+
     fn clock(&mut self) {
         // fetches the current instruction and increments
         // the PC (program counter) accordingly
@@ -224,89 +307,6 @@ impl Chip8 for Chip8Neo {
         }
         self.keys[key as usize] = false;
     }
-
-    fn load_rom(&mut self, rom: &[u8]) {
-        self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom);
-    }
-
-    fn beep(&self) -> bool {
-        self.st > 0
-    }
-
-    fn pc(&self) -> u16 {
-        self.pc
-    }
-
-    fn sp(&self) -> u8 {
-        self.sp
-    }
-
-    fn ram(&self) -> Vec<u8> {
-        self.ram.to_vec()
-    }
-
-    fn vram(&self) -> Vec<u8> {
-        self.vram.to_vec()
-    }
-
-    fn get_state(&self) -> Vec<u8> {
-        let mut buffer: Vec<u8> = Vec::new();
-        buffer.extend(self.ram.iter());
-        buffer.extend(self.vram.iter());
-        buffer.extend(self.stack.map(|v| v.to_le_bytes()).iter().flatten());
-        buffer.extend(self.regs.iter());
-        buffer.extend(self.pc.to_le_bytes().iter());
-        buffer.extend(self.i.to_le_bytes().iter());
-        buffer.extend(self.sp.to_le_bytes().iter());
-        buffer.extend(self.dt.to_le_bytes().iter());
-        buffer.extend(self.st.to_le_bytes().iter());
-        buffer.extend(self.keys.map(|v| v as u8).iter());
-        buffer.extend(self.last_key.to_le_bytes().iter());
-        buffer
-    }
-
-    fn set_state(&mut self, state: &[u8]) {
-        let mut u8_buffer = [0u8; 1];
-        let mut u16_buffer = [0u8; 2];
-        let mut regs_buffer = [0u8; REGISTERS_SIZE * 2];
-        let mut keys_buffer = [0u8; KEYS_SIZE];
-
-        let mut cursor = Cursor::new(state.to_vec());
-
-        cursor.read_exact(&mut self.ram).unwrap();
-        cursor.read_exact(&mut self.vram).unwrap();
-        cursor.read_exact(&mut regs_buffer).unwrap();
-        self.stack.clone_from_slice(
-            regs_buffer
-                .chunks(2)
-                .map(|v| {
-                    u16_buffer.clone_from_slice(&v[0..2]);
-                    u16::from_le_bytes(u16_buffer)
-                })
-                .collect::<Vec<u16>>()
-                .as_slice(),
-        );
-        cursor.read_exact(&mut self.regs).unwrap();
-        cursor.read_exact(&mut u16_buffer).unwrap();
-        self.pc = u16::from_le_bytes(u16_buffer);
-        cursor.read_exact(&mut u16_buffer).unwrap();
-        self.i = u16::from_le_bytes(u16_buffer);
-        cursor.read_exact(&mut u8_buffer).unwrap();
-        self.sp = u8::from_le_bytes(u8_buffer);
-        cursor.read_exact(&mut u8_buffer).unwrap();
-        self.dt = u8::from_le_bytes(u8_buffer);
-        cursor.read_exact(&mut u8_buffer).unwrap();
-        self.st = u8::from_le_bytes(u8_buffer);
-        cursor.read_exact(&mut keys_buffer).unwrap();
-        self.keys.clone_from_slice(
-            keys_buffer
-                .map(|v| if v == 1 { true } else { false })
-                .iter()
-                .as_slice(),
-        );
-        cursor.read_exact(&mut u8_buffer).unwrap();
-        self.last_key = u8::from_le_bytes(u8_buffer);
-    }
 }
 
 #[cfg_attr(feature = "wasm", wasm_bindgen)]
@@ -364,6 +364,25 @@ impl Chip8Neo {
     }
 }
 
+#[cfg_attr(feature = "wasm", wasm_bindgen)]
+impl Chip8Neo {
+    pub fn load_rom_ws(&mut self, rom: &[u8]) {
+        self.load_rom(rom)
+    }
+
+    pub fn reset_ws(&mut self) {
+        self.reset()
+    }
+
+    pub fn reset_hard_ws(&mut self) {
+        self.reset_hard()
+    }
+
+    pub fn clock_ws(&mut self) {
+        self.clock()
+    }
+}
+
 impl Default for Chip8Neo {
     fn default() -> Chip8Neo {
         Chip8Neo::new()