diff --git a/Nibriboard/Client/Nibri.css b/Nibriboard/Client/Nibri.css
deleted file mode 100644
index e69de29..0000000
diff --git a/Nibriboard/Client/NibriClient.js b/Nibriboard/Client/NibriClient.js
deleted file mode 100644
index 30feb57..0000000
--- a/Nibriboard/Client/NibriClient.js
+++ /dev/null
@@ -1,5 +0,0 @@
-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-'use strict';
-
-
-},{}]},{},[1]);
diff --git a/Nibriboard/Client/index.html b/Nibriboard/Client/index.html
deleted file mode 100644
index e69de29..0000000
diff --git a/Nibriboard/Client/index.js b/Nibriboard/Client/index.js
deleted file mode 100644
index 23e72fd..0000000
--- a/Nibriboard/Client/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-"use strict";
-
diff --git a/Nibriboard/ClientFiles/BoardWindow.js b/Nibriboard/ClientFiles/BoardWindow.js
new file mode 100644
index 0000000..380fe8b
--- /dev/null
+++ b/Nibriboard/ClientFiles/BoardWindow.js
@@ -0,0 +1,73 @@
+"use strict";
+
+// npm modules
+window.EventEmitter = require("event-emitter-es6");
+window.FaviconNotification = require("favicon-notification");
+
+// Our files
+import RippleLink from './RippleLink';
+import { get } from './Utilities';
+
+class BoardWindow extends EventEmitter
+{
+	constructor(canvas)
+	{
+		super(); // Run the parent constructor
+		
+		this.canvas = canvas;
+		this.context = canvas.getContext("2d");
+		FaviconNotification.init({
+			color: '#ff6333'
+		});
+		FaviconNotification.add();
+		
+		get("/Settings.json").then(JSON.parse).then((function(settings) {
+			this.settings = settings;
+			this.setup();
+		}).bind(this), function(errorMessage) {
+			console.error(`Error: Failed to fetch settings from server! Response: ${errorMessage}`);
+		});
+		
+		this.trackWindowSize();
+	}
+	
+	nextFrame()
+	{
+		this.update();
+		this.render(this.canvas, this.context);
+		requestAnimationFrame(this.nextFrame.bind(this));
+	}
+	
+	update()
+	{
+		
+	}
+	
+	render(canvas, context)
+	{
+		context.clearRect(0, 0, canvas.width, canvas.height);
+		
+		context.fillStyle = "red";
+		context.fillRect(10, 10, 100, 100);
+	}
+	
+	/**
+	 * Updates the canvas size to match the current viewport size.
+	 */
+	matchWindowSize() {
+		this.canvas.width = window.innerWidth;
+		this.canvas.height = window.innerHeight;
+		
+		this.render(this.canvas, this.context);
+	}
+	
+	/**
+	 * Makes the canvas size track the window size.
+	 */
+	trackWindowSize() {
+		this.matchWindowSize();
+		window.addEventListener("resize", this.matchWindowSize.bind(this));
+	}
+}
+
+export default BoardWindow;
diff --git a/Nibriboard/ClientFiles/Nibri.css b/Nibriboard/ClientFiles/Nibri.css
new file mode 100644
index 0000000..41c372c
--- /dev/null
+++ b/Nibriboard/ClientFiles/Nibri.css
@@ -0,0 +1,11 @@
+html, body { font-size: 100%; }
+body
+{
+    font-family: sans-serif;
+}
+
+#canvas-main
+{
+    position: absolute;
+    top: 0; right: 0; bottom: 0; left: 0;
+}
diff --git a/Nibriboard/ClientFiles/NibriClient.js b/Nibriboard/ClientFiles/NibriClient.js
new file mode 100644
index 0000000..9400db2
--- /dev/null
+++ b/Nibriboard/ClientFiles/NibriClient.js
@@ -0,0 +1,471 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+'use strict';
+
+function get(u){return new Promise(function(r,t,a){a=new XMLHttpRequest();a.onload=function(b,c){b=a.status;c=a.response;if(b>199&&b<300){r(c);}else{t(c);}};a.open("GET",u,true);a.send(null);})}
+
+// npm modules
+window.EventEmitter = require("event-emitter-es6");
+window.FaviconNotification = require("favicon-notification");
+
+// Our files
+class BoardWindow extends EventEmitter
+{
+	constructor(canvas)
+	{
+		super(); // Run the parent constructor
+		
+		this.canvas = canvas;
+		this.context = canvas.getContext("2d");
+		FaviconNotification.init({
+			color: '#ff6333'
+		});
+		FaviconNotification.add();
+		
+		get("/Settings.json").then(JSON.parse).then((function(settings) {
+			this.settings = settings;
+			this.setup();
+		}).bind(this), function(errorMessage) {
+			console.error(`Error: Failed to fetch settings from server! Response: ${errorMessage}`);
+		});
+		
+		this.trackWindowSize();
+	}
+	
+	nextFrame()
+	{
+		this.update();
+		this.render(this.canvas, this.context);
+		requestAnimationFrame(this.nextFrame.bind(this));
+	}
+	
+	update()
+	{
+		
+	}
+	
+	render(canvas, context)
+	{
+		context.clearRect(0, 0, canvas.width, canvas.height);
+		
+		context.fillStyle = "red";
+		context.fillRect(10, 10, 100, 100);
+	}
+	
+	/**
+	 * Updates the canvas size to match the current viewport size.
+	 */
+	matchWindowSize() {
+		this.canvas.width = window.innerWidth;
+		this.canvas.height = window.innerHeight;
+		
+		this.render(this.canvas, this.context);
+	}
+	
+	/**
+	 * Makes the canvas size track the window size.
+	 */
+	trackWindowSize() {
+		this.matchWindowSize();
+		window.addEventListener("resize", this.matchWindowSize.bind(this));
+	}
+}
+
+window.addEventListener("load", function (event) {
+	var canvas = document.getElementById("canvas-main"),
+		boardWindow = new BoardWindow(canvas);
+	boardWindow.nextFrame();
+	window.boardWindow = boardWindow;
+});
+
+},{"event-emitter-es6":2,"favicon-notification":3}],2:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var DEFAULT_VALUES = {
+    emitDelay: 10,
+    strictMode: false
+};
+
+/**
+ * @typedef {object} EventEmitterListenerFunc
+ * @property {boolean} once
+ * @property {function} fn
+ */
+
+/**
+ * @class EventEmitter
+ *
+ * @private
+ * @property {Object.<string, EventEmitterListenerFunc[]>} _listeners
+ * @property {string[]} events
+ */
+
+var EventEmitter = function () {
+
+    /**
+     * @constructor
+     * @param {{}}      [opts]
+     * @param {number}  [opts.emitDelay = 10] - Number in ms. Specifies whether emit will be sync or async. By default - 10ms. If 0 - fires sync
+     * @param {boolean} [opts.strictMode = false] - is true, Emitter throws error on emit error with no listeners
+     */
+
+    function EventEmitter() {
+        var opts = arguments.length <= 0 || arguments[0] === undefined ? DEFAULT_VALUES : arguments[0];
+
+        _classCallCheck(this, EventEmitter);
+
+        var emitDelay = void 0,
+            strictMode = void 0;
+
+        if (opts.hasOwnProperty('emitDelay')) {
+            emitDelay = opts.emitDelay;
+        } else {
+            emitDelay = DEFAULT_VALUES.emitDelay;
+        }
+        this._emitDelay = emitDelay;
+
+        if (opts.hasOwnProperty('strictMode')) {
+            strictMode = opts.strictMode;
+        } else {
+            strictMode = DEFAULT_VALUES.strictMode;
+        }
+        this._strictMode = strictMode;
+
+        this._listeners = {};
+        this.events = [];
+    }
+
+    /**
+     * @protected
+     * @param {string} type
+     * @param {function} listener
+     * @param {boolean} [once = false]
+     */
+
+
+    _createClass(EventEmitter, [{
+        key: '_addListenner',
+        value: function _addListenner(type, listener, once) {
+            if (typeof listener !== 'function') {
+                throw TypeError('listener must be a function');
+            }
+
+            if (this.events.indexOf(type) === -1) {
+                this._listeners[type] = [{
+                    once: once,
+                    fn: listener
+                }];
+                this.events.push(type);
+            } else {
+                this._listeners[type].push({
+                    once: once,
+                    fn: listener
+                });
+            }
+        }
+
+        /**
+         * Subscribes on event type specified function
+         * @param {string} type
+         * @param {function} listener
+         */
+
+    }, {
+        key: 'on',
+        value: function on(type, listener) {
+            this._addListenner(type, listener, false);
+        }
+
+        /**
+         * Subscribes on event type specified function to fire only once
+         * @param {string} type
+         * @param {function} listener
+         */
+
+    }, {
+        key: 'once',
+        value: function once(type, listener) {
+            this._addListenner(type, listener, true);
+        }
+
+        /**
+         * Removes event with specified type. If specified listenerFunc - deletes only one listener of specified type
+         * @param {string} eventType
+         * @param {function} [listenerFunc]
+         */
+
+    }, {
+        key: 'off',
+        value: function off(eventType, listenerFunc) {
+            var _this = this;
+
+            var typeIndex = this.events.indexOf(eventType);
+            var hasType = eventType && typeIndex !== -1;
+
+            if (hasType) {
+                if (!listenerFunc) {
+                    delete this._listeners[eventType];
+                    this.events.splice(typeIndex, 1);
+                } else {
+                    (function () {
+                        var removedEvents = [];
+                        var typeListeners = _this._listeners[eventType];
+
+                        typeListeners.forEach(
+                        /**
+                         * @param {EventEmitterListenerFunc} fn
+                         * @param {number} idx
+                         */
+                        function (fn, idx) {
+                            if (fn.fn === listenerFunc) {
+                                removedEvents.unshift(idx);
+                            }
+                        });
+
+                        removedEvents.forEach(function (idx) {
+                            typeListeners.splice(idx, 1);
+                        });
+
+                        if (!typeListeners.length) {
+                            _this.events.splice(typeIndex, 1);
+                            delete _this._listeners[eventType];
+                        }
+                    })();
+                }
+            }
+        }
+
+        /**
+         * Applies arguments to specified event type
+         * @param {string} eventType
+         * @param {*[]} eventArguments
+         * @protected
+         */
+
+    }, {
+        key: '_applyEvents',
+        value: function _applyEvents(eventType, eventArguments) {
+            var typeListeners = this._listeners[eventType];
+
+            if (!typeListeners || !typeListeners.length) {
+                if (this._strictMode) {
+                    throw 'No listeners specified for event: ' + eventType;
+                } else {
+                    return;
+                }
+            }
+
+            var removableListeners = [];
+            typeListeners.forEach(function (eeListener, idx) {
+                eeListener.fn.apply(null, eventArguments);
+                if (eeListener.once) {
+                    removableListeners.unshift(idx);
+                }
+            });
+
+            removableListeners.forEach(function (idx) {
+                typeListeners.splice(idx, 1);
+            });
+        }
+
+        /**
+         * Emits event with specified type and params.
+         * @param {string} type
+         * @param eventArgs
+         */
+
+    }, {
+        key: 'emit',
+        value: function emit(type) {
+            var _this2 = this;
+
+            for (var _len = arguments.length, eventArgs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+                eventArgs[_key - 1] = arguments[_key];
+            }
+
+            if (this._emitDelay) {
+                setTimeout(function () {
+                    _this2._applyEvents.call(_this2, type, eventArgs);
+                }, this._emitDelay);
+            } else {
+                this._applyEvents(type, eventArgs);
+            }
+        }
+
+        /**
+         * Emits event with specified type and params synchronously.
+         * @param {string} type
+         * @param eventArgs
+         */
+
+    }, {
+        key: 'emitSync',
+        value: function emitSync(type) {
+            for (var _len2 = arguments.length, eventArgs = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+                eventArgs[_key2 - 1] = arguments[_key2];
+            }
+
+            this._applyEvents(type, eventArgs);
+        }
+
+        /**
+         * Destroys EventEmitter
+         */
+
+    }, {
+        key: 'destroy',
+        value: function destroy() {
+            this._listeners = {};
+            this.events = [];
+        }
+    }]);
+
+    return EventEmitter;
+}();
+
+module.exports = EventEmitter;
+
+},{}],3:[function(require,module,exports){
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define([], factory);
+    } else if (typeof exports === 'object') {
+        // Node. Does not work with strict CommonJS, but
+        // only CommonJS-like environments that support module.exports,
+        // like Node.
+        module.exports = factory();
+    } else {
+        // Browser globals (root is window)
+        root.FaviconNotification = factory();
+  }
+}(this, function() {
+
+    // Just return a value to define the module export.
+    // This example returns an object, but the module
+    // can return a function as the exported value.
+
+    // Only run in browser
+    if (typeof document === 'undefined') {
+      console.log('This script only run in browsers.');
+      return;
+    }
+
+    // Private properties
+    var _options = {};
+
+    var _defaults = {
+      url: '/favicon.ico',
+      color: '#eb361e',
+      lineColor: '#ffffff'
+    };
+
+    var _generatedFavicon;
+    var _iconElement;
+
+    // Provate methods
+    var _addFavicon = function(src) {
+      var head = document.getElementsByTagName('head')[0];
+      _iconElement = document.createElement('link');
+      _iconElement.type = 'image/x-icon';
+      _iconElement.rel = 'icon';
+      _iconElement.href = src;
+
+      // remove existing favicons
+      var links = document.getElementsByTagName('link');
+      for(var i=0, len=links.length; i < len; i++) {
+    		var exists = (typeof(links[i]) !== 'undefined');
+    		if (exists && (links[i].getAttribute('rel') || '').match(/\bicon\b/)) {
+    			head.removeChild(links[i]);
+    		}
+    	}
+
+      head.appendChild(_iconElement);
+    };
+
+    var _generateIcon = function(cb) {
+      var img = document.createElement('img');
+      img.src = _options.url;
+
+      img.onload = function() {
+        var lineWidth = 2;
+        var canvas = document.createElement('canvas');
+        canvas.width = img.width;
+        canvas.height = img.height;
+
+        var context = canvas.getContext('2d');
+        context.clearRect(0, 0, img.width, img.height);
+        context.drawImage(img, 0, 0);
+
+        var centerX = img.width - (img.width / 4.5) - lineWidth;
+        var centerY = img.height - (img.height / 4.5) - lineWidth;
+        var radius = img.width / 4.5;
+
+        context.fillStyle = _options.color;
+        context.strokeStyle = _options.lineColor;
+        context.lineWidth = lineWidth;
+
+        context.beginPath();
+        context.arc(centerX, centerY, radius, 0, Math.PI * 2, false);
+        context.closePath();
+        context.fill();
+        context.stroke();
+
+        cb(null, context.canvas.toDataURL());
+      };
+    };
+
+    var _setOptions = function(options) {
+      if (!options) {
+        _options = _defaults;
+        return;
+      }
+
+      _options = {};
+
+      for(var key in _defaults){
+           _options[key] = options.hasOwnProperty(key) ? options[key] : _defaults[key];
+      }
+    };
+
+    var FaviconNotification = {
+      init: function(options) {
+
+        _setOptions(options);
+
+        _generateIcon(function(err, url){
+          _generatedFavicon = url;
+        });
+
+        _addFavicon(_options.url);
+
+      },
+
+      add: function() {
+        if (!_generatedFavicon && !_iconElement) {
+          _setOptions();
+          _generateIcon(function(err, url) {
+            _generatedFavicon = url;
+            _addFavicon(url);
+          });
+        } else {
+          _iconElement.href = _generatedFavicon;
+        }
+
+      },
+
+      remove: function() {
+        _iconElement.href = _options.url;
+      }
+    };
+
+
+    return FaviconNotification;
+
+
+}));
+
+},{}]},{},[1]);
diff --git a/Nibriboard/ClientFiles/RippleLink.js b/Nibriboard/ClientFiles/RippleLink.js
new file mode 100644
index 0000000..3b2a3ab
--- /dev/null
+++ b/Nibriboard/ClientFiles/RippleLink.js
@@ -0,0 +1,12 @@
+"use strict";
+
+
+class RippleLink
+{
+	constructor(socketUrl)
+	{
+		
+	}
+}
+
+export default RippleLink;
diff --git a/Nibriboard/ClientFiles/Utilities.js b/Nibriboard/ClientFiles/Utilities.js
new file mode 100644
index 0000000..15c0c38
--- /dev/null
+++ b/Nibriboard/ClientFiles/Utilities.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function get(u){return new Promise(function(r,t,a){a=new XMLHttpRequest();a.onload=function(b,c){b=a.status;c=a.response;if(b>199&&b<300){r(c)}else{t(c)}};a.open("GET",u,true);a.send(null)})}
+
+export {
+	get
+};
diff --git a/Nibriboard/ClientFiles/favicon.ico b/Nibriboard/ClientFiles/favicon.ico
new file mode 100644
index 0000000..6848441
Binary files /dev/null and b/Nibriboard/ClientFiles/favicon.ico differ
diff --git a/Nibriboard/ClientFiles/index.html b/Nibriboard/ClientFiles/index.html
new file mode 100644
index 0000000..148f8d6
--- /dev/null
+++ b/Nibriboard/ClientFiles/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset='utf-8' />
+		<title>Nibriboard</title>
+	</head>
+	<body>
+		<canvas id="canvas-main"></canvas>
+		
+		<!---------------->
+		<link rel="stylesheet" href="Nibri.css" />
+		
+		<script src="NibriClient.js" charset="utf-8"></script>
+	</head>
+</html>
diff --git a/Nibriboard/ClientFiles/index.js b/Nibriboard/ClientFiles/index.js
new file mode 100644
index 0000000..5b700f2
--- /dev/null
+++ b/Nibriboard/ClientFiles/index.js
@@ -0,0 +1,10 @@
+"use strict";
+
+import BoardWindow from './BoardWindow';
+
+window.addEventListener("load", function (event) {
+	var canvas = document.getElementById("canvas-main"),
+		boardWindow = new BoardWindow(canvas);
+	boardWindow.nextFrame();
+	window.boardWindow = boardWindow;
+});
diff --git a/Nibriboard/Client/package.json b/Nibriboard/ClientFiles/package.json
similarity index 100%
rename from Nibriboard/Client/package.json
rename to Nibriboard/ClientFiles/package.json
diff --git a/Nibriboard/Env.cs b/Nibriboard/Env.cs
index a1c2b08..4392365 100644
--- a/Nibriboard/Env.cs
+++ b/Nibriboard/Env.cs
@@ -8,7 +8,7 @@ namespace Nibriboard
 		public static double SecondsSinceStart {
 			get {
 				TimeSpan timeSinceStart = DateTime.Now - ServerStartTime;
-				return timeSinceStart.TotalSeconds;
+				return Math.Round(timeSinceStart.TotalSeconds, 4);
 			}
 		}
 	}
diff --git a/Nibriboard/HttpEmbeddedFileHandler.cs b/Nibriboard/HttpEmbeddedFileHandler.cs
new file mode 100644
index 0000000..35c6c41
--- /dev/null
+++ b/Nibriboard/HttpEmbeddedFileHandler.cs
@@ -0,0 +1,75 @@
+﻿using System;
+using System.IO;
+using IotWeb.Common.Http;
+using MimeSharp;
+
+using SBRLUtilities;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Nibriboard
+{
+	public class HttpEmbeddedFileHandler : IHttpRequestHandler
+	{
+		private string filePrefix;
+
+		private Mime mimeTypeFinder = new Mime();
+		private Dictionary<string, string> mimeTypeOverrides = new Dictionary<string, string>() {
+			["application/xhtml+xml"] = "text/html",
+			["application/tei+xml"] = "image/x-icon"
+		};
+
+		private List<string> embeddedFiles = new List<string>(EmbeddedFiles.ResourceList);
+
+		public HttpEmbeddedFileHandler(string inFilePrefix)
+		{
+			filePrefix = inFilePrefix;
+		}
+
+		public void HandleRequest(string uri, HttpRequest request, HttpResponse response, HttpContext context) {
+			StreamWriter responseData = new StreamWriter(response.Content) { AutoFlush = true };
+			if (request.Method != HttpMethod.Get) {
+				response.ResponseCode = HttpResponseCode.MethodNotAllowed;
+				response.ContentType = "text/plain";
+				responseData.WriteLine("Error: That method isn't supported yet.");
+				logRequest(request, response);
+				return;
+			}
+
+			response.ContentType = getMimeType(request.URI);
+			response.Headers.Add("content-type", response.ContentType);
+
+			string expandedFilePath = getEmbeddedFileReference(request.URI);
+			if (!embeddedFiles.Contains(expandedFilePath)) {
+				response.ResponseCode = HttpResponseCode.NotFound;
+				response.ContentType = "text/plain";
+				responseData.WriteLine("Can't find {0}.", expandedFilePath);
+				logRequest(request, response);
+				return;
+			}
+			byte[] embeddedFile = EmbeddedFiles.ReadAllBytes(expandedFilePath);
+			response.ContentLength = embeddedFile.Length;
+			response.Content.Write(embeddedFile, 0, embeddedFile.Length);
+
+			logRequest(request, response);
+		}
+
+		protected string getEmbeddedFileReference(string uri) {
+			return filePrefix + "." + uri.TrimStart("/".ToCharArray());
+		}
+
+		protected string getMimeType(string uri) {
+			string mimeType = mimeTypeFinder.Lookup(uri);
+			foreach (KeyValuePair<string, string> mimeMapping in mimeTypeOverrides) {
+				if (mimeType == mimeMapping.Key)
+					mimeType = mimeMapping.Value;
+			}
+			return mimeType;
+		}
+
+		private void logRequest(HttpRequest request, HttpResponse response) {
+			Log.WriteLine("[Http/FileHandler] {0} {1} {2} {3}", response.ResponseCode.ResponseCode(), response.ContentType, request.Method.ToString().ToUpper(), request.URI);
+		}
+	}
+}
+
diff --git a/Nibriboard/Nibriboard.csproj b/Nibriboard/Nibriboard.csproj
index fa7650a..78799a1 100644
--- a/Nibriboard/Nibriboard.csproj
+++ b/Nibriboard/Nibriboard.csproj
@@ -1,4 +1,5 @@
-﻿<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@@ -6,7 +7,7 @@
     <OutputType>Exe</OutputType>
     <RootNamespace>Nibriboard</RootNamespace>
     <AssemblyName>Nibriboard</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
     <DebugSymbols>true</DebugSymbols>
@@ -20,13 +21,10 @@
     <PlatformTarget>x86</PlatformTarget>
     <CustomCommands>
       <CustomCommands>
-        <Command>
-          <type>BeforeBuild</type>
-          <command>npm run build</command>
-          <workingdir>${ProjectDir}/Client</workingdir>
-        </Command>
+        <Command type="BeforeBuild" command="npm run build" workingdir="${ProjectDir}/ClientFiles" />
       </CustomCommands>
     </CustomCommands>
+    <Externalconsole>true</Externalconsole>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
     <Optimize>true</Optimize>
@@ -44,6 +42,12 @@
     <Reference Include="IotWeb.Server">
       <HintPath>..\packages\IotWeb.0.8.6\lib\net45\IotWeb.Server.dll</HintPath>
     </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="MimeSharp">
+      <HintPath>..\packages\MimeSharp.1.0.0\lib\MimeSharp.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Program.cs" />
@@ -62,20 +66,22 @@
     <Compile Include="Utilities\EmbeddedFiles.cs" />
     <Compile Include="Env.cs" />
     <Compile Include="RippleSpace\RippleSpaceManager.cs" />
+    <Compile Include="HttpEmbeddedFileHandler.cs" />
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="Client\index.html" />
-    <EmbeddedResource Include="Client\NibriClient.js" />
-    <EmbeddedResource Include="Client\Nibri.css" />
+    <EmbeddedResource Include="ClientFiles\index.html" />
+    <EmbeddedResource Include="ClientFiles\NibriClient.js" />
+    <EmbeddedResource Include="ClientFiles\Nibri.css" />
+    <EmbeddedResource Include="ClientFiles\favicon.ico" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="RippleSpace\" />
-    <Folder Include="Client\" />
     <Folder Include="Utilities\" />
+    <Folder Include="ClientFiles\" />
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
-    <None Include="Client\index.js" />
+    <None Include="ClientFiles\index.js" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/Nibriboard/NibriboardServer.cs b/Nibriboard/NibriboardServer.cs
index e5bab44..339c1c2 100644
--- a/Nibriboard/NibriboardServer.cs
+++ b/Nibriboard/NibriboardServer.cs
@@ -24,11 +24,12 @@ namespace Nibriboard
 			httpServer = new HttpServer(Port);
 			httpServer.AddHttpRequestHandler(
 				"/",
-				new HttpResourceHandler(
+				new HttpEmbeddedFileHandler("Nibriboard.ClientFiles")
+				/*new HttpResourceHandler(
 					Assembly.GetExecutingAssembly(),
-					"Nibriboard",
+					"ClientFiles",
 					"index.html"
-				)
+				)*/
 			);
 			httpServer.AddWebSocketRequestHandler(
 				"/RipplespaceConnection",
diff --git a/Nibriboard/RippleSpace/ChunkReference.cs b/Nibriboard/RippleSpace/ChunkReference.cs
index 37beff1..2ac4c69 100644
--- a/Nibriboard/RippleSpace/ChunkReference.cs
+++ b/Nibriboard/RippleSpace/ChunkReference.cs
@@ -36,6 +36,10 @@ namespace Nibriboard.RippleSpace
 			return $"{Plane.Name}-{X},{Y}.chunk";
 		}
 
+		public override int GetHashCode ()
+		{
+			return $"({Plane.Name})+{X}+{Y}".GetHashCode();
+		}
 		public override bool Equals(object obj)
 		{
 			ChunkReference otherChunkReference = obj as ChunkReference;
diff --git a/Nibriboard/RippleSpace/LocationReference.cs b/Nibriboard/RippleSpace/LocationReference.cs
index c1e65ca..1c445d9 100644
--- a/Nibriboard/RippleSpace/LocationReference.cs
+++ b/Nibriboard/RippleSpace/LocationReference.cs
@@ -36,6 +36,10 @@ namespace Nibriboard.RippleSpace
 			}
 			return false;
 		}
+		public override int GetHashCode ()
+		{
+			return $"({Plane.Name})+{X}+{Y}".GetHashCode();
+		}
 
 		public override string ToString()
 		{
diff --git a/Nibriboard/Utilities/EmbeddedFiles.cs b/Nibriboard/Utilities/EmbeddedFiles.cs
index 6086b9c..d94f869 100644
--- a/Nibriboard/Utilities/EmbeddedFiles.cs
+++ b/Nibriboard/Utilities/EmbeddedFiles.cs
@@ -11,7 +11,7 @@ namespace SBRLUtilities
 	/// A collection of static methods for manipulating embedded resources.
 	/// </summary>
 	/// <description>
-	/// v0.2, by Starbeamrainbowlabs <feedback@starbeamrainbowlabs.com>
+	/// v0.3, by Starbeamrainbowlabs <feedback@starbeamrainbowlabs.com>
 	/// Last updated 8th August 2016.
 	/// Licensed under MPL-2.0.
 	/// 
@@ -20,6 +20,8 @@ namespace SBRLUtilities
 	/// 	- Initial release.
 	/// v0.2 (8th August 2016):
 	/// 	- Changed namespace.
+	/// v0.3 (21st January 2017):
+	/// 	- Added GetRawReader()
 	/// </description>
 	public static class EmbeddedFiles
 	{
@@ -52,11 +54,22 @@ namespace SBRLUtilities
 		/// <summary>
 		/// Gets a StreamReader attached to the specified embedded resource.
 		/// </summary>
-		/// <param name="filename">The filename of the embedded resource to a StreamReader for.</param>
+		/// <param name="filename">The filename of the embedded resource to get a StreamReader of.</param>
 		/// <returns>A StreamReader attached to the specified embedded resource.</returns>
 		public static StreamReader GetReader(string filename)
 		{
-			return new StreamReader(Assembly.GetCallingAssembly().GetManifestResourceStream(filename));
+			return new StreamReader(GetRawReader(filename));
+		}
+
+		/// <summary>
+		/// Gets a raw Stream that's attached to the specified embedded resource.
+		/// Useful when you want to copy an embedded resource to some other stream.
+		/// </summary>
+		/// <param name="filename">The path to the embedded resource.</param>
+		/// <returns>A raw Stream object attached to the specified file..</returns>
+		public static Stream GetRawReader(string filename)
+		{
+			return Assembly.GetCallingAssembly().GetManifestResourceStream(filename);
 		}
 
 		/// <summary>
@@ -76,7 +89,7 @@ namespace SBRLUtilities
 		/// <returns>The specified embedded resource's content as a byte array.</returns>
 		public static async Task<byte[]> ReadAllBytesAsync(string filename)
 		{
-			using (Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(filename))
+			using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename))
 			using (MemoryStream temp = new MemoryStream())
 			{
 				await resourceStream.CopyToAsync(temp);
diff --git a/Nibriboard/packages.config b/Nibriboard/packages.config
index 0b10140..f44a2b3 100644
--- a/Nibriboard/packages.config
+++ b/Nibriboard/packages.config
@@ -1,4 +1,6 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="IotWeb" version="0.8.6" targetFramework="net452" />
+  <package id="MimeSharp" version="1.0.0" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
 </packages>
\ No newline at end of file
