From 3a48bfd9123c13c8181ca8958df4e1ffac584d5b Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Tue, 8 Dec 2015 23:42:50 +0100
Subject: [PATCH] r20151208

Signed-off-by: Flashwave <me@flash.moe>
---
 public/content/data/yuuno/css/chat.css      | 106 ---
 public/content/data/yuuno/js/chat.js        |  57 --
 public/content/data/yuuno/js/ybabstat.js    |  88 +--
 public/content/data/yuuno/js/ybabstat.ts    |  68 ++
 public/content/data/yuuno/js/yuuno.test.js  | 591 ++++++++++++++++
 public/content/data/yuuno/js/yuuno.test.ts  | 716 ++++++++++++++++++++
 public/content/data/yuuno/jsold/ybabstat.js |  79 +++
 public/settings.php                         |   2 +-
 sakura.php                                  |   2 +-
 templates/yuuno/global/chat.tpl             |  30 -
 templates/yuuno/global/master.tpl           |   4 +-
 11 files changed, 1485 insertions(+), 258 deletions(-)
 delete mode 100644 public/content/data/yuuno/css/chat.css
 delete mode 100644 public/content/data/yuuno/js/chat.js
 create mode 100644 public/content/data/yuuno/js/ybabstat.ts
 create mode 100644 public/content/data/yuuno/js/yuuno.test.js
 create mode 100644 public/content/data/yuuno/js/yuuno.test.ts
 create mode 100644 public/content/data/yuuno/jsold/ybabstat.js
 delete mode 100644 templates/yuuno/global/chat.tpl

diff --git a/public/content/data/yuuno/css/chat.css b/public/content/data/yuuno/css/chat.css
deleted file mode 100644
index 631a043..0000000
--- a/public/content/data/yuuno/css/chat.css
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Site chat styling
- */
-#chat > #chatOnlineUsers {
-    position: fixed;
-    right: -300px;
-    top: 0;
-    bottom: 0;
-    width: 300px;
-    background: rgba(0, 0, 0, .7);
-    color: #FFF;
-    z-index: 1;
-    box-shadow: 0 0 2px #222;
-    overflow: auto;
-    transition: .5s;
-    visibility: hidden;
-}
-
-#chat > #chatOnlineUsers.open {
-    right: 0;
-    visibility: visible;
-}
-
-#chat > #chatOnlineUsers > div {
-    margin: 5px;
-}
-
-#chat > #chatOnlineUsers > .chatOnlineListTitle {
-    background: none;
-    font-size: 2em;
-    line-height: 1.2em;
-    font-family: "SegoeUI-Light", sans-serif;
-    padding: 5px;
-}
-
-#chatUserList > div {
-    display: flex;
-    align-items: center;
-    background: rgba(0, 0, 0, .7);
-    padding: 5px;
-    border-radius: 5px;
-}
-
-#chatUserList > div:not(:last-child) {
-    margin-bottom: 5px;
-}
-
-#chatUserList > div > .avatar {
-    width: 50px;
-    height: 50px;
-    background: transparent url("/content/pixel.png") no-repeat scroll left center / contain;
-    border-radius: 5px;
-}
-
-#chatUserList > div > .options {
-    margin-left: 10px;
-}
-
-#chatUserList > div > .options > .actions {
-    list-style: none;
-    font-size: .8em;
-    line-height: 1.2em;
-}
-
-#chatAccessButtons {
-    position: fixed;
-    bottom: 0;
-    right: 0;
-    z-index: 2;
-}
-
-#chatAccessButtons > a {
-    display: flex;
-    flex-direction: column;
-    margin: 5px 0;
-    background: rgba(0, 0, 0, .5);
-    color: #FFF;
-    font-size: 2em;
-    width: 40px;
-    height: 40px;
-    line-height: 40px;
-    border-radius: 5px 0 0 5px;
-    text-decoration: none;
-    text-align: center;
-    text-shadow: 0 0 2px #FFF;
-    position: relative;
-    transition: .5s;
-    box-shadow: 0 0 2px #222;
-    cursor: pointer;
-}
-
-#chatAccessButtons > a:hover {
-    text-shadow: 0 0 5px #FFF;
-}
-
-#chatAccessButtons > a:active {
-    text-shadow: 0 0 7px #FFF;
-}
-
-#chatAccessButtons > a.enter {
-    animation: slideInFromRight 1 .6s, fadeIn 1 .6s;
-}
-
-#chatAccessButtons > a.exit {
-    animation: slideOutToBottom 1 .6s, fadeOut 1 .6s;
-}
diff --git a/public/content/data/yuuno/js/chat.js b/public/content/data/yuuno/js/chat.js
deleted file mode 100644
index 54bdfb7..0000000
--- a/public/content/data/yuuno/js/chat.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * On-site Sock Chat client
- */
-
-var Chat = {
-
-    server: null,
-    chatContainer: null,
-    accessButtons: null,
-    onlineList: null,
-    connected: false,
-
-    connect: function(server, force) {
-        // Set server
-        this.server = server;
-
-        // Set required variables
-        this.chatContainer = document.getElementById('chat');
-        this.accessButtons = document.getElementById('chatAccessButtons');
-        this.onlineList = document.getElementById('chatOnlineUsers');
-
-        // Check if we haven't already established a connection
-        if(this.connected && !force) {
-            this.accessButtons.innerHTML += '<a id="chatConnecting" class="fa fa-exclamation-triangle" title="Force a reconnect." href="javascript:void(0);" onclick="Chat.connect('+ this.server +', true);"></a>';
-        }
-
-        // Attempt to connect to the server
-        this.accessButtons.innerHTML = '<a id="chatConnecting" title="Connecting to chat..."><div class="fa fa-spin fa-spinner" style="line-height: inherit;"></div></a>';
-
-        // Grab connection indicator
-        var connectionIndicator = document.getElementById('chatConnecting');
-
-        setTimeout(function() {
-            if(Chat.connected) {
-                connectionIndicator.setAttribute('title', 'Connected!');
-                connectionIndicator.children[0].className = 'fa fa-chain';
-                var accessButtonsCont = '<a id="showOnlineUsers" class="fa fa-users" href="javascript:void(0);" onclick="Chat.toggleOnlineList();" title="Toggle online users list"></a><a id="openSiteChat" class="fa fa-comments-o" href="javascript:void(0);" title="View chat"></a>';
-            } else {
-                connectionIndicator.setAttribute('title', 'Failed to connect, try again later!');
-                connectionIndicator.children[0].className = 'fa fa-chain-broken';
-                var accessButtonsCont = '<a id="showChatTicker" class="fa fa-refresh" href="javascript:void(0);" onclick="Chat.connect('+ this.server +');"></a>';
-            }
-            setTimeout(function() {
-                Chat.accessButtons.innerHTML = accessButtonsCont;
-            }, 500);
-        }, 500);
-    },
-
-    toggleOnlineList: function() {
-        this.onlineList.className = this.onlineList.className != 'open' ? 'open' : '';
-    },
-
-    toggleTicker: function() {
-        this.chatTicker.className = this.chatTicker.className != 'open' ? 'open' : '';
-    }
-
-};
diff --git a/public/content/data/yuuno/js/ybabstat.js b/public/content/data/yuuno/js/ybabstat.js
index eca73ff..34a534f 100644
--- a/public/content/data/yuuno/js/ybabstat.js
+++ b/public/content/data/yuuno/js/ybabstat.js
@@ -1,79 +1,47 @@
-var illuminati  = [];
-var startTime   = (new Date()).getTime();
-
+var illuminati = new Array();
+var startTime = (new Date()).getTime();
 function hideYourMind(conflictions) {
-
     var twoThousandTwelveIsTheYearWeAscendToSpaceRobots = conflictions.keyCode;
-
     illuminati.push(twoThousandTwelveIsTheYearWeAscendToSpaceRobots);
-
-    if(illuminati[0] == 68 && illuminati[1] == 73 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 83) {
-
-        var dicksAre        = document.createElement('audio');
-        var forMyFriends    = document.createElement('source');
-        var whenTheyCome    = document.createElement('source');
-
-        forMyFriends.setAttribute('type', 'audio/mp3');
-        whenTheyCome.setAttribute('type', 'audio/ogg');
-
-        forMyFriends.setAttribute('src', sakuraVars.content + '/sounds/dicks.mp3');
-        whenTheyCome.setAttribute('src', sakuraVars.content + '/sounds/dicks.ogg');
-
+    if (illuminati[0] == 68 && illuminati[1] == 73 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 83) {
+        var dicksAre = document.createElement('audio');
+        var forMyFriends = document.createElement('source');
+        var whenTheyCome = document.createElement('source');
+        forMyFriends.type = 'audio/mp3';
+        whenTheyCome.type = 'audio/ogg';
+        forMyFriends.src = sakuraVars.content + '/sounds/dicks.mp3';
+        whenTheyCome.src = sakuraVars.content + '/sounds/dicks.ogg';
         dicksAre.appendChild(forMyFriends);
         dicksAre.appendChild(whenTheyCome);
-
         var toMyHouse = dicksAre;
-
         toMyHouse.play();
-
-        illuminati = [];
-
+        illuminati = new Array();
     }
-
-    if(illuminati[0] == 77 && illuminati[1] == 69 && illuminati[2] == 87 && illuminati[3] == 79 && illuminati[4] == 87) {
-
-        var noklz       = document.createElement('audio');
-        var von         = document.createElement('source');
-        var schnitzel   = document.createElement('source');
-
-        von.setAttribute('type', 'audio/mp3');
-        schnitzel.setAttribute('type', 'audio/ogg');
-
-        von.setAttribute('src', sakuraVars.content + '/sounds/mewow.mp3');
-        schnitzel.setAttribute('src', sakuraVars.content + '/sounds/mewow.ogg');
-
+    if (illuminati[0] == 77 && illuminati[1] == 69 && illuminati[2] == 87 && illuminati[3] == 79 && illuminati[4] == 87) {
+        var noklz = document.createElement('audio');
+        var von = document.createElement('source');
+        var schnitzel = document.createElement('source');
+        von.type = 'audio/mp3';
+        schnitzel.type = 'audio/ogg';
+        von.src = sakuraVars.content + '/sounds/mewow.mp3';
+        schnitzel.src = sakuraVars.content + '/sounds/mewow.ogg';
         noklz.appendChild(von);
         noklz.appendChild(schnitzel);
-
         noklz.play();
-
         document.body.style.animation = 'spin 5s infinite linear';
-
-        illuminati = [];
-
+        illuminati = new Array();
     }
-
-    if(illuminati[0] == 83 && illuminati[1] == 79 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 67 && illuminati[5] == 72 && illuminati[6] == 65 && illuminati[7] == 84) {
-
-        setInterval("twoThousandSixteenIsTheYearWePhysicallyMergeWithCats();", 17);
-
-        illuminati = [];
-
+    if (illuminati[0] == 83 && illuminati[1] == 79 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 67 && illuminati[5] == 72 && illuminati[6] == 65 && illuminati[7] == 84) {
+        setInterval(twoThousandSixteenIsTheYearWePhysicallyMergeWithCats, 20);
+        illuminati = new Array();
     }
-
 }
-
 function twoThousandSixteenIsTheYearWePhysicallyMergeWithCats() {
-
     var diff = (new Date()).getTime() - startTime;
     var vals = [-7 / Math.cos((diff / 500) * (.85 * Math.PI)), -7 * Math.tan((diff / 250) * (.85 * Math.PI))];
-
-    document.body.style.position    = 'absolute';
-    document.body.style.left        = vals[0] + 'px';
-    document.body.style.top         = vals[1] + 'px';
-    document.body.style.fontSize    = vals[0] + 'px';
-
+    document.body.style.position = 'absolute';
+    document.body.style.left = vals[0] + 'px';
+    document.body.style.top = vals[1] + 'px';
+    document.body.style.fontSize = vals[0] + 'px';
 }
-
-document.addEventListener("onkeydown",  hideYourMind, false);
-document.addEventListener("keydown",    hideYourMind, false);
+document.addEventListener('keydown', hideYourMind, false);
diff --git a/public/content/data/yuuno/js/ybabstat.ts b/public/content/data/yuuno/js/ybabstat.ts
new file mode 100644
index 0000000..8fc7fbb
--- /dev/null
+++ b/public/content/data/yuuno/js/ybabstat.ts
@@ -0,0 +1,68 @@
+var illuminati: Array<number> = new Array<number>();
+var startTime: number = (new Date()).getTime();
+
+function hideYourMind(conflictions: KeyboardEvent): void {
+    var twoThousandTwelveIsTheYearWeAscendToSpaceRobots: number = conflictions.keyCode;
+
+    illuminati.push(twoThousandTwelveIsTheYearWeAscendToSpaceRobots);
+
+    if (illuminati[0] == 68 && illuminati[1] == 73 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 83) {
+        var dicksAre: HTMLAudioElement = document.createElement('audio');
+        var forMyFriends: HTMLSourceElement = document.createElement('source');
+        var whenTheyCome: HTMLSourceElement = document.createElement('source');
+
+        forMyFriends.type = 'audio/mp3';
+        whenTheyCome.type = 'audio/ogg';
+
+        forMyFriends.src = sakuraVars.content + '/sounds/dicks.mp3';
+        whenTheyCome.src = sakuraVars.content + '/sounds/dicks.ogg';
+
+        dicksAre.appendChild(forMyFriends);
+        dicksAre.appendChild(whenTheyCome);
+
+        var toMyHouse: HTMLAudioElement = dicksAre;
+
+        toMyHouse.play();
+
+        illuminati = new Array<number>();
+    }
+
+    if (illuminati[0] == 77 && illuminati[1] == 69 && illuminati[2] == 87 && illuminati[3] == 79 && illuminati[4] == 87) {
+        var noklz: HTMLAudioElement = document.createElement('audio');
+        var von: HTMLSourceElement = document.createElement('source');
+        var schnitzel: HTMLSourceElement = document.createElement('source');
+
+        von.type = 'audio/mp3';
+        schnitzel.type = 'audio/ogg';
+
+        von.src = sakuraVars.content + '/sounds/mewow.mp3';
+        schnitzel.src = sakuraVars.content + '/sounds/mewow.ogg';
+
+        noklz.appendChild(von);
+        noklz.appendChild(schnitzel);
+
+        noklz.play();
+
+        document.body.style.animation = 'spin 5s infinite linear';
+
+        illuminati = new Array<number>();
+    }
+
+    if (illuminati[0] == 83 && illuminati[1] == 79 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 67 && illuminati[5] == 72 && illuminati[6] == 65 && illuminati[7] == 84) {
+        setInterval(twoThousandSixteenIsTheYearWePhysicallyMergeWithCats, 20);
+
+        illuminati = new Array<number>();
+    }
+}
+
+function twoThousandSixteenIsTheYearWePhysicallyMergeWithCats() {
+    var diff: number = (new Date()).getTime() - startTime;
+    var vals: Array<number> = [-7 / Math.cos((diff / 500) * (.85 * Math.PI)), -7 * Math.tan((diff / 250) * (.85 * Math.PI))];
+
+    document.body.style.position = 'absolute';
+    document.body.style.left = vals[0] + 'px';
+    document.body.style.top = vals[1] + 'px';
+    document.body.style.fontSize = vals[0] + 'px';
+}
+
+document.addEventListener('keydown', hideYourMind, false);
diff --git a/public/content/data/yuuno/js/yuuno.test.js b/public/content/data/yuuno/js/yuuno.test.js
new file mode 100644
index 0000000..604adb8
--- /dev/null
+++ b/public/content/data/yuuno/js/yuuno.test.js
@@ -0,0 +1,591 @@
+/*
+ * Sakura Yuuno
+ */
+// Spawns a notification
+function notifyUI(content) {
+    // Grab the container and create an ID
+    var cont = document.getElementById('notifications');
+    var id = 'sakura-notification-' + Date.now();
+    // Create the elements
+    var alert = document.createElement('div');
+    var aIcon = document.createElement('div');
+    var aCont = document.createElement('div');
+    var aTitle = document.createElement('div');
+    var aText = document.createElement('div');
+    var aClose = document.createElement('div');
+    var aCIcon = document.createElement('div');
+    var aClear = document.createElement('div');
+    var aIconCont;
+    // Add attributes to the main element
+    alert.className = 'notification-enter';
+    alert.id = id;
+    // Add the icon
+    if ((typeof content.img).toLowerCase() === 'undefined' && content.img == null && !(content.img.length > 1)) {
+        aIconCont = document.createElement('div');
+        aIconCont.className = 'font-icon fa fa-info fa-4x';
+    }
+    else if (content.img.substr(0, 5) == 'FONT:') {
+        aIconCont = document.createElement('div');
+        aIconCont.className = 'font-icon fa ' + content.img.replace('FONT:', '') + ' fa-4x';
+    }
+    else {
+        aIconCont = document.createElement('img');
+        aIconCont.alt = id;
+        aIconCont.img = content.img;
+    }
+    aIcon.appendChild(aIconCont);
+    aIcon.className = 'notification-icon';
+    alert.appendChild(aIcon);
+    // Add the content
+    aCont.className = 'notification-content';
+    aTitle.className = 'notification-title';
+    aText.className = 'notifcation-text';
+    aTitle.textContent = content.title;
+    aText.textContent = content.text;
+    // Check if a link exists and add if it does
+    if ((typeof content.link).toLowerCase() !== 'undefined' && content.link !== null && content.link.length > 1) {
+        alert.setAttribute('sakurahref', content.link);
+        aCont.setAttribute('onclick', content.link.substr(0, 11) == 'javascript:' ? content.link.substring(11) : 'notifyOpen(this.parentNode.id);');
+    }
+    // Append stuff
+    aCont.appendChild(aTitle);
+    aCont.appendChild(aText);
+    alert.appendChild(aCont);
+    // Add the close button
+    aClose.className = 'notification-close';
+    aClose.setAttribute('onclick', 'notifyClose(this.parentNode.id);');
+    aClose.appendChild(aCIcon);
+    alert.appendChild(aClose);
+    // Append the notification to the document
+    cont.appendChild(alert);
+    // Play sound if request
+    if (content.sound) {
+        // Create the elements
+        var sound = document.createElement('audio');
+        var mp3 = document.createElement('source');
+        var ogg = document.createElement('source');
+        // Assign attribs
+        mp3.type = 'audio/mp3';
+        ogg.type = 'audio/ogg';
+        mp3.src = sakuraVars.content_path + '/sounds/notify.mp3';
+        ogg.src = sakuraVars.content_path + '/sounds/notify.ogg';
+        // Append
+        sound.appendChild(mp3);
+        sound.appendChild(ogg);
+        // And play
+        sound.play();
+    }
+    // If keepalive is 0 keep the notification open forever
+    if (content.timeout > 0) {
+        // Set a timeout and close after an amount
+        setTimeout(function () {
+            notifyClose(id);
+        }, content.timeout);
+    }
+}
+// Closing a notification
+function notifyClose(id) {
+    // Get the element
+    var e = document.getElementById(id);
+    // Add the animation
+    e.className = 'notification-exit';
+    // Remove after 410 ms
+    setTimeout(function () {
+        Sakura.removeById(id);
+    }, 410);
+}
+// Opening an alerted link
+function notifyOpen(id) {
+    var sakuraHref = document.getElementById(id).getAttribute('sakurahref');
+    if ((typeof sakuraHref).toLowerCase() !== 'undefined') {
+        location = new Location();
+        location.assign(sakuraHref);
+        window.location = location;
+    }
+}
+// Request notifications
+function notifyRequest(session) {
+    // Check if the document isn't hidden
+    if (document.hidden) {
+        return;
+    }
+    // Create AJAX object
+    var get = new AJAX();
+    get.setUrl('/settings.php?request-notifications=true&time=' + Sakura.epoch() + '&session=' + session);
+    // Add callbacks
+    get.addCallback(200, function () {
+        // Assign the parsed JSON
+        var data = JSON.parse(get.response());
+        // Check if nothing went wrong
+        if ((typeof data).toLowerCase() === 'undefined') {
+            // Inform the user
+            throw "No or invalid data was returned";
+            // Stop
+            return;
+        }
+        // Create an object for every notification
+        for (var id in data) {
+            notifyUI(data[id]);
+        }
+    });
+    get.start(HTTPMethods.GET);
+}
+// Show the full page busy window
+function ajaxBusyView(show, message, type) {
+    if (message === void 0) { message = null; }
+    if (type === void 0) { type = null; }
+    // Get elements
+    var cont = document.getElementById('ajaxBusy');
+    var stat = document.getElementById('ajaxStatus');
+    var anim = document.getElementById('ajaxAnimate');
+    var body = document.getElementById('contentwrapper');
+    var icon = 'fa fa-4x ';
+    // Select the proper icon
+    switch (type) {
+        case 'ok':
+            icon += 'fa-check';
+            break;
+        case 'fail':
+            icon += 'fa-remove';
+            break;
+        case 'busy':
+        default:
+            icon += 'fa-refresh fa-spin';
+            break;
+    }
+    // If request to show the window, build it
+    if (show) {
+        if ((typeof cont).toLowerCase() === 'undefined' || cont === null) {
+            // Container
+            var cCont = document.createElement('div');
+            cCont.className = 'ajax-busy';
+            cCont.id = 'ajaxBusy';
+            // Inner
+            var cInner = document.createElement('div');
+            cInner.className = 'ajax-inner';
+            cCont.appendChild(cInner);
+            // Desc
+            var cMsg = document.createElement('h2');
+            cMsg.id = 'ajaxStatus';
+            cInner.appendChild(cMsg);
+            // Icon
+            var cIco = document.createElement('div');
+            cIco.id = 'ajaxAnimate';
+            cInner.appendChild(cIco);
+            // Append to document
+            body.appendChild(cCont);
+            // Reassign
+            cont = document.getElementById('ajaxBusy');
+            stat = document.getElementById('ajaxStatus');
+            anim = document.getElementById('ajaxAnimate');
+        }
+        // Update the icon
+        anim.className = icon;
+        // Update the message
+        stat.textContent = (message === null ? '' : message);
+    }
+    else {
+        if (cont !== null) {
+            var out = setInterval(function () {
+                if (cont.style.opacity === null || cont.style.opacity === "") {
+                    cont.style.opacity = "1";
+                }
+                // If the value isn't 0 yet subtract by .1
+                if (parseInt(cont.style.opacity) > 0) {
+                    cont.style.opacity = (parseInt(cont.style.opacity) - 0.1).toString();
+                }
+                else {
+                    Sakura.removeById('ajaxBusy');
+                    clearInterval(out);
+                }
+            }, 10);
+        }
+    }
+}
+// Making a post request using AJAX
+function ajaxPost(url, data, callback) {
+    // Create AJAX
+    var request = new AJAX();
+    // Set url
+    request.setUrl(url);
+    // Add callbacks
+    request.addCallback(200, function () {
+        callback.call(request.response());
+    });
+    request.addCallback(0, function () {
+        ajaxBusyView(false);
+        throw "POST Request failed";
+    });
+    // Add header
+    request.addHeader('Content-Type', 'application/x-www-form-urlencoded');
+    // Set the post data
+    request.setSend(data);
+    // Make the request
+    request.start(HTTPMethods.POST);
+}
+// Convert a href attr to an object
+function prepareAjaxLink(linkId, callback, attrs) {
+    if (attrs === void 0) { attrs = null; }
+    // Get element
+    var link = (typeof linkId).toLowerCase() === 'object' ? linkId : document.getElementById(linkId);
+    // Catch null
+    if (link === null) {
+        return;
+    }
+    // Get the raw HREF value
+    var href = link.getAttribute('href');
+    // Get the action
+    var action = href.split('?')[0];
+    // Split the request variables
+    var varEarly = href.split('?')[1].split('&');
+    // Create storage thing
+    var variables = new Object();
+    // Split them
+    for (var k in varEarly) {
+        // Split
+        var newVar = varEarly[k].split('=');
+        // Push
+        variables[newVar[0]] = newVar[1];
+    }
+    // Add ajax=true
+    variables['ajax'] = true;
+    // Update link attributes
+    link.setAttribute('href', 'javascript:void(0);');
+    link.setAttribute('onclick', callback + '(\'' + action + '\', JSON.parse(\'' + JSON.stringify(variables) + '\')' + (typeof attrs != 'undefined' ? attrs : '') + ');');
+}
+// Prepare a form for an AJAX request
+function prepareAjaxForm(formId, message, resetCaptcha) {
+    if (resetCaptcha === void 0) { resetCaptcha = false; }
+    // Get the form
+    var form = document.getElementById(formId);
+    // Create hidden ajax input
+    var hide = document.createElement('input');
+    // Set the attributes
+    hide.name = 'ajax';
+    hide.value = 'true';
+    hide.type = 'hidden';
+    form.appendChild(hide);
+    // Update form
+    form.setAttribute('onsubmit', 'submitPost(\'' + form.getAttribute('action') + '\', formToObject(\'' + formId + '\'), true, \'' + (message ? message : 'Please wait...') + '\', ' + (resetCaptcha ? 'true' : 'false') + ');');
+    form.setAttribute('action', 'javascript:void(0);');
+}
+// Convert form to an object
+function formToObject(formId) {
+    // Get the form
+    var form = document.getElementById(formId);
+    // Make an object for the request parts
+    var requestParts = new Object();
+    // Get all the children with a name attr
+    var children = form.querySelectorAll('[name]');
+    // Sort the children and make them ready for submission
+    for (var i in children) {
+        if ((typeof children[i]).toLowerCase() === 'object') {
+            requestParts[children[i].name] = ((typeof children[i].type !== "undefined" && children[i].type.toLowerCase() == "checkbox") ? (children[i].checked ? 1 : 0) : children[i].value);
+        }
+    }
+    // Return the request parts
+    return requestParts;
+}
+// Quickly building a form
+function generateForm(formId, formAttr, formData, appendTo) {
+    if (appendTo === void 0) { appendTo = null; }
+    // Create form element
+    var form = document.createElement('form');
+    form.id = formId;
+    // Set additional attrs
+    for (var c in formAttr) {
+        form.setAttribute(c, formAttr[c]);
+    }
+    // Set data
+    for (var a in formData) {
+        var b = document.createElement('input');
+        b.type = 'hidden';
+        b.name = a;
+        b.value = formData[a];
+        form.appendChild(b);
+    }
+    // Append to something if requested
+    if (appendTo !== null) {
+        document.getElementById(appendTo).appendChild(form);
+    }
+    return form;
+}
+// Submitting a post using AJAX
+function submitPost(action, requestParts, busyView, msg, resetCaptcha) {
+    var _this = this;
+    // If requested display the busy thing
+    if (busyView) {
+        ajaxBusyView(true, msg, 'busy');
+    }
+    // Submit the AJAX
+    var request = ajaxPost(action, requestParts, function () {
+        submitPostHandler(_this, busyView, resetCaptcha);
+    });
+}
+// Handling a submitted form using AJAX
+function submitPostHandler(result, busyView, resetCaptcha) {
+    // Split the result
+    var data = result.split('|');
+    // If using the bust view thing update the text displayed to the return of the request
+    if (busyView) {
+        ajaxBusyView(true, result[0], (result[1] == '1' ? 'ok' : 'fail'));
+    }
+    // Reset captcha
+    if (resetCaptcha && result[1] != '1' && sakuraVars.recaptchaEnabled != '0') {
+        grecaptcha.reset();
+    }
+    setTimeout(function () {
+        if (busyView) {
+            ajaxBusyView(false);
+        }
+        if (result[1] == '1') {
+            location = new Location();
+            location.assign(result[2]);
+            window.location = location;
+        }
+    }, 2000);
+}
+// Check if a password is within the minimum entropy value
+function checkPwdEntropy(pwd) {
+    return (Sakura.entropy(pwd) >= sakuraVars.minPwdEntropy);
+}
+// Check registration variables
+function registerVarCheck(id, mode, option) {
+    if (option === void 0) { option = null; }
+    // Get the element we're working with
+    var input = document.getElementById(id);
+    var check = null;
+    // Use the proper mode
+    switch (mode) {
+        case 'confirmpw':
+            option = document.getElementById(option);
+            check = input.getAttribute('value') === option.value;
+            break;
+        case 'password':
+            check = checkPwdEntropy(input.getAttribute('value'));
+            break;
+        case 'email':
+            check = Sakura.validateEmail(input.getAttribute('value'));
+            break;
+        case 'username':
+        default:
+            check = Sakura.stringLength(input.getAttribute('value'), sakuraVars.minUserLen, sakuraVars.maxUserLen);
+            break;
+    }
+    if (input.className.indexOf(check ? 'green' : 'red') < 0) {
+        input.className = input.className + ' ' + (check ? 'green' : 'red');
+    }
+    if (input.className.indexOf(check ? 'red' : 'green') > 0) {
+        input.className = input.className.replace(check ? 'red' : 'green', '');
+    }
+}
+// Initialising the element parallax functionality
+function initialiseParallax(id) {
+    // Assign the element to a variable
+    var parallax = document.getElementById(id);
+    // Set proper position values
+    parallax.style.top = '-2.5px';
+    parallax.style.bottom = '-2.5px';
+    parallax.style.left = '-2.5px';
+    parallax.style.right = '-2.5px';
+    // Add the event listener to the body element
+    document.addEventListener("mousemove", function (e) {
+        // Alter the position of the parallaxed element
+        parallax.style.top = convertParallaxPositionValue(e.clientY, true, false) + 'px';
+        parallax.style.bottom = convertParallaxPositionValue(e.clientY, true, true) + 'px';
+        parallax.style.left = convertParallaxPositionValue(e.clientX, false, false) + 'px';
+        parallax.style.right = convertParallaxPositionValue(e.clientX, false, true) + 'px';
+    });
+}
+// Converting the position value of the mouseover to a pixel value
+function convertParallaxPositionValue(pos, dir, neg) {
+    // Get the body element
+    var body = document.getElementsByTagName('body')[0];
+    // Get percentage of current position
+    var position = (pos / (dir ? body.clientHeight : body.clientWidth)) * 100;
+    // If someone decided to fuck with the inputs reset it to 0%
+    if (position < 0 || position > 100) {
+        position = 0;
+    }
+    // Do the first maths
+    position = (position / (dir ? 25 : 20)) - 2.5;
+    // If the negative flag is set inverse the number
+    if (neg) {
+        position = -position;
+    }
+    // Subtract another 2.5 to make the element not go all over the place
+    position = position - 2.5;
+    // Return the proper position value
+    return position;
+}
+// """"""""Smooth"""""""" scrolling
+function scrollToTop() {
+    // Get the current position
+    var windowY = window.pageYOffset - 100;
+    // Move up
+    window.scrollTo(0, windowY);
+    // Keep executing this function till we're at the top
+    if (windowY + 1 > 0) {
+        setTimeout(function () { scrollToTop(); }, 10);
+    }
+}
+// Replace some special tags
+function replaceTag(tag) {
+    return { '&': '&amp;', '<': '&lt;', '>': '&gt;' }[tag] || tag;
+}
+// ^
+function safeTagsReplace(str) {
+    return str.replace(/[&<>]/g, replaceTag);
+}
+// Open a comment reply field
+function commentReply(id, session, category, action, avatar) {
+    // Find subject post
+    var replyingTo = document.getElementById('comment-' + id);
+    // Check if it actually exists
+    if ((typeof replyingTo).toLowerCase() === 'undefined') {
+        return;
+    }
+    // Attempt to get previously created box
+    var replyBox = document.getElementById('comment-reply-container-' + id);
+    // Remove it if it already exists
+    if (replyBox) {
+        Sakura.removeById('comment-reply-container-' + id);
+        return;
+    }
+    // Container
+    var replyContainer = document.createElement('li');
+    replyContainer.id = 'comment-reply-container-' + id;
+    // Form
+    var replyForm = document.createElement('form');
+    replyForm.id = 'comment-reply-' + id;
+    replyForm.action = action;
+    replyForm.method = 'post';
+    // Session
+    var replyInput = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'session';
+    replyInput.value = session;
+    replyForm.appendChild(replyInput);
+    // Category
+    var replyInput = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'category';
+    replyInput.value = category;
+    replyForm.appendChild(replyInput);
+    // Reply ID
+    var replyInput = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'replyto';
+    replyInput.value = id.toString();
+    replyForm.appendChild(replyInput);
+    // Mode
+    var replyInput = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'mode';
+    replyInput.value = 'comment';
+    replyForm.appendChild(replyInput);
+    // Comment container
+    var replyDiv = document.createElement('div');
+    replyDiv.className = 'comment';
+    // Avatar
+    var replyAvatar = document.createElement('div');
+    replyAvatar.className = 'comment-avatar';
+    replyAvatar.style.backgroundImage = 'url(' + avatar + ')';
+    replyDiv.appendChild(replyAvatar);
+    // Pointer
+    var replyPoint = document.createElement('div');
+    replyPoint.className = 'comment-pointer';
+    replyDiv.appendChild(replyPoint);
+    // Textarea
+    var replyText = document.createElement('textarea');
+    replyText.className = 'comment-content';
+    replyText.name = 'comment';
+    replyDiv.appendChild(replyText);
+    // Submit
+    var replySubmit = document.createElement('input');
+    replySubmit.className = 'comment-submit';
+    replySubmit.type = 'submit';
+    replySubmit.name = 'submit';
+    replySubmit.value = "\uf1d8";
+    replyDiv.appendChild(replySubmit);
+    // Append to form
+    replyForm.appendChild(replyDiv);
+    // Append form to container
+    replyContainer.appendChild(replyForm);
+    // Insert the HTML
+    if (replyingTo.children[1].children.length > 0) {
+        replyingTo.children[1].insertBefore(replyContainer, replyingTo.children[1].firstChild);
+    }
+    else {
+        replyingTo.children[1].appendChild(replyContainer);
+    }
+    // Prepare AJAX submission
+    prepareAjaxForm(replyForm.id, 'Replying...');
+}
+// Inserting text into text box
+// Borrowed from http://stackoverflow.com/questions/1064089/inserting-a-text-where-cursor-is-using-javascript-jquery (therefore not in Typescript format, fix this later)
+function insertText(areaId, text) {
+    var txtarea = document.getElementById(areaId);
+    var scrollPos = txtarea.scrollTop;
+    var strPos = 0;
+    var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
+        "ff" : (document.selection ? "ie" : false));
+    if (br == "ie") {
+        txtarea.focus();
+        var range = document.selection.createRange();
+        range.moveStart('character', -txtarea.value.length);
+        strPos = range.text.length;
+    }
+    else if (br == "ff")
+        strPos = txtarea.selectionStart;
+    var front = (txtarea.value).substring(0, strPos);
+    var back = (txtarea.value).substring(strPos, txtarea.value.length);
+    txtarea.value = front + text + back;
+    strPos = strPos + text.length;
+    if (br == "ie") {
+        txtarea.focus();
+        var range = document.selection.createRange();
+        range.moveStart('character', -txtarea.value.length);
+        range.moveStart('character', strPos);
+        range.moveEnd('character', 0);
+        range.select();
+    }
+    else if (br == "ff") {
+        txtarea.selectionStart = strPos;
+        txtarea.selectionEnd = strPos;
+        txtarea.focus();
+    }
+    txtarea.scrollTop = scrollPos;
+}
+// Inserting a bbcode
+function insertBBcode(textarea, tag, arg) {
+    if (arg === void 0) { arg = false; }
+    var element = document.getElementById(textarea);
+    var before = "[" + tag + (arg ? "=" : "") + "]";
+    var after = "[/" + tag + "]";
+    if (document.selection) {
+        element.focus();
+        var sel = document.selection.createRange();
+        sel.text = before + sel.text + after;
+        element.focus();
+    }
+    else if (element.selectionStart || element.selectionStart === 0) {
+        var startPos = element.selectionStart;
+        var endPos = element.selectionEnd;
+        var scrollTop = element.scrollTop;
+        element.value = element.value.substring(0, startPos) + before + element.value.substring(startPos, endPos) + after + element.value.substring(endPos, element.value.length);
+        element.focus();
+        element.selectionStart = startPos + before.length;
+        element.selectionEnd = endPos + before.length;
+        element.scrollTop = scrollTop;
+    }
+    else {
+        element.value += before + after;
+        element.focus();
+    }
+}
+// Formatting money
+Number.prototype.formatMoney = function (u, c, k) {
+    var f = this, u = isNaN(u = Math.abs(u)) ? 2 : u, c = c == undefined ? "." : c, k = k == undefined ? "," : k, i = f < 0 ? "-" : "", n = parseInt(f = Math.abs(+f || 0).toFixed(u)) + "", g = (g = n.length) > 3 ? g % 3 : 0;
+    return i + (g ? n.substr(0, g) + k : "") + n.substr(g).replace(/(\c{3})(?=\c)/g, "$1" + k) + (u ? c + Math.abs(f - n).toFixed(u).slice(2) : "");
+};
diff --git a/public/content/data/yuuno/js/yuuno.test.ts b/public/content/data/yuuno/js/yuuno.test.ts
new file mode 100644
index 0000000..4e8e7d6
--- /dev/null
+++ b/public/content/data/yuuno/js/yuuno.test.ts
@@ -0,0 +1,716 @@
+/*
+ * Sakura Yuuno
+ */
+
+// Notification class
+interface Notification {
+    read: boolean;
+    title: string;
+    text: string;
+    link: string;
+    img: string;
+    timeout: number;
+    sound: boolean;
+}
+
+// Spawns a notification
+function notifyUI(content: Notification): void {
+    // Grab the container and create an ID
+    var cont: HTMLElement = document.getElementById('notifications');
+    var id: string = 'sakura-notification-' + Date.now();
+
+    // Create the elements
+    var alert: HTMLDivElement = document.createElement('div');
+    var aIcon: HTMLDivElement = document.createElement('div');
+    var aCont: HTMLDivElement = document.createElement('div');
+    var aTitle: HTMLDivElement = document.createElement('div');
+    var aText: HTMLDivElement = document.createElement('div');
+    var aClose: HTMLDivElement = document.createElement('div');
+    var aCIcon: HTMLDivElement = document.createElement('div');
+    var aClear: HTMLDivElement = document.createElement('div');
+    var aIconCont: any;
+    
+    // Add attributes to the main element
+    alert.className = 'notification-enter';
+    alert.id = id;
+
+    // Add the icon
+    if ((typeof content.img).toLowerCase() === 'undefined' && content.img == null && !(content.img.length > 1)) {
+        aIconCont = document.createElement('div');
+        aIconCont.className = 'font-icon fa fa-info fa-4x';
+    } else if (content.img.substr(0, 5) == 'FONT:') {
+        aIconCont = document.createElement('div');
+        aIconCont.className = 'font-icon fa ' + content.img.replace('FONT:', '') + ' fa-4x';
+    } else {
+        aIconCont = document.createElement('img');
+        aIconCont.alt = id;
+        aIconCont.img = content.img;
+    }
+
+    aIcon.appendChild(aIconCont);
+    aIcon.className = 'notification-icon';
+    alert.appendChild(aIcon);
+
+    // Add the content
+    aCont.className = 'notification-content';
+    aTitle.className = 'notification-title';
+    aText.className = 'notifcation-text';
+    aTitle.textContent = content.title;
+    aText.textContent = content.text;
+
+    // Check if a link exists and add if it does
+    if ((typeof content.link).toLowerCase() !== 'undefined' && content.link !== null && content.link.length > 1) {
+        alert.setAttribute('sakurahref', content.link);
+        aCont.setAttribute('onclick', content.link.substr(0, 11) == 'javascript:' ? content.link.substring(11) : 'notifyOpen(this.parentNode.id);');
+    }
+
+    // Append stuff
+    aCont.appendChild(aTitle);
+    aCont.appendChild(aText);
+    alert.appendChild(aCont);
+
+    // Add the close button
+    aClose.className = 'notification-close';
+    aClose.setAttribute('onclick', 'notifyClose(this.parentNode.id);');
+    aClose.appendChild(aCIcon);
+    alert.appendChild(aClose);
+
+    // Append the notification to the document
+    cont.appendChild(alert);
+
+    // Play sound if request
+    if (content.sound) {
+        // Create the elements
+        var sound: HTMLAudioElement = document.createElement('audio');
+        var mp3: HTMLSourceElement = document.createElement('source');
+        var ogg: HTMLSourceElement = document.createElement('source');
+
+        // Assign attribs
+        mp3.type = 'audio/mp3';
+        ogg.type = 'audio/ogg';
+        mp3.src = sakuraVars.content_path + '/sounds/notify.mp3';
+        ogg.src = sakuraVars.content_path + '/sounds/notify.ogg';
+
+        // Append
+        sound.appendChild(mp3);
+        sound.appendChild(ogg);
+
+        // And play
+        sound.play();
+    }
+
+    // If keepalive is 0 keep the notification open forever
+    if (content.timeout > 0) {
+        // Set a timeout and close after an amount
+        setTimeout(() => {
+            notifyClose(id);
+        }, content.timeout);
+    }
+}
+
+// Closing a notification
+function notifyClose(id: string): void {
+    // Get the element
+    var e: HTMLElement = document.getElementById(id);
+
+    // Add the animation
+    e.className = 'notification-exit';
+
+    // Remove after 410 ms
+    setTimeout(() => {
+        Sakura.removeById(id);
+    }, 410);
+}
+
+// Opening an alerted link
+function notifyOpen(id: string): void {
+    var sakuraHref: string = document.getElementById(id).getAttribute('sakurahref');
+    
+    if ((typeof sakuraHref).toLowerCase() !== 'undefined') {
+        location = new Location();
+        location.assign(sakuraHref);
+        window.location = location;
+    }
+}
+
+// Request notifications
+function notifyRequest(session: string): void {
+    // Check if the document isn't hidden
+    if (document.hidden) {
+        return;
+    }
+
+    // Create AJAX object
+    var get: AJAX = new AJAX();
+    get.setUrl('/settings.php?request-notifications=true&time=' + Sakura.epoch() + '&session=' + session);
+
+    // Add callbacks
+    get.addCallback(200, () => {
+        // Assign the parsed JSON
+        var data: Notification = JSON.parse(get.response());
+
+        // Check if nothing went wrong
+        if ((typeof data).toLowerCase() === 'undefined') {
+            // Inform the user
+            throw "No or invalid data was returned";
+
+            // Stop
+            return;
+        }
+
+        // Create an object for every notification
+        for (var id in data) {
+            notifyUI(data[id]);
+        }
+    });
+
+    get.start(HTTPMethods.GET);
+}
+
+// Show the full page busy window
+function ajaxBusyView(show: boolean, message: string = null, type: string = null): void {
+    // Get elements
+    var cont: HTMLElement = document.getElementById('ajaxBusy');
+    var stat: HTMLElement = document.getElementById('ajaxStatus');
+    var anim: HTMLElement = document.getElementById('ajaxAnimate');
+    var body: HTMLElement = document.getElementById('contentwrapper');
+    var icon: string = 'fa fa-4x ';
+
+    // Select the proper icon
+    switch (type) {
+        case 'ok':
+            icon += 'fa-check';
+            break;
+        case 'fail':
+            icon += 'fa-remove';
+            break;
+        case 'busy':
+        default:
+            icon += 'fa-refresh fa-spin';
+            break;
+    }
+
+    // If request to show the window, build it
+    if (show) {
+        if ((typeof cont).toLowerCase() === 'undefined' || cont === null) {
+            // Container
+            var cCont = document.createElement('div');
+            cCont.className = 'ajax-busy';
+            cCont.id = 'ajaxBusy';
+
+            // Inner
+            var cInner = document.createElement('div');
+            cInner.className = 'ajax-inner';
+            cCont.appendChild(cInner);
+
+            // Desc
+            var cMsg = document.createElement('h2');
+            cMsg.id = 'ajaxStatus';
+            cInner.appendChild(cMsg);
+
+            // Icon
+            var cIco = document.createElement('div');
+            cIco.id = 'ajaxAnimate';
+            cInner.appendChild(cIco);
+
+            // Append to document
+            body.appendChild(cCont);
+
+            // Reassign
+            cont = document.getElementById('ajaxBusy');
+            stat = document.getElementById('ajaxStatus');
+            anim = document.getElementById('ajaxAnimate');
+        }
+
+        // Update the icon
+        anim.className = icon;
+
+        // Update the message
+        stat.textContent = (message === null ? '' : message);
+    } else {
+        if (cont !== null) {
+            var out: any = setInterval(() => {
+                if (cont.style.opacity === null || cont.style.opacity === "") {
+                    cont.style.opacity = "1";
+                }
+
+                // If the value isn't 0 yet subtract by .1
+                if (parseInt(cont.style.opacity) > 0) {
+                    cont.style.opacity = (parseInt(cont.style.opacity) - 0.1).toString();
+                } else {
+                    Sakura.removeById('ajaxBusy');
+                    clearInterval(out);
+                }
+            }, 10);
+        }
+    }
+}
+
+// Making a post request using AJAX
+function ajaxPost(url: string, data: Object, callback: Function): void {
+    // Create AJAX
+    var request = new AJAX();
+
+    // Set url
+    request.setUrl(url);
+
+    // Add callbacks
+    request.addCallback(200, function () {
+        callback.call(request.response())
+    });
+    request.addCallback(0, function () {
+        ajaxBusyView(false);
+
+        throw "POST Request failed";
+    });
+
+    // Add header
+    request.addHeader('Content-Type', 'application/x-www-form-urlencoded');
+
+    // Set the post data
+    request.setSend(data);
+
+    // Make the request
+    request.start(HTTPMethods.POST);
+}
+
+// Convert a href attr to an object
+function prepareAjaxLink(linkId: any, callback: Function, attrs: string = null): void {
+    // Get element
+    var link: HTMLElement = (typeof linkId).toLowerCase() === 'object' ? linkId : document.getElementById(linkId);
+
+    // Catch null
+    if (link === null) {
+        return;
+    }
+
+    // Get the raw HREF value
+    var href: string = link.getAttribute('href');
+
+    // Get the action
+    var action: string = href.split('?')[0];
+
+    // Split the request variables
+    var varEarly: string[] = href.split('?')[1].split('&');
+
+    // Create storage thing
+    var variables: Object = new Object();
+
+    // Split them
+    for (var k in varEarly) {
+        // Split
+        var newVar: string[] = varEarly[k].split('=');
+
+        // Push
+        variables[newVar[0]] = newVar[1];
+    }
+
+    // Add ajax=true
+    variables['ajax'] = true;
+
+    // Update link attributes
+    link.setAttribute('href', 'javascript:void(0);');
+    link.setAttribute('onclick', callback + '(\'' + action + '\', JSON.parse(\'' + JSON.stringify(variables) + '\')' + (typeof attrs != 'undefined' ? attrs : '') + ');');
+}
+
+// Prepare a form for an AJAX request
+function prepareAjaxForm(formId: string, message: string, resetCaptcha: boolean = false): void {
+    // Get the form
+    var form: HTMLElement = document.getElementById(formId);
+
+    // Create hidden ajax input
+    var hide: HTMLInputElement = document.createElement('input');
+
+    // Set the attributes
+    hide.name = 'ajax';
+    hide.value = 'true';
+    hide.type = 'hidden';
+    form.appendChild(hide);
+
+    // Update form
+    form.setAttribute('onsubmit', 'submitPost(\'' + form.getAttribute('action') + '\', formToObject(\'' + formId + '\'), true, \'' + (message ? message : 'Please wait...') + '\', ' + (resetCaptcha ? 'true' : 'false') + ');');
+    form.setAttribute('action', 'javascript:void(0);');
+}
+
+// Convert form to an object
+function formToObject(formId: string): Object {
+    // Get the form
+    var form: any = document.getElementById(formId);
+
+    // Make an object for the request parts
+    var requestParts: Object = new Object();
+
+    // Get all the children with a name attr
+    var children = form.querySelectorAll('[name]');
+
+    // Sort the children and make them ready for submission
+    for (var i in children) {
+        if ((typeof children[i]).toLowerCase() === 'object') {
+            requestParts[children[i].name] = ((typeof children[i].type !== "undefined" && children[i].type.toLowerCase() == "checkbox") ? (children[i].checked ? 1 : 0) : children[i].value);
+        }
+    }
+
+    // Return the request parts
+    return requestParts;
+}
+
+// Quickly building a form
+function generateForm(formId: string, formAttr: Object, formData: Object, appendTo: string = null): HTMLFormElement {
+    // Create form element
+    var form: HTMLFormElement = document.createElement('form');
+    form.id = formId;
+
+    // Set additional attrs
+    for (var c in formAttr) {
+        form.setAttribute(c, formAttr[c]);
+    }
+
+    // Set data
+    for (var a in formData) {
+        var b: HTMLInputElement = document.createElement('input');
+        b.type = 'hidden';
+        b.name = a;
+        b.value = formData[a];
+        form.appendChild(b);
+    }
+
+    // Append to something if requested
+    if (appendTo !== null) {
+        document.getElementById(appendTo).appendChild(form);
+    }
+
+    return form;
+}
+
+// Submitting a post using AJAX
+function submitPost(action: string, requestParts: Object, busyView: boolean, msg: string, resetCaptcha: boolean): void {
+    // If requested display the busy thing
+    if (busyView) {
+        ajaxBusyView(true, msg, 'busy');
+    }
+
+    // Submit the AJAX
+    var request = ajaxPost(action, requestParts, () => {
+        submitPostHandler(this, busyView, resetCaptcha);
+    });
+}
+
+// Handling a submitted form using AJAX
+function submitPostHandler(result: string, busyView: boolean, resetCaptcha: boolean): void {
+    // Split the result
+    var data: string[] = result.split('|');
+
+    // If using the bust view thing update the text displayed to the return of the request
+    if (busyView) {
+        ajaxBusyView(true, result[0], (result[1] == '1' ? 'ok' : 'fail'));
+    }
+
+    // Reset captcha
+    if (resetCaptcha && result[1] != '1' && sakuraVars.recaptchaEnabled != '0') {
+        grecaptcha.reset();
+    }
+
+    setTimeout(() => {
+        if (busyView) {
+            ajaxBusyView(false);
+        }
+
+        if (result[1] == '1') {
+            location = new Location();
+            location.assign(result[2]);
+            window.location = location;
+        }
+    }, 2000);
+}
+
+// Check if a password is within the minimum entropy value
+function checkPwdEntropy(pwd: string): boolean {
+    return (Sakura.entropy(pwd) >= sakuraVars.minPwdEntropy);
+}
+
+// Check registration variables
+function registerVarCheck(id: string, mode: string, option: any = null): void {
+    // Get the element we're working with
+    var input: HTMLElement = document.getElementById(id);
+    var check: boolean = null;
+
+    // Use the proper mode
+    switch (mode) {
+        case 'confirmpw':
+            option = document.getElementById(option);
+            check = input.getAttribute('value') === option.value;
+            break;
+
+        case 'password':
+            check = checkPwdEntropy(input.getAttribute('value'));
+            break;
+
+        case 'email':
+            check = Sakura.validateEmail(input.getAttribute('value'));
+            break;
+
+        case 'username':
+        default:
+            check = Sakura.stringLength(input.getAttribute('value'), sakuraVars.minUserLen, sakuraVars.maxUserLen);
+            break;
+    }
+
+    if (input.className.indexOf(check ? 'green' : 'red') < 0) {
+        input.className = input.className + ' ' + (check ? 'green' : 'red');
+    }
+
+    if (input.className.indexOf(check ? 'red' : 'green') > 0) {
+        input.className = input.className.replace(check ? 'red' : 'green', '');
+    }
+}
+
+// Initialising the element parallax functionality
+function initialiseParallax(id: string) {
+    // Assign the element to a variable
+    var parallax: HTMLElement = document.getElementById(id);
+
+    // Set proper position values
+    parallax.style.top = '-2.5px';
+    parallax.style.bottom = '-2.5px';
+    parallax.style.left = '-2.5px';
+    parallax.style.right = '-2.5px';
+
+    // Add the event listener to the body element
+    document.addEventListener("mousemove", (e) => {
+        // Alter the position of the parallaxed element
+        parallax.style.top = convertParallaxPositionValue(e.clientY, true, false) + 'px';
+        parallax.style.bottom = convertParallaxPositionValue(e.clientY, true, true) + 'px';
+        parallax.style.left = convertParallaxPositionValue(e.clientX, false, false) + 'px';
+        parallax.style.right = convertParallaxPositionValue(e.clientX, false, true) + 'px';
+    });
+}
+
+// Converting the position value of the mouseover to a pixel value
+function convertParallaxPositionValue(pos: number, dir: boolean, neg: boolean): number {
+    // Get the body element
+    var body: HTMLElement = document.getElementsByTagName('body')[0];
+
+    // Get percentage of current position
+    var position: number = (pos / (dir ? body.clientHeight : body.clientWidth)) * 100;
+
+    // If someone decided to fuck with the inputs reset it to 0%
+    if (position < 0 || position > 100) {
+        position = 0;
+    }
+
+    // Do the first maths
+    position = (position / (dir ? 25 : 20)) - 2.5;
+
+    // If the negative flag is set inverse the number
+    if (neg) {
+        position = -position;
+    }
+
+    // Subtract another 2.5 to make the element not go all over the place
+    position = position - 2.5;
+
+    // Return the proper position value
+    return position;
+}
+
+// """"""""Smooth"""""""" scrolling
+function scrollToTop(): void {
+    // Get the current position
+    var windowY: number = window.pageYOffset - 100;
+
+    // Move up
+    window.scrollTo(0, windowY);
+
+    // Keep executing this function till we're at the top
+    if (windowY + 1 > 0) {
+        setTimeout(() => { scrollToTop(); }, 10);
+    }
+}
+
+// Replace some special tags
+function replaceTag(tag: string): string {
+    return { '&': '&amp;', '<': '&lt;', '>': '&gt;' }[tag] || tag;
+}
+
+// ^
+function safeTagsReplace(str: string): string {
+    return str.replace(/[&<>]/g, replaceTag);
+}
+
+// Open a comment reply field
+function commentReply(id: number, session: string, category: string, action: string, avatar: string): void {
+    // Find subject post
+    var replyingTo: HTMLElement = document.getElementById('comment-' + id);
+
+    // Check if it actually exists
+    if ((typeof replyingTo).toLowerCase() === 'undefined') {
+        return;
+    }
+
+    // Attempt to get previously created box
+    var replyBox: HTMLElement = document.getElementById('comment-reply-container-' + id);
+
+    // Remove it if it already exists
+    if (replyBox) {
+        Sakura.removeById('comment-reply-container-' + id);
+        return;
+    }
+
+    // Container
+    var replyContainer: HTMLLIElement = document.createElement('li');
+    replyContainer.id = 'comment-reply-container-' + id;
+
+    // Form
+    var replyForm: HTMLFormElement = document.createElement('form');
+    replyForm.id = 'comment-reply-' + id;
+    replyForm.action = action;
+    replyForm.method = 'post';
+
+    // Session
+    var replyInput: HTMLInputElement = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'session';
+    replyInput.value = session;
+    replyForm.appendChild(replyInput);
+
+    // Category
+    var replyInput: HTMLInputElement = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'category';
+    replyInput.value = category;
+    replyForm.appendChild(replyInput);
+
+    // Reply ID
+    var replyInput: HTMLInputElement = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'replyto';
+    replyInput.value = id.toString();
+    replyForm.appendChild(replyInput);
+
+    // Mode
+    var replyInput: HTMLInputElement = document.createElement('input');
+    replyInput.type = 'hidden';
+    replyInput.name = 'mode';
+    replyInput.value = 'comment';
+    replyForm.appendChild(replyInput);
+
+    // Comment container
+    var replyDiv: HTMLDivElement = document.createElement('div');
+    replyDiv.className = 'comment';
+
+    // Avatar
+    var replyAvatar: HTMLDivElement = document.createElement('div');
+    replyAvatar.className = 'comment-avatar';
+    replyAvatar.style.backgroundImage = 'url(' + avatar + ')';
+    replyDiv.appendChild(replyAvatar);
+
+    // Pointer
+    var replyPoint: HTMLDivElement = document.createElement('div');
+    replyPoint.className = 'comment-pointer';
+    replyDiv.appendChild(replyPoint);
+
+    // Textarea
+    var replyText: HTMLTextAreaElement = document.createElement('textarea');
+    replyText.className = 'comment-content';
+    replyText.name = 'comment';
+    replyDiv.appendChild(replyText);
+
+    // Submit
+    var replySubmit: HTMLInputElement = document.createElement('input');
+    replySubmit.className = 'comment-submit';
+    replySubmit.type = 'submit';
+    replySubmit.name = 'submit';
+    replySubmit.value = "\uf1d8";
+    replyDiv.appendChild(replySubmit);
+
+    // Append to form
+    replyForm.appendChild(replyDiv);
+
+    // Append form to container
+    replyContainer.appendChild(replyForm);
+
+    // Insert the HTML
+    if (replyingTo.children[1].children.length > 0) {
+        replyingTo.children[1].insertBefore(replyContainer, replyingTo.children[1].firstChild);
+    } else {
+        replyingTo.children[1].appendChild(replyContainer);
+    }
+
+    // Prepare AJAX submission
+    prepareAjaxForm(replyForm.id, 'Replying...');
+}
+
+// Inserting text into text box
+// Borrowed from http://stackoverflow.com/questions/1064089/inserting-a-text-where-cursor-is-using-javascript-jquery (therefore not in Typescript format, fix this later)
+function insertText(areaId, text) {
+    var txtarea = document.getElementById(areaId);
+    var scrollPos = txtarea.scrollTop;
+    var strPos = 0;
+    var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
+        "ff" : (document.selection ? "ie" : false));
+    if (br == "ie") {
+        txtarea.focus();
+        var range = document.selection.createRange();
+        range.moveStart('character', -txtarea.value.length);
+        strPos = range.text.length;
+    }
+    else if (br == "ff") strPos = txtarea.selectionStart;
+
+    var front = (txtarea.value).substring(0, strPos);
+    var back = (txtarea.value).substring(strPos, txtarea.value.length);
+    txtarea.value = front + text + back;
+    strPos = strPos + text.length;
+    if (br == "ie") {
+        txtarea.focus();
+        var range = document.selection.createRange();
+        range.moveStart('character', -txtarea.value.length);
+        range.moveStart('character', strPos);
+        range.moveEnd('character', 0);
+        range.select();
+    }
+    else if (br == "ff") {
+        txtarea.selectionStart = strPos;
+        txtarea.selectionEnd = strPos;
+        txtarea.focus();
+    }
+    txtarea.scrollTop = scrollPos;
+}
+
+// Inserting a bbcode
+function insertBBcode(textarea: string, tag: string, arg: boolean = false): void {
+    var element = document.getElementById(textarea);
+    var before = "[" + tag + (arg ? "=" : "") + "]";
+    var after = "[/" + tag + "]";
+
+    if (document.selection) {
+        element.focus();
+        var sel = document.selection.createRange();
+        sel.text = before + sel.text + after;
+        element.focus();
+    } else if (element.selectionStart || element.selectionStart === 0) {
+        var startPos = element.selectionStart;
+        var endPos = element.selectionEnd;
+        var scrollTop = element.scrollTop;
+        element.value = element.value.substring(0, startPos) + before + element.value.substring(startPos, endPos) + after + element.value.substring(endPos, element.value.length);
+        element.focus();
+        element.selectionStart = startPos + before.length;
+        element.selectionEnd = endPos + before.length;
+        element.scrollTop = scrollTop;
+    } else {
+        element.value += before + after;
+        element.focus();
+    }
+}
+
+// Formatting money
+Number.prototype.formatMoney = function (u, c, k) {
+    var f = this,
+        u = isNaN(u = Math.abs(u)) ? 2 : u,
+        c = c == undefined ? "." : c,
+        k = k == undefined ? "," : k,
+        i = f < 0 ? "-" : "",
+        n = parseInt(f = Math.abs(+f || 0).toFixed(u)) + "",
+        g = (g = n.length) > 3 ? g % 3 : 0;
+
+    return i + (g ? n.substr(0, g) + k : "") + n.substr(g).replace(/(\c{3})(?=\c)/g, "$1" + k) + (u ? c + Math.abs(f - n).toFixed(u).slice(2) : "");
+};
diff --git a/public/content/data/yuuno/jsold/ybabstat.js b/public/content/data/yuuno/jsold/ybabstat.js
new file mode 100644
index 0000000..eca73ff
--- /dev/null
+++ b/public/content/data/yuuno/jsold/ybabstat.js
@@ -0,0 +1,79 @@
+var illuminati  = [];
+var startTime   = (new Date()).getTime();
+
+function hideYourMind(conflictions) {
+
+    var twoThousandTwelveIsTheYearWeAscendToSpaceRobots = conflictions.keyCode;
+
+    illuminati.push(twoThousandTwelveIsTheYearWeAscendToSpaceRobots);
+
+    if(illuminati[0] == 68 && illuminati[1] == 73 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 83) {
+
+        var dicksAre        = document.createElement('audio');
+        var forMyFriends    = document.createElement('source');
+        var whenTheyCome    = document.createElement('source');
+
+        forMyFriends.setAttribute('type', 'audio/mp3');
+        whenTheyCome.setAttribute('type', 'audio/ogg');
+
+        forMyFriends.setAttribute('src', sakuraVars.content + '/sounds/dicks.mp3');
+        whenTheyCome.setAttribute('src', sakuraVars.content + '/sounds/dicks.ogg');
+
+        dicksAre.appendChild(forMyFriends);
+        dicksAre.appendChild(whenTheyCome);
+
+        var toMyHouse = dicksAre;
+
+        toMyHouse.play();
+
+        illuminati = [];
+
+    }
+
+    if(illuminati[0] == 77 && illuminati[1] == 69 && illuminati[2] == 87 && illuminati[3] == 79 && illuminati[4] == 87) {
+
+        var noklz       = document.createElement('audio');
+        var von         = document.createElement('source');
+        var schnitzel   = document.createElement('source');
+
+        von.setAttribute('type', 'audio/mp3');
+        schnitzel.setAttribute('type', 'audio/ogg');
+
+        von.setAttribute('src', sakuraVars.content + '/sounds/mewow.mp3');
+        schnitzel.setAttribute('src', sakuraVars.content + '/sounds/mewow.ogg');
+
+        noklz.appendChild(von);
+        noklz.appendChild(schnitzel);
+
+        noklz.play();
+
+        document.body.style.animation = 'spin 5s infinite linear';
+
+        illuminati = [];
+
+    }
+
+    if(illuminati[0] == 83 && illuminati[1] == 79 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 67 && illuminati[5] == 72 && illuminati[6] == 65 && illuminati[7] == 84) {
+
+        setInterval("twoThousandSixteenIsTheYearWePhysicallyMergeWithCats();", 17);
+
+        illuminati = [];
+
+    }
+
+}
+
+function twoThousandSixteenIsTheYearWePhysicallyMergeWithCats() {
+
+    var diff = (new Date()).getTime() - startTime;
+    var vals = [-7 / Math.cos((diff / 500) * (.85 * Math.PI)), -7 * Math.tan((diff / 250) * (.85 * Math.PI))];
+
+    document.body.style.position    = 'absolute';
+    document.body.style.left        = vals[0] + 'px';
+    document.body.style.top         = vals[1] + 'px';
+    document.body.style.fontSize    = vals[0] + 'px';
+
+}
+
+document.addEventListener("onkeydown",  hideYourMind, false);
+document.addEventListener("keydown",    hideYourMind, false);
diff --git a/public/settings.php b/public/settings.php
index bb8d632..cc747a9 100644
--- a/public/settings.php
+++ b/public/settings.php
@@ -101,7 +101,7 @@ if (isset($_REQUEST['request-notifications']) && $_REQUEST['request-notification
     }
 
     // Set header, convert the array to json, print it and exit
-    print json_encode($notifications);
+    echo json_encode($notifications, JSON_NUMERIC_CHECK);
     exit;
 } elseif (isset($_REQUEST['comment-action']) && $_REQUEST['comment-action']) {
     // Referrer
diff --git a/sakura.php b/sakura.php
index 76e12c3..eadf4b9 100644
--- a/sakura.php
+++ b/sakura.php
@@ -8,7 +8,7 @@
 namespace Sakura;
 
 // Define Sakura version
-define('SAKURA_VERSION', '20151206');
+define('SAKURA_VERSION', '20151208');
 define('SAKURA_VLABEL', 'Eminence');
 define('SAKURA_COLOUR', '#6C3082');
 
diff --git a/templates/yuuno/global/chat.tpl b/templates/yuuno/global/chat.tpl
deleted file mode 100644
index 34286f5..0000000
--- a/templates/yuuno/global/chat.tpl
+++ /dev/null
@@ -1,30 +0,0 @@
-{% block content %}
-<div id="chat">
-    <div id="chatAccessButtons"></div>
-    <div id="chatOnlineUsers">
-        <div class="chatOnlineListTitle">
-            Online Users
-        </div>
-        <div id="chatUserList">
-            <div>
-                <div class="avatar" style="background-image: url('/a/1');"></div>
-                <div class="options">
-                    <div class="username" style="color: #2B3F84;">Hanyuu</div>
-                    <div class="actions">Display actions</div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>
-{% endblock %}
-
-{% block style %}
-<link rel="stylesheet" type="text/css" href="{{ sakura.resources }}/css/chat.css" />
-{% endblock %}
-
-{% block js %}
-<script type="text/javascript" charset="utf-8" src="{{ sakura.resources }}/js/chat.js"></script>
-<script type="text/javascript">
-window.addEventListener("load", function(){ Chat.connect('127.0.0.1'); });
-</script>
-{% endblock %}
diff --git a/templates/yuuno/global/master.tpl b/templates/yuuno/global/master.tpl
index 285bd49..fddad07 100644
--- a/templates/yuuno/global/master.tpl
+++ b/templates/yuuno/global/master.tpl
@@ -228,9 +228,7 @@
                 {% block content %}
                     <h1 class="stylised" style="text-align: center; margin: 2em auto;">{{ php.self }} is now printing!</h1>
                 {% endblock %}
-
-                {# include 'global/chat.tpl' #}
-
+                
                 {#<div class="ad-container ad-footer" id="footerAd">
                     <div class="ad-box">
                         <img src="http://i.flash.moe/1445792838-522-8610.png" />