diff --git a/build.js b/build.js
index bfe729e..2e9bd7b 100644
--- a/build.js
+++ b/build.js
@@ -14,7 +14,8 @@ const exec = require('util').promisify(require('child_process').exec);
         debug: isDebug,
         swc: {
             es: 'es5',
-            jsx: 'er',
+            jsx: '$element',
+            jsxf: '$fragment',
         },
         housekeep: [
             pathJoin(__dirname, 'public', 'assets'),
diff --git a/public/polyfill/symbol.js b/public/polyfill/symbol.js
new file mode 100644
index 0000000..fb10000
--- /dev/null
+++ b/public/polyfill/symbol.js
@@ -0,0 +1,11 @@
+/*!
+ * Symbol-ES6 v0.1.2
+ * ES6 Symbol polyfill in pure ES5.
+ *
+ * @license Copyright (c) 2017-2018 Rousan Ali, MIT License
+ *
+ * Codebase: https://github.com/rousan/symbol-es6
+ * Date: 28th Jan, 2018
+ */
+
+!function(global,factory){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=factory(global):factory(global)}("undefined"!=typeof window?window:global,function(global){"use strict";var defineProperty=Object.defineProperty,defineProperties=Object.defineProperties,symbolHiddenCounter=0,globalSymbolRegistry=[],slice=Array.prototype.slice,ES6="object"==typeof global.ES6?global.ES6:global.ES6={},isArray=Array.isArray,objectToString=Object.prototype.toString,push=Array.prototype.push,emptyFunction=function(){},simpleFunction=function(arg){return arg},isCallable=function(fn){return"function"==typeof fn},Iterator=function(){},ArrayIterator=function(array,flag){this._array=array,this._flag=flag,this._nextIndex=0},StringIterator=function(string,flag){this._string=string,this._flag=flag,this._nextIndex=0},isObject=function(value){return null!==value&&("object"==typeof value||"function"==typeof value)},setupSymbolInternals=function(symbol,desc){return defineProperties(symbol,{_description:{value:desc},_isSymbol:{value:!0},_id:{value:symbolHiddenCounter++}}),symbol},appendArray=function(array1,array2){if("number"==typeof array1.length&&array1.length>=0&&"number"==typeof array2.length&&array2.length>=0){var length1=Math.floor(array1.length),length2=Math.floor(array2.length),i=0;for(array1.length=length1+length2;i<length2;++i)array2.hasOwnProperty(i)&&(array1[length1+i]=array2[i])}},simpleInheritance=function(child,parent){if("function"!=typeof child||"function"!=typeof parent)throw new TypeError("Child and Parent must be function type");child.prototype=Object.create(parent.prototype),child.prototype.constructor=child},Symbol=function Symbol(desc){if(desc=void 0===desc?"":String(desc),this instanceof Symbol)throw new TypeError("Symbol is not a constructor");return setupSymbolInternals(Object.create(Symbol.prototype),desc)};defineProperties(Symbol,{for:{value:function(key){key=String(key);for(var record,registryLength=globalSymbolRegistry.length,i=0;i<registryLength;++i)if((record=globalSymbolRegistry[i]).key===key)return record.symbol;return record={key:key,symbol:Symbol(key)},globalSymbolRegistry.push(record),record.symbol},writable:!0,configurable:!0},keyFor:{value:function(symbol){if(!ES6.isSymbol(symbol))throw new TypeError(String(symbol)+" is not a symbol");for(var record,registryLength=globalSymbolRegistry.length,i=0;i<registryLength;++i)if((record=globalSymbolRegistry[i]).symbol===symbol)return record.key},writable:!0,configurable:!0},hasInstance:{value:Symbol("Symbol.hasInstance")},isConcatSpreadable:{value:Symbol("Symbol.isConcatSpreadable")},iterator:{value:Symbol("Symbol.iterator")},toStringTag:{value:Symbol("Symbol.toStringTag")}}),Symbol.prototype.toString=function(){return"@@_____"+this._id+"_____"},Symbol.prototype.valueOf=function(){return this},defineProperty(Iterator.prototype,Symbol.iterator.toString(),{value:function(){return this},writable:!0,configurable:!0}),simpleInheritance(ArrayIterator,Iterator),simpleInheritance(StringIterator,Iterator),defineProperty(ArrayIterator.prototype,Symbol.toStringTag.toString(),{value:"Array Iterator",configurable:!0}),defineProperty(StringIterator.prototype,Symbol.toStringTag.toString(),{value:"String Iterator",configurable:!0}),ArrayIterator.prototype.next=function(){if(!(this instanceof ArrayIterator))throw new TypeError("Method Array Iterator.prototype.next called on incompatible receiver "+String(this));var nextValue;return-1===this._nextIndex?{done:!0,value:void 0}:"number"==typeof this._array.length&&this._array.length>=0&&this._nextIndex<Math.floor(this._array.length)?(1===this._flag?nextValue=[this._nextIndex,this._array[this._nextIndex]]:2===this._flag?nextValue=this._array[this._nextIndex]:3===this._flag&&(nextValue=this._nextIndex),this._nextIndex++,{done:!1,value:nextValue}):(this._nextIndex=-1,{done:!0,value:void 0})},StringIterator.prototype.next=function(){if(!(this instanceof StringIterator))throw new TypeError("Method String Iterator.prototype.next called on incompatible receiver "+String(this));var nextValue,stringObject=new String(this._string);return-1===this._nextIndex?{done:!0,value:void 0}:this._nextIndex<stringObject.length?(nextValue=stringObject[this._nextIndex],this._nextIndex++,{done:!1,value:nextValue}):(this._nextIndex=-1,{done:!0,value:void 0})};var SpreadOperatorImpl=function(target,thisArg){this._target=target,this._values=[],this._thisArg=thisArg};SpreadOperatorImpl.prototype.spread=function(){var self=this;return slice.call(arguments).forEach(function(iterable){ES6.forOf(iterable,function(value){self._values.push(value)})}),self},SpreadOperatorImpl.prototype.add=function(){var self=this;return slice.call(arguments).forEach(function(value){self._values.push(value)}),self},SpreadOperatorImpl.prototype.call=function(thisArg){if("function"!=typeof this._target)throw new TypeError("Target is not a function");return thisArg=arguments.length<=0?this._thisArg:thisArg,this._target.apply(thisArg,this._values)},SpreadOperatorImpl.prototype.new=function(){if("function"!=typeof this._target)throw new TypeError("Target is not a constructor");var temp,returnValue;return temp=Object.create(this._target.prototype),returnValue=this._target.apply(temp,this._values),isObject(returnValue)?returnValue:temp},SpreadOperatorImpl.prototype.array=function(){if(!isArray(this._target))throw new TypeError("Target is not a array");return push.apply(this._target,this._values),this._target};return defineProperties(ES6,{isSymbol:{value:function(symbol){return symbol instanceof Symbol&&function(symbol){return!0===symbol._isSymbol&&"number"==typeof symbol._id&&"string"==typeof symbol._description}(symbol)},writable:!0,configurable:!0},instanceOf:{value:function(object,constructor){if(!isObject(constructor))throw new TypeError("Right-hand side of 'instanceof' is not an object");var hasInstanceSymbolProp=constructor[Symbol.hasInstance];if(void 0===hasInstanceSymbolProp)return object instanceof constructor;if("function"!=typeof hasInstanceSymbolProp)throw new TypeError(typeof hasInstanceSymbolProp+" is not a function");return hasInstanceSymbolProp.call(constructor,object)},writable:!0,configurable:!0},forOf:{value:function(iterable,callback,thisArg){if(callback="function"!=typeof callback?emptyFunction:callback,"function"!=typeof iterable[Symbol.iterator])throw new TypeError("Iterable[Symbol.iterator] is not a function");var iterationResult,iterator=iterable[Symbol.iterator]();if("function"!=typeof iterator.next)throw new TypeError(".iterator.next is not a function");for(;;){if(iterationResult=iterator.next(),!isObject(iterationResult))throw new TypeError("Iterator result "+iterationResult+" is not an object");if(iterationResult.done)break;callback.call(thisArg,iterationResult.value)}},writable:!0,configurable:!0},spreadOperator:{value:function(target,thisArg){if("function"!=typeof target&&!isArray(target))throw new TypeError("Spread operator only supports on array and function objects at this moment");return new SpreadOperatorImpl(target,thisArg)},writable:!0,configurable:!0}}),defineProperty(global,"Symbol",{value:Symbol,writable:!0,configurable:!0}),defineProperty(Function.prototype,Symbol.hasInstance.toString(),{value:function(instance){return"function"==typeof this&&instance instanceof this}}),defineProperty(Array.prototype,"concat",{value:function(){if(void 0===this||null===this)throw new TypeError("Array.prototype.concat called on null or undefined");var self=Object(this),targets=slice.call(arguments),outputs=[];return targets.unshift(self),targets.forEach(function(target){isObject(target)?void 0!==target[Symbol.isConcatSpreadable]?target[Symbol.isConcatSpreadable]?appendArray(outputs,target):outputs.push(target):isArray(target)?appendArray(outputs,target):outputs.push(target):outputs.push(target)}),outputs},writable:!0,configurable:!0}),defineProperty(Object.prototype,"toString",{value:function(){return void 0===this||null===this?objectToString.call(this):"string"==typeof this[Symbol.toStringTag]?"[object "+this[Symbol.toStringTag]+"]":objectToString.call(this)},writable:!0,configurable:!0}),defineProperty(Array.prototype,Symbol.iterator.toString(),{value:function(){if(void 0===this||null===this)throw new TypeError("Cannot convert undefined or null to object");var self=Object(this);return new ArrayIterator(self,2)},writable:!0,configurable:!0}),defineProperty(Array,"from",{value:function(arrayLike,mapFn,thisArg){var constructor,length,outputs,i=0;if(constructor=isCallable(this)?this:Array,void 0===arrayLike||null===arrayLike)throw new TypeError("Cannot convert undefined or null to object");if(arrayLike=Object(arrayLike),void 0===mapFn)mapFn=simpleFunction;else if(!isCallable(mapFn))throw new TypeError(mapFn+" is not a function");if(void 0===arrayLike[Symbol.iterator]){if(!("number"==typeof arrayLike.length&&arrayLike.length>=0))return(outputs=new constructor(0)).length=0,outputs;for(length=Math.floor(arrayLike.length),(outputs=new constructor(length)).length=length;i<length;++i)outputs[i]=mapFn.call(thisArg,arrayLike[i])}else(outputs=new constructor).length=0,ES6.forOf(arrayLike,function(value){outputs.length++,outputs[outputs.length-1]=mapFn.call(thisArg,value)});return outputs},writable:!0,configurable:!0}),defineProperty(Array.prototype,"entries",{value:function(){if(void 0===this||null===this)throw new TypeError("Cannot convert undefined or null to object");var self=Object(this);return new ArrayIterator(self,1)},writable:!0,configurable:!0}),defineProperty(Array.prototype,"keys",{value:function(){if(void 0===this||null===this)throw new TypeError("Cannot convert undefined or null to object");var self=Object(this);return new ArrayIterator(self,3)},writable:!0,configurable:!0}),defineProperty(String.prototype,Symbol.iterator.toString(),{value:function(){if(void 0===this||null===this)throw new TypeError("String.prototype[Symbol.iterator] called on null or undefined");return new StringIterator(String(this),0)},writable:!0,configurable:!0}),ES6});
diff --git a/src/ami.html b/src/ami.html
index 1f38b71..92e7256 100644
--- a/src/ami.html
+++ b/src/ami.html
@@ -23,6 +23,7 @@
                 </div>
             </div>
         </noscript>
-        <script src="{ami.js}" type="text/javascript" charset="utf-8"></script>
+        <script src="/polyfill/symbol.js" type="text/javascript"></script>
+        <script src="{ami.js}" type="text/javascript"></script>
     </body>
 </html>
diff --git a/src/ami.js/array.js b/src/ami.js/array.js
new file mode 100644
index 0000000..d3a0541
--- /dev/null
+++ b/src/ami.js/array.js
@@ -0,0 +1,27 @@
+const $arrayRemoveAt = function(array, index) {
+    array.splice(index, 1);
+};
+
+const $arrayRemoveValue = function(array, item) {
+    let index;
+    while(array.length > 0 && (index = array.indexOf(item)) >= 0)
+        $arrayRemoveAt(array, index);
+};
+
+const $arrayRemoveAny = function(array, predicate) {
+    let index;
+    while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
+        $arrayRemoveAt(array, index);
+};
+
+const $arrayShuffle = function(array) {
+    if(array.length < 2)
+        return;
+
+    for(let i = array.length - 1; i > 0; --i) {
+        const j = Math.floor(Math.random() * (i + 1));
+        const tmp = array[i];
+        array[i] = array[j];
+        array[j] = tmp;
+    }
+};
diff --git a/src/ami.js/buttons.js b/src/ami.js/buttons.js
index 252fd30..0b07d28 100644
--- a/src/ami.js/buttons.js
+++ b/src/ami.js/buttons.js
@@ -1,21 +1,16 @@
-#include utility.js
-
 var AmiOptionButtons = function(parent) {
-    var container = $e({ attrs: { id: 'bbCodeContainer' } }),
-        buttons = new Map;
+    var container = $element('div', { id: 'bbCodeContainer' });
+    var buttons = new Map;
     parent.appendChild(container);
 
     var createBtn = function(name, title, languageKey, style, onclick) {
         var pub = {};
-        var elem = $e({
-            tag: 'input',
-            attrs: {
-                type: 'button',
-                name: typeof languageKey === 'string' ? languageKey : ';;',
-                value: title,
-                style: style,
-                onclick: function(ev) { performClick(ev); },
-            },
+        var elem = $element('input', {
+            type: 'button',
+            name: typeof languageKey === 'string' ? languageKey : ';;',
+            value: title,
+            style: style,
+            onclick: function(ev) { performClick(ev); },
         });
 
         var hide = function() { elem.classList.add('hidden'); };
@@ -62,7 +57,7 @@ var AmiOptionButtons = function(parent) {
         },
         clear: function() {
             buttons.clear();
-            $rc(container);
+            $removeChildren(container);
         },
         hide: hide,
         show: show,
diff --git a/src/ami.js/chat.js b/src/ami.js/chat.js
index c7d3c21..1dff731 100644
--- a/src/ami.js/chat.js
+++ b/src/ami.js/chat.js
@@ -8,10 +8,9 @@
 #include status.js
 #include submitbox.js
 #include title.js
-#include utility.js
 
 var AmiChat = function(chat, title, parent) {
-    var container = $e({ attrs: { id: 'chat' } });
+    var container = $element('div', { id: 'chat' });
 
     var pub = {
         getElement: function() { return container; },
@@ -76,7 +75,7 @@ var AmiChat = function(chat, title, parent) {
         pub.appendTo(parent);
 
     optionIcons.create('unembed', 'Unembed any embedded media', 'unembed', function() {
-        var buttons = $c('js-unembed-btn');
+        var buttons = $queryAll('.js-unembed-btn');
         while(buttons.length > 0)
             buttons[0].click();
     });
diff --git a/src/ami.js/colourpick.js b/src/ami.js/colourpick.js
index 6602d72..e88cf64 100644
--- a/src/ami.js/colourpick.js
+++ b/src/ami.js/colourpick.js
@@ -1,10 +1,9 @@
 #include colour.js
-#include utility.js
 #include colpick/picker.jsx
 
 var AmiColourPicker = function(parent) {
-    var container = $e({ attrs: { id: 'pickerNew', className: 'hidden' } }),
-        callback = undefined;
+    var container = $element('div', { id: 'pickerNew', className: 'hidden' });
+    var callback = undefined;
     parent.appendChild(container);
 
     var picker = new FwColourPicker(
diff --git a/src/ami.js/compat.js b/src/ami.js/compat.js
index 266b9f3..81b7f73 100644
--- a/src/ami.js/compat.js
+++ b/src/ami.js/compat.js
@@ -1,5 +1,3 @@
-#include utility.js
-
 if(!('WebSocket' in window) && 'MozWebSocket' in window)
     window.WebSocket = window.MozWebSocket;
 
@@ -35,8 +33,8 @@ if(!('Map' in window))
             var index = keys.indexOf(key);
             if(key < 0) return false;
 
-            $ar(keys, index);
-            $ar(values, index);
+            $arrayRemoveAt(keys, index);
+            $arrayRemoveAt(values, index);
             --obj.size;
         };
 
diff --git a/src/ami.js/copyright.js b/src/ami.js/copyright.js
index 1b19b7c..77019e1 100644
--- a/src/ami.js/copyright.js
+++ b/src/ami.js/copyright.js
@@ -1,7 +1,5 @@
-#include utility.js
-
 var AmiCopyright = function(parent) {
-    var container = $e({ attrs: { id: 'copyright' } });
+    var container = $element('div', { id: 'copyright' });
     parent.appendChild(container);
 
     return {
@@ -13,20 +11,13 @@ var AmiCopyright = function(parent) {
                 if(typeof section === 'string')
                     children.push(section);
                 else if(Array.isArray(section))
-                    children.push({
-                        tag: 'a',
-                        attrs: {
-                            href: section[1],
-                            target: '_blank',
-                        },
-                        child: section[0],
-                    });
+                    children.push($element('a', {
+                        href: section[1],
+                        target: '_blank',
+                    }, section[0]));
             }
 
-            container.appendChild($e({
-                tag: 'p',
-                child: children,
-            }))
+            container.appendChild($element('p', {}, ...children));
         },
     };
 };
diff --git a/src/ami.js/ctx.js b/src/ami.js/ctx.js
index d5ae86f..f54cfbf 100644
--- a/src/ami.js/ctx.js
+++ b/src/ami.js/ctx.js
@@ -12,7 +12,6 @@
 #include styles.js
 #include title.js
 #include txtrigs.js
-#include utility.js
 
 var AmiContext = function(title, auth, loading) {
     var pub = {};
diff --git a/src/ami.js/emotes.js b/src/ami.js/emotes.js
index e60fe49..584b5e2 100644
--- a/src/ami.js/emotes.js
+++ b/src/ami.js/emotes.js
@@ -1,23 +1,20 @@
 #include watcher.js
 
 var AmiEmoticonList = function(parent) {
-    var container = $e({ attrs: { id: 'emotes' } }),
-        watcher = new AmiWatcher;
+    var container = $element('div', { id: 'emotes' });
+    var watcher = new AmiWatcher;
     parent.appendChild(container);
 
     var clear = function() {
-        $rc(container);
+        $removeChildren(container);
     };
 
     var add = function(emote) {
-        container.appendChild($e({
-            tag: 'img',
-            attrs: {
-                src: emote.url,
-                alt: emote.strings[0],
-                title: emote.strings[0],
-                onclick: function() { watcher.call(emote, [emote]); },
-            },
+        container.appendChild($element('img', {
+            src: emote.url,
+            alt: emote.strings[0],
+            title: emote.strings[0],
+            onclick: function() { watcher.call(emote, [emote]); },
         }));
     };
 
diff --git a/src/ami.js/html.js b/src/ami.js/html.js
new file mode 100644
index 0000000..f2fad43
--- /dev/null
+++ b/src/ami.js/html.js
@@ -0,0 +1,139 @@
+const $id = document.getElementById.bind(document);
+const $query = document.querySelector.bind(document);
+const $queryAll = document.querySelectorAll.bind(document);
+const $text = document.createTextNode.bind(document);
+
+const $insertBefore = function(target, element) {
+    target.parentNode.insertBefore(element, target);
+};
+
+const $appendChild = function(element, child) {
+    switch(typeof child) {
+        case 'undefined':
+            break;
+
+        case 'string':
+            element.appendChild($text(child));
+            break;
+
+        case 'function':
+            $appendChild(element, child());
+            break;
+
+        case 'object':
+            if(child === null)
+                break;
+
+            if(child instanceof Node)
+                element.appendChild(child);
+            else if(child?.element instanceof Node)
+                element.appendChild(child.element);
+            else if(typeof child?.toString === 'function')
+                element.appendChild($text(child.toString()));
+            break;
+
+        default:
+            element.appendChild($text(child.toString()));
+            break;
+    }
+};
+
+const $appendChildren = function(element, ...children) {
+    for(const child of children)
+        $appendChild(element, child);
+};
+
+const $removeChildren = function(element) {
+    while(element.lastChild)
+        element.removeChild(element.lastChild);
+};
+
+const $fragment = function(props, ...children) {
+    const fragment = document.createDocumentFragment();
+    $appendChildren(fragment, ...children);
+    return fragment;
+};
+
+const $element = function(type, props, ...children) {
+    if(typeof type === 'function')
+        return new type(props ?? {}, ...children);
+
+    const element = document.createElement(type ?? 'div');
+
+    if(props)
+        for(let key in props) {
+            const prop = props[key];
+            if(prop === undefined || prop === null)
+                continue;
+
+            switch(typeof prop) {
+                case 'function':
+                    if(key.substring(0, 2) === 'on')
+                        key = key.substring(2).toLowerCase();
+                    element.addEventListener(key, prop);
+                    break;
+
+                case 'object':
+                    if(prop instanceof Array) {
+                        if(key === 'class')
+                            key = 'classList';
+
+                        const attr = element[key];
+                        let addFunc = null;
+
+                        if(attr instanceof Array)
+                            addFunc = attr.push.bind(attr);
+                        else if(attr instanceof DOMTokenList)
+                            addFunc = attr.add.bind(attr);
+
+                        if(addFunc !== null) {
+                            for(let j = 0; j < prop.length; ++j)
+                                addFunc(prop[j]);
+                        } else {
+                            if(key === 'classList')
+                                key = 'class';
+                            element.setAttribute(key, prop.toString());
+                        }
+                    } else {
+                        if(key === 'class' || key === 'className')
+                            key = 'classList';
+
+                        let setFunc = null;
+                        if(element[key] instanceof DOMTokenList)
+                            setFunc = (ak, av) => { if(av) element[key].add(ak); };
+                        else if(element[key] instanceof CSSStyleDeclaration)
+                            setFunc = (ak, av) => {
+                                if(ak.indexOf('-') >= 0)
+                                    element[key].setProperty(ak, av, '');
+                                else
+                                    element[key][ak] = av;
+                            };
+                        else
+                            setFunc = (ak, av) => { element[key][ak] = av; };
+
+                        for(const attrKey in prop) {
+                            const attrValue = prop[attrKey];
+                            if(attrValue)
+                                setFunc(attrKey, attrValue);
+                        }
+                    }
+                    break;
+
+                case 'boolean':
+                    if(prop)
+                        element.setAttribute(key, '');
+                    break;
+
+                default:
+                    if(key === 'className')
+                        key = 'class';
+
+                    element.setAttribute(key, prop.toString());
+                    break;
+            }
+        }
+
+    $appendChildren(element, ...children);
+
+    return element;
+};
diff --git a/src/ami.js/inputbox.js b/src/ami.js/inputbox.js
index 985d748..88c458e 100644
--- a/src/ami.js/inputbox.js
+++ b/src/ami.js/inputbox.js
@@ -1,21 +1,14 @@
-#include utility.js
 #include watcher.js
 
 var AmiInputBox = function(parent) {
-    var watchers = new AmiWatcherCollection,
-        input = $e({
-            tag: 'textarea',
-            attrs: {
-                cols: '2',
-                id: 'message',
-                autofocus: 'autofocus',
-                maxlength: '0',
-            },
-        }),
-        container = $e({
-            attrs: { id: 'inputFieldContainer' },
-            child: input,
-        });
+    var watchers = new AmiWatcherCollection;
+    var input = $element('textarea', {
+        cols: '2',
+        id: 'message',
+        autofocus: 'autofocus',
+        maxlength: '0',
+    });
+    var container = $element('div', { id: 'inputFieldContainer' }, input);
 
     parent.appendChild(container);
 
@@ -145,7 +138,7 @@ var AmiInputBox = function(parent) {
                             if(split[0] === value)
                                 input.style[name] = null;
                         } else if(split.length > 1) {
-                            $ari(split, value);
+                            $arrayRemoveValue(split, value);
                             input.style[name] = split.join(' ');
                         }
                     }
diff --git a/src/ami.js/loadoverlay.js b/src/ami.js/loadoverlay.js
index ef3ff62..36e6d0e 100644
--- a/src/ami.js/loadoverlay.js
+++ b/src/ami.js/loadoverlay.js
@@ -1,23 +1,23 @@
-#include utility.js
-
 var AmiLoadingOverlay = function(parent, indicatorShown) {
-    var text = $e({
-        attrs: { id: 'conntxt' },
-        child: 'Loading Sock Chat...',
-    });
-
-    var indicator = $e({ attrs: { id: 'indicator' } });
-
-    var container = $e({
-        attrs: { id: 'connmsg' },
-        child: {
-            attrs: { id: 'info' },
-            child: [text, indicator],
-        },
-    });
+    var text = $element(
+        'div',
+        { id: 'conntxt' },
+        'Loading Sock Chat...',
+    );
+    var indicator = $element('div', { id: 'indicator' });
+    var container = $element(
+        'div',
+        { id: 'connmsg' },
+        $element(
+            'div',
+            { id: 'info' },
+            text,
+            indicator
+        ),
+    );
 
     var appendTo = function(parent) { parent.appendChild(container); };
-    var destroy = function() { $r(container); };
+    var destroy = function() { container.parentNode.removeChild(container); };
 
     var showIndicator = function() { indicator.classList.remove('hidden'); };
     var hideIndicator = function() { indicator.classList.add('hidden'); };
diff --git a/src/ami.js/main.js b/src/ami.js/main.js
index 32846c7..147532e 100644
--- a/src/ami.js/main.js
+++ b/src/ami.js/main.js
@@ -1,6 +1,9 @@
 #vars self build
 
 #include compat.js
+#include array.js
+#include html.js
+
 #include common.js
 #include ctx.js
 #include loadoverlay.js
diff --git a/src/ami.js/messages.js b/src/ami.js/messages.js
index 3cf9a33..d441de4 100644
--- a/src/ami.js/messages.js
+++ b/src/ami.js/messages.js
@@ -1,11 +1,10 @@
-#include utility.js
 #include watcher.js
 
 var AmiMessageList = function(parent) {
-    var container = $e({ attrs: { id: 'chatList', className: 'fullWidth' } }),
-        isEven = true,
-        idPfx = 'sock_msg_',
-        watchers = new AmiWatcherCollection;
+    var container = $element('div', { id: 'chatList', className: 'fullWidth' });
+    var isEven = true;
+    var idPfx = 'sock_msg_';
+    var watchers = new AmiWatcherCollection;
     parent.appendChild(container);
 
     watchers.define(['nameInsert', 'delete']);
@@ -29,90 +28,80 @@ var AmiMessageList = function(parent) {
 
             var nameBody = msgInfo.sender.username;
             if(msgInfo.sender.isBot())
-                nameBody = {
-                    tag: 'span',
-                    attrs: { className: 'botName' },
-                    child: nameBody,
-                };
+                nameBody = $element(
+                    'span',
+                    { className: 'botName' },
+                    nameBody,
+                );
 
             var messageBody = [
-                {
-                    tag: 'span',
-                    attrs: { className: 'date' },
-                    child: '(' + msgInfo.created.toLocaleTimeString() + ')',
-                },
+                $element(
+                    'span',
+                    { className: 'date' },
+                    '(' + msgInfo.created.toLocaleTimeString() + ')',
+                ),
                 ' ',
-                {
-                    tag: 'span',
-                    attrs: {
-                        style: nameStyle,
-                        onclick: function() {
-                            watchers.call('nameInsert', msgInfo, [msgInfo]);
-                        },
+                $element('span', {
+                    style: nameStyle,
+                    onclick: function() {
+                        watchers.call('nameInsert', msgInfo, [msgInfo]);
                     },
-                    child: nameBody,
-                },
+                }, nameBody),
             ];
 
             if(msgInfo.showColon)
-                messageBody.push({
-                    tag: 'span',
-                    attrs: { className: 'msgColon' },
-                    child: ':',
-                });
+                messageBody.push($element(
+                    'span',
+                    { className: 'msgColon' },
+                    ':',
+                ));
 
             if(msgInfo.isAction) {
                 if(msgInfo.bodyText.indexOf("'") !== 0 || (msgInfo.bodyText.match(/\'/g).length % 2) === 0)
                     messageBody.push(' ');
             } else {
                 messageBody.push(' ');
-                messageBody.push({
-                    tag: 'span',
-                    attrs: { className: 'msgBreak' },
-                    child: { tag: 'br' },
-                });
+                messageBody.push($element(
+                    'span',
+                    { className: 'msgBreak' },
+                    $element('br'),
+                ));
             }
 
             if(msgInfo.isPM) {
                 if(msgInfo.pmTargetName)
-                    messageBody.push({
-                        tag: 'i',
-                        child: AmiStrings.getMenuString('whisperto', msgInfo.pmTargetName),
-                    });
+                    messageBody.push($element(
+                        'i', {},
+                        AmiStrings.getMenuString('whisperto', msgInfo.pmTargetName),
+                    ));
                 else
-                    messageBody.push({
-                        tag: 'i',
-                        child: AmiStrings.getMenuString('whisper'),
-                    });
+                    messageBody.push($element(
+                        'i', {},
+                        AmiStrings.getMenuString('whisper'),
+                    ));
                 messageBody.push(' ');
             }
 
-            var message = $e({
-                attrs: {
-                    id: idPfx + msgInfo.id,
-                    className: isEven ? 'rowEven' : 'rowOdd'
-                },
-                child: messageBody,
-            });
+            var message = $element('div', {
+                id: idPfx + msgInfo.id,
+                className: isEven ? 'rowEven' : 'rowOdd'
+            }, ...messageBody);
 
             let msgBodyTarget = message;
             if(msgInfo.isAction)
-                message.appendChild(msgBodyTarget = $e({ tag: 'i' }));
+                message.appendChild(msgBodyTarget = $element('i'));
             msgBodyTarget.insertAdjacentHTML('beforeend', msgInfo.bodyRaw);
 
             if(msgInfo.canDelete)
-                message.appendChild($e({
-                    tag: 'a',
-                    attrs: {
-                        title: 'Delete this message',
-                        className: 'sprite-small sprite-delete',
-                        style: {
-                            float: 'right',
-                            margin: '2px 0 0 0',
-                        },
-                        onclick: function() {
-                            watchers.call('delete', msgInfo, [msgInfo]);
-                        },
+                message.appendChild($element('a', {
+                    title: 'Delete this message',
+                    className: 'sprite-small sprite-delete',
+                    style: {
+                        float: 'right',
+                        margin: '2px 0 0 0',
+                    },
+                    onclick: function() {
+                        watchers.call('delete', msgInfo, [msgInfo]);
                     },
                 }));
 
@@ -122,11 +111,13 @@ var AmiMessageList = function(parent) {
             return message;
         },
         remove: function(msgId) {
-            $ri(idPfx + msgId);
+            const elem = $id(idPfx + msgId);
+            if(elem)
+                elem.parentNode.removeChild(elem);
             reflow();
         },
         clear: function() {
-            $rc(container);
+            $removeChildren(container);
             isEven = true;
         },
         reflow: reflow,
diff --git a/src/ami.js/opticons.js b/src/ami.js/opticons.js
index c572ed1..87325dc 100644
--- a/src/ami.js/opticons.js
+++ b/src/ami.js/opticons.js
@@ -1,20 +1,15 @@
-#include utility.js
-
 var AmiOptionIcons = function(parent) {
-    var container = $e({ attrs: { id: 'optionsContainer' } }),
-        icons = new Map;
+    var container = $element('div', { id: 'optionsContainer' });
+    var icons = new Map;
     parent.appendChild(container);
 
     var createIcon = function(name, title, icon, onclick, enabled) {
         var pub = {};
-        var elem = $e({
-            tag: 'a',
-            attrs: {
-                href: 'javascript:void(0);',
-                title: title,
-                className: 'optIcon sprite sprite-' + icon,
-                onclick: function(ev) { performClick(ev); },
-            },
+        var elem = $element('a', {
+            href: 'javascript:void(0);',
+            title: title,
+            className: 'optIcon sprite sprite-' + icon,
+            onclick: function(ev) { performClick(ev); },
         });
 
         var hasState = typeof enabled === 'boolean';
@@ -61,7 +56,7 @@ var AmiOptionIcons = function(parent) {
         },
         clear: function() {
             icons.clear();
-            $rc(container);
+            $removeChildren(container);
         },
     };
 };
diff --git a/src/ami.js/reconnect.js b/src/ami.js/reconnect.js
index d20ce16..ab6837b 100644
--- a/src/ami.js/reconnect.js
+++ b/src/ami.js/reconnect.js
@@ -1,5 +1,3 @@
-#include utility.js
-
 var AmiReconnecter = function(parent) {
     var handler = new AmiReconnectHandler,
         indicator = new AmiReconnectIndicator(parent, handler);
@@ -128,47 +126,35 @@ var AmiReconnectHandler = function() {
 };
 
 var AmiReconnectIndicator = function(parent, handler) {
-    var counterTime = $e({
-        tag: 'span',
-        attrs: {
-            id: 'reconnectCounterTime',
-        },
-        child: '0.0',
-    });
-    var counterBody = $e({
-        attrs: {
+    var counterTime = $element('span', { id: 'reconnectCounterTime' }, '0.0');
+    var counterBody = $element(
+        'div', {
             id: 'reconnectCounter',
             className: 'hidden',
         },
-        child: ['Reconnecting in ', counterTime, ' seconds.'],
-    });
+        'Reconnecting in ', counterTime, ' seconds.'
+    );
 
-    var connectEllipsis = $e({
-        tag: 'span',
-        attrs: {
-            id: 'reconnectConnectEllipsis',
-        },
-        child: '...',
-    });
-    var connectBody = $e({
-        attrs: {
+    var connectEllipsis = $element('span', { id: 'reconnectConnectEllipsis' }, '...');
+    var connectBody = $element(
+        'div',
+        {
             id: 'reconnectConnect',
             className: 'hidden',
         },
-        child: ['Attempting to reconnect', connectEllipsis],
-    });
+        'Attempting to reconnect', connectEllipsis,
+    );
 
-    var body = $e({
-        attrs: {
+    var body = $element(
+        'div',
+        {
             id: 'reconnectIndicator',
             className: 'hidden',
         },
-        child: [
-            { child: 'WARNING: Connection Problem' },
-            counterBody,
-            connectBody,
-        ],
-    });
+        $element('div', {}, 'WARNING: Connection Problem'),
+        counterBody,
+        connectBody,
+    );
 
     parent.appendChild(body);
 
diff --git a/src/ami.js/servers.js b/src/ami.js/servers.js
index 6d8c39d..0321340 100644
--- a/src/ami.js/servers.js
+++ b/src/ami.js/servers.js
@@ -1,11 +1,10 @@
 #include common.js
-#include utility.js
 
 var AmiServers = function() {
     var servers = futami.get('servers').slice(0),
         index = 0xFFFF;
 
-    $as(servers);
+    $arrayShuffle(servers);
 
     return {
         getServer: function(callback) {
diff --git a/src/ami.js/settings.js b/src/ami.js/settings.js
index d933256..2de0e79 100644
--- a/src/ami.js/settings.js
+++ b/src/ami.js/settings.js
@@ -1,5 +1,4 @@
 #include cookies.js
-#include utility.js
 #include watcher.js
 
 // really need to replace null with undefined where possible, in mami too
@@ -47,7 +46,7 @@ var AmiSettings = function() {
             else AmiCookies.removeCookie(cookieName);
             callWatchers();
 
-            $ari(locked, name);
+            $arrayRemoveValue(locked, name);
         };
 
         var setValue = function(value) {
@@ -68,7 +67,7 @@ var AmiSettings = function() {
             else AmiCookies.setCookie(cookieName, value);
             callWatchers();
 
-            $ari(locked, name);
+            $arrayRemoveValue(locked, name);
         };
 
         pub.getName = function() { return name; };
@@ -113,7 +112,7 @@ var AmiSettings = function() {
             else AmiCookies.setCookie(cookieName, value);
             callWatchers();
 
-            $ari(locked, name);
+            $arrayRemoveValue(locked, name);
         };
 
         pub.watch = function(watcher) {
diff --git a/src/ami.js/sidebars.js b/src/ami.js/sidebars.js
index db43335..8dd55f4 100644
--- a/src/ami.js/sidebars.js
+++ b/src/ami.js/sidebars.js
@@ -1,4 +1,3 @@
-#include utility.js
 #include watcher.js
 
 var AmiSidebars = function(container) {
@@ -21,14 +20,15 @@ var AmiSidebars = function(container) {
         var classes = ['sidebar', 'hidden'];
         if(wide) classes.push('widebar');
 
-        var titleElem = $e({ attrs: { className: 'top' } }),
-            elem = $e({
-                attrs: {
-                    id: name,
-                    classList: classes,
-                },
-                child: [titleElem, body],
-            });
+        var titleElem = $element('div', { className: 'top' });
+        var elem = $element(
+            'div',
+            {
+                id: name,
+                classList: classes,
+            },
+            titleElem, body,
+        );
 
         var hide = function() {
             elem.classList.add('hidden');
diff --git a/src/ami.js/sidebars/channelssb.js b/src/ami.js/sidebars/channelssb.js
index a68da75..1796415 100644
--- a/src/ami.js/sidebars/channelssb.js
+++ b/src/ami.js/sidebars/channelssb.js
@@ -1,9 +1,7 @@
-#include utility.js
-
 var AmiChannelsSidebar = function() {
-    var sbName = 'chanList',
-        body = $e(),
-        activeChannel = undefined;
+    var sbName = 'chanList';
+    var body = $element('div');
+    var activeChannel = undefined;
 
     var setTitle;
     var rowEven = false;
@@ -23,16 +21,14 @@ var AmiChannelsSidebar = function() {
             return str;
         };
 
-        var nameElem = $e({
-                attrs: { style: { display: 'black' } },
-                child: toString(),
-            }),
-            elem = $e({
-                attrs: {
-                    className: (rowEven ? 'rowEven' : 'rowOdd'),
-                },
-                child: [nameElem],
-            });
+        var nameElem = $element(
+            'div',
+            { style: { display: 'black' } },
+            toString(),
+        );
+        var elem = $element('div', {
+            className: (rowEven ? 'rowEven' : 'rowOdd'),
+        }, nameElem);
 
         rowEven = !rowEven;
 
diff --git a/src/ami.js/sidebars/helpsb.js b/src/ami.js/sidebars/helpsb.js
index f011449..00733a0 100644
--- a/src/ami.js/sidebars/helpsb.js
+++ b/src/ami.js/sidebars/helpsb.js
@@ -1,10 +1,9 @@
 #include strings.js
-#include utility.js
 
 var AmiHelpSidebar = function() {
-    var sbName = 'helpList',
-        list = $e({ tag: 'table' }),
-        body = $e({ child: list });
+    var sbName = 'helpList';
+    var list = $element('table');
+    var body = $element('div', {}, list);
 
     var setTitle;
 
@@ -35,7 +34,7 @@ var AmiHelpSidebar = function() {
                 title.textContent = helpInfo.title + ':';
 
                 var example = row.insertCell(1);
-                example.appendChild($e({ tag: 'i', child: helpInfo.format }));
+                example.appendChild($element('i', {}, helpInfo.format));
 
                 rowEven = !rowEven;
             });
diff --git a/src/ami.js/sidebars/settingssb.js b/src/ami.js/sidebars/settingssb.js
index 4602548..c64b4ca 100644
--- a/src/ami.js/sidebars/settingssb.js
+++ b/src/ami.js/sidebars/settingssb.js
@@ -1,8 +1,6 @@
-#include utility.js
-
 var AmiSettingsSidebar = function() {
-    var sbName = 'settingsList',
-        body = $e({ child: { tag: 'table' } });
+    var sbName = 'settingsList';
+    var body = $element('div', {}, $element('table'));
 
     var setTitle;
 
diff --git a/src/ami.js/sidebars/uploadssb.js b/src/ami.js/sidebars/uploadssb.js
index ee0d0e5..9369928 100644
--- a/src/ami.js/sidebars/uploadssb.js
+++ b/src/ami.js/sidebars/uploadssb.js
@@ -1,42 +1,26 @@
-#include utility.js
-
 var AmiUploadsSidebar = function() {
-    var sbName = 'uploadList',
-        body = $e({
-            child: [
-                {
-                    attrs: {
-                        className: 'eepromForm',
-                    },
-                    child: [
-                        {
-                            tag: 'input',
-                            attrs: {
-                                id: 'uploadSelector',
-                                type: 'file',
-                                class: 'hidden',
-                                multiple: true,
-                            },
-                        },
-                        {
-                            tag: 'input',
-                            attrs: {
-                                id: 'uploadButton',
-                                type: 'button',
-                                value: 'Select file',
-                            },
-                        },
-                    ],
-                },
-                {
-                    tag: 'table',
-                    attrs: {
-                        id: 'uploadHistory',
-                        class: 'eepromHistory',
-                    },
-                },
-            ],
-        });
+    var sbName = 'uploadList';
+    var body = $element(
+        'div', {},
+        $element(
+            'div', { className: 'eepromForm' },
+            $element('input', {
+                id: 'uploadSelector',
+                type: 'file',
+                class: 'hidden',
+                multiple: true,
+            }),
+            $element('input', {
+                id: 'uploadButton',
+                type: 'button',
+                value: 'Select file',
+            }),
+        ),
+        $element('table', {
+            id: 'uploadHistory',
+            class: 'eepromHistory',
+        }),
+    );
 
     var setTitle;
 
diff --git a/src/ami.js/sidebars/userssb.js b/src/ami.js/sidebars/userssb.js
index f725ec9..4d7789a 100644
--- a/src/ami.js/sidebars/userssb.js
+++ b/src/ami.js/sidebars/userssb.js
@@ -1,8 +1,6 @@
-#include utility.js
-
 var AmiUsersSidebar = function() {
-    var sbName = 'userList',
-        body = $e();
+    var sbName = 'userList';
+    var body = $element('div');
 
     var setTitle;
 
diff --git a/src/ami.js/sound.js b/src/ami.js/sound.js
index cde18fc..e52dac6 100644
--- a/src/ami.js/sound.js
+++ b/src/ami.js/sound.js
@@ -1,5 +1,3 @@
-#include utility.js
-
 var AmiSound = function() {
     var playing = [];
     var isMuted = false,
@@ -74,7 +72,7 @@ var AmiSound = function() {
     };
 
     (function() {
-        var elem = $e('audio');
+        var elem = $element('audio');
         for(var name in formats) {
             var format = formats[name],
                 support = elem.canPlayType(format);
@@ -128,10 +126,7 @@ var AmiSound = function() {
         if(rate === undefined) rate = 1.0;
         else rate = Math.min(4.0, Math.max(0.25, rate));
 
-        var audio = $e({
-            tag: 'audio',
-            attrs: { src: url },
-        });
+        var audio = $element('audio', { src: url });
         audio.volume = Math.max(0.0, Math.min(1.0, volume * localVolume));
         audio.muted = isMuted;
         audio.preservesPitch = false;
@@ -139,7 +134,7 @@ var AmiSound = function() {
         playing.push(audio);
 
         audio.addEventListener('ended', function() {
-            $ari(playing, audio);
+            $arrayRemoveValue(playing, audio);
         });
 
         audio.addEventListener('loadeddata', function() {
diff --git a/src/ami.js/status.js b/src/ami.js/status.js
index 126f132..8ddc88a 100644
--- a/src/ami.js/status.js
+++ b/src/ami.js/status.js
@@ -1,7 +1,5 @@
-#include utility.js
-
 var AmiStatusIndicator = function(parent) {
-    var icon = $e({ attrs: { id: 'statusIconContainer' } });
+    var icon = $element('div', { id: 'statusIconContainer' });
     parent.appendChild(icon);
 
     var setState = function(colour, text) {
diff --git a/src/ami.js/submitbox.js b/src/ami.js/submitbox.js
index dbbc732..dee9b6b 100644
--- a/src/ami.js/submitbox.js
+++ b/src/ami.js/submitbox.js
@@ -1,43 +1,32 @@
-#include utility.js
 #include watcher.js
 
 var AmiSubmitBox = function(parent) {
-    var pub = {},
-        watcher = new AmiWatcher,
-        maxLengthValue = 0,
-        click = function(ev) {
-            watcher.call(pub, [ev]);
-        };
+    var pub = {};
+    var watcher = new AmiWatcher;
+    var maxLengthValue = 0;
+    var click = function(ev) {
+        watcher.call(pub, [ev]);
+    };
 
-    var button = $e({
-            tag: 'input',
-            attrs: {
-                type: 'button',
-                value: 'Send',
-                id: 'sendmsg',
-                onclick: function(ev) { click(ev); },
-            },
-        }),
-        curLength = $e({
-            tag: 'span',
-            child: '0',
-        }),
-        maxLength = $e({
-            tag: 'span',
-            child: maxLengthValue.toString(),
-        }),
-        container = $e({
-            attrs: { id: 'submitButtonContainer' },
-            child: [
-                {
-                    tag: 'span',
-                    attrs: { id: 'messageLengthCounter' },
-                    child: [curLength, '/', maxLength],
-                },
-                ' ',
-                button,
-            ],
-        });
+    var button = $element('input', {
+        type: 'button',
+        value: 'Send',
+        id: 'sendmsg',
+        onclick: function(ev) { click(ev); },
+    });
+    var curLength = $element('span', {}, '0');
+    var maxLength = $element('span', {}, maxLengthValue.toString());
+    var container = $element(
+        'div',
+        { id: 'submitButtonContainer' },
+        $element(
+            'span',
+            { id: 'messageLengthCounter' },
+            curLength, '/', maxLength
+        ),
+        ' ',
+        button,
+    );
 
     parent.appendChild(container);
 
diff --git a/src/ami.js/title.js b/src/ami.js/title.js
index 2e2ce0b..676c94d 100644
--- a/src/ami.js/title.js
+++ b/src/ami.js/title.js
@@ -1,8 +1,7 @@
 #include common.js
-#include utility.js
 
 var AmiChatTitle = function(parent, title) {
-    var elem = $e({ attrs: { id: 'chatTitle' }, child: title});
+    var elem = $element('div', { id: 'chatTitle' }, title);
     parent.appendChild(elem);
 
     return {
@@ -13,8 +12,8 @@ var AmiChatTitle = function(parent, title) {
 };
 
 var AmiWindowTitle = function(title) {
-    var baseTitle = '',
-        activeFlash = undefined;
+    var baseTitle = '';
+    var activeFlash = undefined;
 
     var setTitle = function(text) {
         document.title = text;
diff --git a/src/ami.js/ts_20_ui.js b/src/ami.js/ts_20_ui.js
index c2c2f34..7ccf06b 100644
--- a/src/ami.js/ts_20_ui.js
+++ b/src/ami.js/ts_20_ui.js
@@ -5,7 +5,6 @@
 #include ts_utils.js
 #include title.js
 #include strings.js
-#include utility.js
 
 var UI = (function () {
     function UI() {}
@@ -252,7 +251,7 @@ var UI = (function () {
             msgDiv.id = "sock_user_" + u.id;
             msgDiv.innerHTML = "<a style='color:" + u.color + "; display: block;' href='javascript:UI.ToggleUserMenu(" + u.id + ");'>" + u.username + "</a>";
             msgDiv.appendChild(UI.GenerateContextMenu(u));
-            $i('userList').getElementsByClassName('body')[0].appendChild(msgDiv);
+            $id('userList').getElementsByClassName('body')[0].appendChild(msgDiv);
             this.rowEven[1] = !this.rowEven[1];
         }
         if (addToContext) {
@@ -281,54 +280,53 @@ var UI = (function () {
     };
 
     UI.SwitchChannel = function(name) {
-        var channels = $c('js-sidebar-channel');
+        var channels = $queryAll('.js-sidebar-channel');
         for(var i = 0; i < channels.length; ++i) {
             var channel = channels[i];
             if(channel.style.fontWeight === 'bold')
                 channel.style.fontWeight = null;
         }
 
-        $i('sock_chan_' + name).style.fontWeight = 'bold';
+        $id('sock_chan_' + name).style.fontWeight = 'bold';
     };
 
     UI.AddChannel = function (name, ispwd, istemp, iscurr) {
         var displayName = UI.MakeChannelDisplayName(name, ispwd, istemp);
 
-        var html = $e({
-            attrs: {
+        var html = $element(
+            'div',
+            {
                 classList: ['js-sidebar-channel', this.rowEven[2] ? "rowEven" : "rowOdd"],
                 id: 'sock_chan_' + name,
             },
-            child: {
-                tag: 'a',
-                child: displayName,
-                attrs: {
-                    style: { display: 'block' },
-                    href: 'javascript:void(0);',
-                    onclick: function() {
-                        var cmd = '/join ' + name;
-                        if(ispwd && !UserContext.self.canModerate())
-                            cmd += ' ' + prompt(AmiStrings.getMenuString('chanpwd', [name]));
-                        ami.sockChat.sendMessage(cmd);
-                    },
+            $element('a', {
+                style: { display: 'block' },
+                href: 'javascript:void(0);',
+                onclick: function() {
+                    var cmd = '/join ' + name;
+                    if(ispwd && !UserContext.self.canModerate())
+                        cmd += ' ' + prompt(AmiStrings.getMenuString('chanpwd', [name]));
+                    ami.sockChat.sendMessage(cmd);
                 },
-            }
-        });
+            }, displayName),
+        );
 
         if(iscurr)
             html.style.fontWeight = 'bold';
 
-        $i('chanList').getElementsByClassName('body')[0].appendChild(html);
+        $id('chanList').getElementsByClassName('body')[0].appendChild(html);
         this.rowEven[2] = !this.rowEven[2];
     };
 
     UI.ModifyChannel = function (oldname, newname, ispwd, istemp) {
         var displayName = UI.MakeChannelDisplayName(newname, ispwd, istemp);
-        $i('sock_chat_' + oldname).children[0].textContent = displayName;
+        $id('sock_chat_' + oldname).children[0].textContent = displayName;
     };
 
     UI.RemoveChannel = function (name) {
-        $ri('sock_chat_' + oldname);
+        const elem = $id('sock_chat_' + oldname);
+        if(elem)
+            elem.parentNode.removeChild(elem);
     };
 
     UI.RemoveUser = function (id) {
@@ -337,14 +335,14 @@ var UI = (function () {
     };
 
     UI.RedrawChannelList = function() {
-        $i('chanList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('chans');
-        $i('chanList').getElementsByClassName('body')[0].innerHTML = '';
+        $id('chanList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('chans');
+        $id('chanList').getElementsByClassName('body')[0].innerHTML = '';
         this.rowEven[2] = false;
     };
 
     UI.RedrawUserList = function () {
-        $i('userList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('online');
-        $i('userList').getElementsByClassName('body')[0].innerHTML = '';
+        $id('userList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('online');
+        $id('userList').getElementsByClassName('body')[0].innerHTML = '';
         this.rowEven[1] = false;
         if(UserContext.self !== undefined)
             this.AddUser(UserContext.self, false);
diff --git a/src/ami.js/ts_chat.js b/src/ami.js/ts_chat.js
index 7cb7246..c067e63 100644
--- a/src/ami.js/ts_chat.js
+++ b/src/ami.js/ts_chat.js
@@ -1,7 +1,6 @@
 #include ts_20_ui.js
 #include ts_10_user.js
 #include z_eepromv1.js
-#include utility.js
 #include sidebars/channelssb.js
 #include sidebars/helpsb.js
 #include sidebars/settingssb.js
@@ -17,14 +16,10 @@ var Chat = (function () {
             type: "select",
             load: function(input) {
                 ami.styles.forEach(function(style) {
-                    input.appendChild($e({
-                        tag: 'option',
-                        child: style.getTitle(),
-                        attrs: {
-                            value: style.getName(),
-                            className: style.getOptionClass(),
-                        },
-                    }));
+                    input.appendChild($element('option', {
+                        value: style.getName(),
+                        className: style.getOptionClass(),
+                    }, style.getTitle()));
 
                     if(style.isDefault() && !ami.settings.has('style'))
                         ami.settings.set('style', style.getName());
@@ -475,7 +470,7 @@ var Chat = (function () {
                             ami.settings.set('soundPack', sounds.packs[0].name);
                     }
 
-                    var soundPackSetting = $i('chatSetting-spack');
+                    var soundPackSetting = $id('chatSetting-spack');
                     if(soundPackSetting) {
                         while(soundPackSetting.options.length > 0)
                             soundPackSetting.options.remove(0);
diff --git a/src/ami.js/utility.js b/src/ami.js/utility.js
deleted file mode 100644
index 41e3689..0000000
--- a/src/ami.js/utility.js
+++ /dev/null
@@ -1,163 +0,0 @@
-window.$i = document.getElementById.bind(document);
-window.$c = document.getElementsByClassName.bind(document);
-window.$q = document.querySelector.bind(document);
-window.$qa = document.querySelectorAll.bind(document);
-window.$t = document.createTextNode.bind(document);
-
-window.$r = function(element) {
-    if(element && element.parentNode)
-        element.parentNode.removeChild(element);
-};
-
-window.$ri = function(name) {
-    $r($i(name));
-};
-
-window.$ib = function(ref, elem) {
-    ref.parentNode.insertBefore(elem, ref);
-};
-
-window.$rc = function(element) {
-    while(element.lastChild)
-        element.removeChild(element.lastChild);
-};
-
-window.$e = function(info, attrs, child, created) {
-    info = info || {};
-
-    if(typeof info === 'string') {
-        info = {tag: info};
-        if(attrs)
-            info.attrs = attrs;
-        if(child)
-            info.child = child;
-        if(created)
-            info.created = created;
-    }
-
-    var elem = document.createElement(info.tag || 'div');
-
-    if(info.attrs) {
-        var attrs = info.attrs,
-            keys = Object.keys(attrs);
-
-        for(var key in attrs) {
-            var attr = attrs[key];
-
-            if(attr === undefined || attr === null)
-                continue;
-
-            switch(typeof attr) {
-                case 'function':
-                    if(key.substring(0, 2) === 'on')
-                        key = key.substring(2).toLowerCase();
-                    elem.addEventListener(key, attr);
-                    break;
-
-                case 'object':
-                    if(attr instanceof Array) {
-                        if(key === 'class')
-                            key = 'classList';
-
-                        var addFunc = null,
-                            prop = elem[key];
-
-                        if(prop instanceof Array)
-                            addFunc = prop.push.bind(prop);
-                        else if(prop instanceof DOMTokenList)
-                            addFunc = prop.add.bind(prop);
-
-                        if(addFunc !== null) {
-                            for(var j = 0; j < attr.length; ++j)
-                                addFunc(attr[j]);
-                        } else {
-                            if(key === 'classList')
-                                key = 'class';
-                            elem.setAttribute(key, attr.toString());
-                        }
-                    } else {
-                        for(var attrKey in attr)
-                            elem[key][attrKey] = attr[attrKey];
-                    }
-                    break;
-
-                case 'boolean':
-                    if(attr)
-                        elem.setAttribute(key, '');
-                    break;
-
-                default:
-                    if(key === 'className')
-                        key = 'class';
-                    elem.setAttribute(key, attr.toString());
-                    break;
-            }
-        }
-    }
-
-    if(info.child) {
-        var children = info.child;
-
-        if(!Array.isArray(children))
-            children = [children];
-
-        for(var i in children) {
-            var child = children[i];
-
-            switch(typeof child) {
-                case 'string':
-                    elem.appendChild($t(child));
-                    break;
-
-                case 'object':
-                    if(child instanceof Element)
-                        elem.appendChild(child);
-                    else if(child.getElement) {
-                        var childElem = child.getElement();
-                        if(childElem instanceof Element)
-                            elem.appendChild(childElem);
-                        else
-                            elem.appendChild($e(child));
-                    } else
-                        elem.appendChild($e(child));
-                    break;
-
-                default:
-                    elem.appendChild($t(child.toString()));
-                    break;
-            }
-        }
-    }
-
-    if(info.created)
-        info.created(elem);
-
-    return elem;
-};
-window.$er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });
-
-window.$ar = function(array, index) {
-    array.splice(index, 1);
-};
-window.$ari = function(array, item) {
-    var index;
-    while(array.length > 0 && (index = array.indexOf(item)) >= 0)
-        $ar(array, index);
-};
-window.$arf = function(array, predicate) {
-    var index;
-    while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
-        $ar(array, index);
-};
-
-window.$as = function(array) {
-    if(array.length < 2)
-        return;
-
-    for(var i = array.length - 1; i > 0; --i) {
-        var j = Math.floor(Math.random() * (i + 1));
-        var tmp = array[i];
-        array[i] = array[j];
-        array[j] = tmp;
-    }
-};
diff --git a/src/ami.js/watcher.js b/src/ami.js/watcher.js
index e7960ca..59a377f 100644
--- a/src/ami.js/watcher.js
+++ b/src/ami.js/watcher.js
@@ -1,5 +1,3 @@
-#include utility.js
-
 var AmiWatcher = function() {
     var watchers = [];
 
@@ -26,7 +24,7 @@ var AmiWatcher = function() {
             }
         },
         unwatch: function(watcher) {
-            $ari(watchers, watcher);
+            $arrayRemoveValue(watchers, watcher);
         },
         call: function(thisArg, args) {
             if(!Array.isArray(args)) {
diff --git a/src/ami.js/z_eepromv1.js b/src/ami.js/z_eepromv1.js
index 615fb13..cdc983b 100644
--- a/src/ami.js/z_eepromv1.js
+++ b/src/ami.js/z_eepromv1.js
@@ -1,6 +1,5 @@
 #include common.js
 #include eeprom.js
-#include utility.js
 
 var eepromAppl = 1,
     eepromSupportImageEmbed = true,
@@ -10,13 +9,13 @@ var eepromInitialise = function(auth) {
     window.eepromHistory = {};
     window.eepromClient = new AmiEEPROM(futami.get('eeprom2'), auth.getHttpAuth);
 
-    window.eepromUploadsTable = $i('uploadHistory');
-    $i('uploadSelector').onchange = function() {
+    window.eepromUploadsTable = $id('uploadHistory');
+    $id('uploadSelector').onchange = function() {
         for(var i = 0; i < this.files.length; ++i)
             eepromFileUpload(this.files[i]);
     };
-    $i('uploadButton').onclick = function() {
-        $i('uploadSelector').click();
+    $id('uploadButton').onclick = function() {
+        $id('uploadSelector').click();
     };
 
     ami.chat.inputBox.watch('paste', function(ev) {