diff --git a/VERSION b/VERSION
index 91d54fb4..decc1c18 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-20250202.2
+20250202.3
diff --git a/assets/common.css/main.css b/assets/common.css/main.css
new file mode 100644
index 00000000..1dcafd4a
--- /dev/null
+++ b/assets/common.css/main.css
@@ -0,0 +1,22 @@
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+    position: relative;
+}
+
+html, body {
+    width: 100%;
+    height: 100%;
+}
+
+[hidden],
+.hidden {
+    display: none !important;
+    visibility: hidden !important;
+}
+
+:root {
+    --font-regular: Verdana, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
+    --font-monospace: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+}
diff --git a/assets/common.js/array.js b/assets/common.js/array.js
new file mode 100644
index 00000000..d3a05411
--- /dev/null
+++ b/assets/common.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/assets/misuzu.js/csrf.js b/assets/common.js/csrf.js
similarity index 83%
rename from assets/misuzu.js/csrf.js
rename to assets/common.js/csrf.js
index 7df0610b..7f637da3 100644
--- a/assets/misuzu.js/csrf.js
+++ b/assets/common.js/csrf.js
@@ -1,10 +1,10 @@
-#include utility.js
+#include html.js
 
-const MszCSRF = (() => {
+const $csrf = (() => {
     let elem;
     const getElement = () => {
         if(elem === undefined)
-            elem = $q('meta[name="csrf-token"]');
+            elem = $query('meta[name="csrf-token"]');
         return elem;
     };
 
diff --git a/assets/oauth2.js/utility.js b/assets/common.js/html.js
similarity index 69%
rename from assets/oauth2.js/utility.js
rename to assets/common.js/html.js
index 28bc48cd..b1fb2caf 100644
--- a/assets/oauth2.js/utility.js
+++ b/assets/common.js/html.js
@@ -1,32 +1,20 @@
-const $i = document.getElementById.bind(document);
-const $c = document.getElementsByClassName.bind(document);
-const $q = document.querySelector.bind(document);
-const $qa = document.querySelectorAll.bind(document);
-const $t = document.createTextNode.bind(document);
+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 $r = function(element) {
-    if(element && element.parentNode)
-        element.parentNode.removeChild(element);
-};
-
-const $ri = function(name) {
-    $r($i(name));
-};
-
-const $rq = function(query) {
-    $r($q(query));
-};
-
-const $ib = function(ref, elem) {
+const $insertBefore = function(ref, elem) {
     ref.parentNode.insertBefore(elem, ref);
 };
 
-const $rc = function(element) {
-    while(element.lastChild)
-        element.removeChild(element.lastChild);
+const $removeChildren = function(element) {
+    while(element.firstChild)
+        element.firstChild.remove();
 };
 
-const $e = function(info, attrs, child, created) {
+const $jsx = (type, props, ...children) => $create({ tag: type, attrs: props, child: children });
+
+const $create = function(info, attrs, child, created) {
     info = info || {};
 
     if(typeof info === 'string') {
@@ -117,15 +105,15 @@ const $e = function(info, attrs, child, created) {
                         if(childElem instanceof Element)
                             elem.appendChild(childElem);
                         else
-                            elem.appendChild($e(child));
+                            elem.appendChild($create(child));
                     } else if('getElement' in child) {
                         const childElem = child.getElement();
                         if(childElem instanceof Element)
                             elem.appendChild(childElem);
                         else
-                            elem.appendChild($e(child));
+                            elem.appendChild($create(child));
                     } else {
-                        elem.appendChild($e(child));
+                        elem.appendChild($create(child));
                     }
                     break;
 
@@ -141,30 +129,3 @@ const $e = function(info, attrs, child, created) {
 
     return elem;
 };
-const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });
-
-const $ar = function(array, index) {
-    array.splice(index, 1);
-};
-const $ari = function(array, item) {
-    let index;
-    while(array.length > 0 && (index = array.indexOf(item)) >= 0)
-        $ar(array, index);
-};
-const $arf = function(array, predicate) {
-    let index;
-    while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
-        $ar(array, index);
-};
-
-const $as = function(array) {
-    if(array.length < 2)
-        return;
-
-    for(let i = array.length - 1; i > 0; --i) {
-        let j = Math.floor(Math.random() * (i + 1)),
-            tmp = array[i];
-        array[i] = array[j];
-        array[j] = tmp;
-    }
-};
diff --git a/assets/common.js/main.js b/assets/common.js/main.js
new file mode 100644
index 00000000..d8d77481
--- /dev/null
+++ b/assets/common.js/main.js
@@ -0,0 +1,5 @@
+#include array.js
+#include csrf.js
+#include html.js
+#include uniqstr.js
+#include xhr.js
diff --git a/assets/misuzu.js/uniqstr.js b/assets/common.js/uniqstr.js
similarity index 81%
rename from assets/misuzu.js/uniqstr.js
rename to assets/common.js/uniqstr.js
index 966ca6b6..96a2fc9c 100644
--- a/assets/misuzu.js/uniqstr.js
+++ b/assets/common.js/uniqstr.js
@@ -1,4 +1,4 @@
-const MszRandomInt = function(min, max) {
+const $rngi = function(min, max) {
     let ret = 0;
     const range = max - min;
 
@@ -21,18 +21,18 @@ const MszRandomInt = function(min, max) {
     ret &= mask;
 
     if(ret >= range)
-        return MszRandomInt(min, max);
+        return $rngi(min, max);
 
     return min + ret;
 };
 
-const MszUniqueStr = (function() {
+const $rngs = (function() {
     const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789';
 
     return function(length) {
         let str = '';
         for(let i = 0; i < length; ++i)
-            str += chars[MszRandomInt(0, chars.length)];
+            str += chars[$rngi(0, chars.length)];
         return str;
     };
 })();
diff --git a/assets/oauth2.js/xhr.js b/assets/common.js/xhr.js
similarity index 96%
rename from assets/oauth2.js/xhr.js
rename to assets/common.js/xhr.js
index c6e4b870..4940aff0 100644
--- a/assets/oauth2.js/xhr.js
+++ b/assets/common.js/xhr.js
@@ -1,6 +1,6 @@
 #include csrf.js
 
-const $x = (function() {
+const $xhr = (function() {
     const send = function(method, url, options, body) {
         if(options === undefined)
             options = {};
@@ -18,7 +18,7 @@ const $x = (function() {
                     requestHeaders.set(name.toLowerCase(), options.headers[name]);
 
         if(options.csrf)
-            requestHeaders.set('x-csrf-token', MszCSRF.token);
+            requestHeaders.set('x-csrf-token', $csrf.token);
 
         if(typeof options.download === 'function') {
             xhr.onloadstart = ev => options.download(ev);
@@ -81,7 +81,7 @@ const $x = (function() {
                 })(xhr.getAllResponseHeaders());
 
                 if(options.csrf && headers.has('x-csrf-token'))
-                    MszCSRF.token = headers.get('x-csrf-token');
+                    $csrf.token = headers.get('x-csrf-token');
 
                 resolve({
                     get ev() { return ev; },
diff --git a/assets/misuzu.css/main.css b/assets/misuzu.css/main.css
index d761e8a0..f4088bce 100644
--- a/assets/misuzu.css/main.css
+++ b/assets/misuzu.css/main.css
@@ -1,27 +1,6 @@
-* {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-    position: relative;
-}
-
-html,
-body {
-    width: 100%;
-    height: 100%;
-}
-
-[hidden],
-.hidden {
-    display: none !important;
-    visibility: hidden !important;
-}
-
 :root {
     --font-size: 12px;
     --line-height: 20px;
-    --font-regular: Verdana, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
-    --font-monospace: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
 
     --site-max-width: 1200px;
     --site-mobile-width: 800px;
diff --git a/assets/misuzu.js/embed/audio.js b/assets/misuzu.js/embed/audio.js
index ad61f37d..fdba11b0 100644
--- a/assets/misuzu.js/embed/audio.js
+++ b/assets/misuzu.js/embed/audio.js
@@ -1,4 +1,3 @@
-#include utility.js
 #include watcher.js
 
 const MszAudioEmbedPlayerEvents = function() {
@@ -10,7 +9,7 @@ const MszAudioEmbedPlayerEvents = function() {
 };
 
 const MszAudioEmbed = function(player) {
-    const elem = $e({
+    const elem = $create({
         attrs: {
             classList: ['aembed', 'aembed-' + player.getType()],
         },
@@ -25,14 +24,14 @@ const MszAudioEmbed = function(player) {
             target.appendChild(elem);
         },
         insertBefore: function(ref) {
-            $ib(ref, elem);
+            $insertBefore(ref, elem);
         },
         nuke: function() {
-            $r(elem);
+            elem.remove();
         },
         replaceElement(target) {
-            $ib(target, elem);
-            $r(target);
+            $insertBefore(target, elem);
+            target.remove();
         },
         getPlayer: function() {
             return player;
@@ -59,7 +58,7 @@ const MszAudioEmbedPlayer = function(metadata, options) {
     const watchers = new MszWatchers;
     watchers.define(MszAudioEmbedPlayerEvents());
 
-    const player = $e({
+    const player = $create({
         tag: 'audio',
         attrs: playerAttrs,
     });
@@ -72,14 +71,14 @@ const MszAudioEmbedPlayer = function(metadata, options) {
             target.appendChild(player);
         },
         insertBefore: function(ref) {
-            $ib(ref, player);
+            $insertBefore(ref, player);
         },
         nuke: function() {
-            $r(player);
+            player.remove();
         },
         replaceElement(target) {
-            $ib(target, player);
-            $r(target);
+            $insertBefore(target, player);
+            target.remove();
         },
         getType: function() { return 'external'; },
     };
@@ -236,7 +235,7 @@ const MszAudioEmbedPlaceholder = function(metadata, options) {
     if(typeof metadata.color !== 'undefined')
         style.push('--aembedph-colour: ' + metadata.color);
 
-    const coverBackground = $e({
+    const coverBackground = $create({
         attrs: {
             className: 'aembedph-bg',
         },
@@ -252,7 +251,7 @@ const MszAudioEmbedPlaceholder = function(metadata, options) {
         },
     });
 
-    const coverPreview = $e({
+    const coverPreview = $create({
         attrs: {
             className: 'aembedph-info-cover',
         },
@@ -270,7 +269,7 @@ const MszAudioEmbedPlaceholder = function(metadata, options) {
 
     const pub = {};
 
-    const elem = $e({
+    const elem = $create({
         attrs: {
             className: ('aembedph aembedph-' + (options.type || 'external')),
             style: style.join(';'),
@@ -355,13 +354,13 @@ const MszAudioEmbedPlaceholder = function(metadata, options) {
 
     pub.getElement = function() { return elem; };
     pub.appendTo = function(target) { target.appendChild(elem); };
-    pub.insertBefore = function(ref) { $ib(ref, elem); };
+    pub.insertBefore = function(ref) { $insertBefore(ref, elem); };
     pub.nuke = function() {
-        $r(elem);
+        elem.remove();
     };
     pub.replaceElement = function(target) {
-        $ib(target, elem);
-        $r(target);
+        $insertBefore(target, elem);
+        target.remove();
     };
 
     return pub;
diff --git a/assets/misuzu.js/embed/embed.js b/assets/misuzu.js/embed/embed.js
index a9403be0..08201ad4 100644
--- a/assets/misuzu.js/embed/embed.js
+++ b/assets/misuzu.js/embed/embed.js
@@ -1,4 +1,3 @@
-#include utility.js
 #include embed/audio.js
 #include embed/image.js
 #include embed/video.js
@@ -30,8 +29,8 @@ const MszEmbed = (function() {
                     continue;
                 }
 
-                $rc(target);
-                target.appendChild($e({
+                $removeChildren(target);
+                target.appendChild($create({
                     tag: 'i',
                     attrs: {
                         className: 'fas fa-2x fa-spinner fa-pulse',
@@ -52,7 +51,7 @@ const MszEmbed = (function() {
 
             const replaceWithUrl = function(targets, url) {
                 for(const target of targets) {
-                    let body = $e({
+                    let body = $create({
                         tag: 'a',
                         attrs: {
                             className: 'link',
@@ -62,8 +61,8 @@ const MszEmbed = (function() {
                         },
                         child: url
                     });
-                    $ib(target, body);
-                    $r(target);
+                    $insertBefore(target, body);
+                    target.remove();
                 }
             };
 
diff --git a/assets/misuzu.js/embed/image.js b/assets/misuzu.js/embed/image.js
index 9cd387f2..4dddb6bd 100644
--- a/assets/misuzu.js/embed/image.js
+++ b/assets/misuzu.js/embed/image.js
@@ -1,9 +1,7 @@
-#include utility.js
-
 const MszImageEmbed = function(metadata, options, target) {
     options = options || {};
 
-    const image = $e({
+    const image = $create({
         tag: 'img',
         attrs: {
             alt: target.dataset.mszEmbedAlt || '',
@@ -19,14 +17,14 @@ const MszImageEmbed = function(metadata, options, target) {
             target.appendChild(image);
         },
         insertBefore: function(ref) {
-            $ib(ref, image);
+            $insertBefore(ref, image);
         },
         nuke: function() {
-            $r(image);
+            image.remove();
         },
         replaceElement(target) {
-            $ib(target, image);
-            $r(target);
+            $insertBefore(target, image);
+            target.remove();
         },
         getType: function() { return 'external'; },
     };
diff --git a/assets/misuzu.js/embed/video.js b/assets/misuzu.js/embed/video.js
index 569816e7..8c30d064 100644
--- a/assets/misuzu.js/embed/video.js
+++ b/assets/misuzu.js/embed/video.js
@@ -1,5 +1,3 @@
-#include utility.js
-#include uniqstr.js
 #include watcher.js
 
 const MszVideoEmbedPlayerEvents = function() {
@@ -49,7 +47,7 @@ const MszVideoEmbed = function(playerOrFrame) {
     const frame = playerOrFrame;
     const player = 'getPlayer' in frame ? frame.getPlayer() : frame;
 
-    const elem = $e({
+    const elem = $create({
         attrs: {
             classList: ['embed', 'embed-' + player.getType()],
         },
@@ -64,14 +62,14 @@ const MszVideoEmbed = function(playerOrFrame) {
             target.appendChild(elem);
         },
         insertBefore: function(ref) {
-            $ib(ref, elem);
+            $insertBefore(ref, elem);
         },
         nuke: function() {
-            $r(elem);
+            elem.remove();
         },
         replaceElement(target) {
-            $ib(target, elem);
-            $r(target);
+            $insertBefore(target, elem);
+            target.remove();
         },
         getFrame: function() {
             return frame;
@@ -93,7 +91,7 @@ const MszVideoEmbedFrame = function(player, options) {
         icoVolQuiet = 'fa-volume-down',
         icoVolLoud = 'fa-volume-up';
 
-    const btnPlayPause = $e({
+    const btnPlayPause = $create({
         attrs: {},
         child: {
             tag: 'i',
@@ -103,7 +101,7 @@ const MszVideoEmbedFrame = function(player, options) {
         }
     });
 
-    const btnStop = $e({
+    const btnStop = $create({
         attrs: {},
         child: {
             tag: 'i',
@@ -113,20 +111,20 @@ const MszVideoEmbedFrame = function(player, options) {
         },
     });
 
-    const numCurrentTime = $e({
+    const numCurrentTime = $create({
         attrs: {},
     });
 
-    const sldProgress = $e({
+    const sldProgress = $create({
         attrs: {},
         child: [],
     });
 
-    const numDurationRemaining = $e({
+    const numDurationRemaining = $create({
         attrs: {},
     });
 
-    const btnVolMute = $e({
+    const btnVolMute = $create({
         attrs: {},
         child: {
             tag: 'i',
@@ -140,7 +138,7 @@ const MszVideoEmbedFrame = function(player, options) {
         },
     });
 
-    const elem = $e({
+    const elem = $create({
         attrs: {
             className: 'embedvf',
             style: {
@@ -185,14 +183,14 @@ const MszVideoEmbedFrame = function(player, options) {
             target.appendChild(elem);
         },
         insertBefore: function(ref) {
-            $ib(ref, elem);
+            $insertBefore(ref, elem);
         },
         nuke: function() {
-            $r(elem);
+            elem.remove();
         },
         replaceElement(target) {
-            $ib(target, elem);
-            $r(target);
+            $insertBefore(target, elem);
+            target.remove();
         },
         getPlayer: function() {
             return player;
@@ -232,7 +230,7 @@ const MszVideoEmbedPlayer = function(metadata, options) {
     const watchers = new MszWatchers;
     watchers.define(MszVideoEmbedPlayerEvents());
 
-    const player = $e({
+    const player = $create({
         tag: 'video',
         attrs: videoAttrs,
     });
@@ -251,14 +249,14 @@ const MszVideoEmbedPlayer = function(metadata, options) {
             target.appendChild(player);
         },
         insertBefore: function(ref) {
-            $ib(ref, player);
+            $insertBefore(ref, player);
         },
         nuke: function() {
-            $r(player);
+            player.remove();
         },
         replaceElement(target) {
-            $ib(target, player);
-            $r(target);
+            $insertBefore(target, player);
+            target.remove();
         },
         getType: function() { return 'external'; },
         getWidth: function() { return width; },
@@ -350,7 +348,7 @@ const MszVideoEmbedYouTube = function(metadata, options) {
     options = options || {};
 
     const ytOrigin = 'https://www.youtube.com',
-        playerId = 'yt-' + MszUniqueStr(8),
+        playerId = 'yt-' + $rngs(8),
         shouldAutoplay = options.autoplay === undefined || options.autoplay;
 
     let embedUrl = 'https://www.youtube.com/embed/' + metadata.youtube_video_id + '?enablejsapi=1';
@@ -378,7 +376,7 @@ const MszVideoEmbedYouTube = function(metadata, options) {
     const watchers = new MszWatchers;
     watchers.define(MszVideoEmbedPlayerEvents());
 
-    const player = $e({
+    const player = $create({
         tag: 'iframe',
         attrs: {
             frameborder: 0,
@@ -396,14 +394,14 @@ const MszVideoEmbedYouTube = function(metadata, options) {
             target.appendChild(player);
         },
         insertBefore: function(ref) {
-            $ib(ref, player);
+            $insertBefore(ref, player);
         },
         nuke: function() {
-            $r(player);
+            player.remove();
         },
         replaceElement(target) {
-            $ib(target, player);
-            $r(target);
+            $insertBefore(target, player);
+            target.remove();
         },
         getType: function() { return 'youtube'; },
         getWidth: function() { return 560; },
@@ -562,7 +560,7 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
     options = options || {};
 
     const nndOrigin = 'https://embed.nicovideo.jp',
-        playerId = 'nnd-' + MszUniqueStr(8),
+        playerId = 'nnd-' + $rngs(8),
         shouldAutoplay = options.autoplay === undefined || options.autoplay;
 
     let embedUrl = 'https://embed.nicovideo.jp/watch/' + metadata.nicovideo_video_id + '?jsapi=1&playerId=' + playerId;
@@ -579,7 +577,7 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
     const watchers = new MszWatchers;
     watchers.define(MszVideoEmbedPlayerEvents());
 
-    const player = $e({
+    const player = $create({
         tag: 'iframe',
         attrs: {
             frameborder: 0,
@@ -597,14 +595,14 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
             target.appendChild(player);
         },
         insertBefore: function(ref) {
-            $ib(ref, player);
+            $insertBefore(ref, player);
         },
         nuke: function() {
-            $r(player);
+            player.remove();
         },
         replaceElement(target) {
-            $ib(target, player);
-            $r(target);
+            $insertBefore(target, player);
+            target.remove();
         },
         getType: function() { return 'nicovideo'; },
         getWidth: function() { return 640; },
@@ -792,7 +790,7 @@ const MszVideoEmbedPlaceholder = function(metadata, options) {
 
     const pub = {};
 
-    const elem = $e({
+    const elem = $create({
         attrs: {
             className: ('embedph embedph-' + (options.type || 'external')),
             style: style.join(';'),
@@ -894,13 +892,13 @@ const MszVideoEmbedPlaceholder = function(metadata, options) {
 
     pub.getElement = function() { return elem; };
     pub.appendTo = function(target) { target.appendChild(elem); };
-    pub.insertBefore = function(ref) { $ib(ref, elem); };
+    pub.insertBefore = function(ref) { $insertBefore(ref, elem); };
     pub.nuke = function() {
-        $r(elem);
+        elem.remove();
     };
     pub.replaceElement = function(target) {
-        $ib(target, elem);
-        $r(target);
+        $insertBefore(target, elem);
+        target.remove();
     };
 
     return pub;
diff --git a/assets/misuzu.js/events/christmas2019.js b/assets/misuzu.js/events/christmas2019.js
index bebdc27c..21254683 100644
--- a/assets/misuzu.js/events/christmas2019.js
+++ b/assets/misuzu.js/events/christmas2019.js
@@ -1,5 +1,3 @@
-#include utility.js
-
 const MszChristmas2019EventInfo = function() {
     return {
         isActive: () => {
@@ -16,8 +14,8 @@ const MszChristmas2019EventInfo = function() {
 
 const MszChristmas2019Event = function() {
     const propName = 'msz-christmas-' + (new Date).getFullYear().toString();
-    const headerBg = $q('.header__background');
-    const menuBgs = Array.from($qa('.header__desktop__submenu__background'));
+    const headerBg = $query('.header__background');
+    const menuBgs = Array.from($queryAll('.header__desktop__submenu__background'));
 
     if(!localStorage.getItem(propName))
         localStorage.setItem(propName, '0');
diff --git a/assets/misuzu.js/ext/eeprom.js b/assets/misuzu.js/ext/eeprom.js
index 80abdecc..b9527d0b 100644
--- a/assets/misuzu.js/ext/eeprom.js
+++ b/assets/misuzu.js/ext/eeprom.js
@@ -1,5 +1,3 @@
-#include xhr.js
-
 const MszEEPROM = function(appId, endPoint) {
     if(typeof appId !== 'string')
         throw 'appId must be a string';
@@ -44,7 +42,7 @@ const MszEEPROM = function(appId, endPoint) {
                         formData.append('src', appId);
                         formData.append('file', fileInput);
 
-                        const { status, body } = await $x.post(`${endPoint}/uploads`, {
+                        const { status, body } = await $xhr.post(`${endPoint}/uploads`, {
                             type: 'json',
                             authed: true,
                             upload: reportProgress,
@@ -79,7 +77,7 @@ const MszEEPROM = function(appId, endPoint) {
             if(typeof fileInfo.urlf !== 'string')
                 throw 'fileInfo.urlf must be a string';
 
-            const { status, body } = await $x.delete(fileInfo.urlf, {
+            const { status, body } = await $xhr.delete(fileInfo.urlf, {
                 type: 'json',
                 authed: true,
             });
diff --git a/assets/misuzu.js/ext/uiharu.js b/assets/misuzu.js/ext/uiharu.js
index b288a8dd..089be168 100644
--- a/assets/misuzu.js/ext/uiharu.js
+++ b/assets/misuzu.js/ext/uiharu.js
@@ -1,5 +1,3 @@
-#include xhr.js
-
 const MszUiharu = function(apiUrl) {
     const maxBatchSize = 4;
     const lookupOneUrl = apiUrl + '/metadata';
@@ -9,7 +7,7 @@ const MszUiharu = function(apiUrl) {
             if(typeof targetUrl !== 'string')
                 throw 'targetUrl must be a string';
 
-            return $x.post(lookupOneUrl, { type: 'json' }, targetUrl);
+            return $xhr.post(lookupOneUrl, { type: 'json' }, targetUrl);
         },
     };
 };
diff --git a/assets/misuzu.js/forum/editor.jsx b/assets/misuzu.js/forum/editor.jsx
index 4f895722..d0719538 100644
--- a/assets/misuzu.js/forum/editor.jsx
+++ b/assets/misuzu.js/forum/editor.jsx
@@ -1,7 +1,6 @@
 #include msgbox.jsx
 #include parsing.js
 #include utility.js
-#include xhr.js
 #include ext/eeprom.js
 
 let MszForumEditorAllowClose = false;
@@ -38,12 +37,9 @@ const MszForumEditor = function(form) {
             </div>
         </div>;
 
-        if(eepromHistory.children.length > 0)
-            $ib(eepromHistory.firstChild, uploadElem);
-        else
-            eepromHistory.appendChild(uploadElem);
+        eepromHistory.insertAdjacentElement('afterbegin', uploadElem);
 
-        const explodeUploadElem = () => $r(uploadElem);
+        const explodeUploadElem = () => { uploadElem.remove(); };
         const uploadTask = eepromClient.create(file);
 
         uploadTask.onProgress(prog => {
@@ -79,7 +75,7 @@ const MszForumEditor = function(form) {
             };
 
             uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => insertTheLinkIntoTheBoxEx2()}>Insert</a>);
-            uploadElemProgressText.appendChild($t(' '));
+            uploadElemProgressText.appendChild($text(' '));
             uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => {
                 eepromClient.delete(fileInfo)
                     .then(() => explodeUploadElem())
@@ -172,7 +168,7 @@ const MszForumEditor = function(form) {
     });
 
     const switchButtons = parser => {
-        $rc(markupActs);
+        $removeChildren(markupActs);
 
         const tags = MszParsing.getTagsFor(parser);
         for(const tag of tags)
@@ -191,7 +187,7 @@ const MszForumEditor = function(form) {
         formData.append('post[text]', text);
         formData.append('post[parser]', parseInt(parser));
 
-        return (await $x.post('/forum/posting.php', { authed: true }, formData)).body;
+        return (await $xhr.post('/forum/posting.php', { authed: true }, formData)).body;
     };
 
     const previewBtn = <button class="input__button" type="button" value="preview">Preview</button>;
@@ -233,7 +229,7 @@ const MszForumEditor = function(form) {
                     lastPostParser = postParser;
                     previewElem.innerHTML = body;
 
-                    MszEmbed.handle($qa('.js-msz-embed-media'));
+                    MszEmbed.handle($queryAll('.js-msz-embed-media'));
 
                     previewElem.removeAttribute('hidden');
                     textElem.setAttribute('hidden', 'hidden');
@@ -276,7 +272,7 @@ const MszForumEditor = function(form) {
                 lastPostParser = postParser;
                 previewElem.innerHTML = body;
 
-                MszEmbed.handle($qa('.js-msz-embed-media'));
+                MszEmbed.handle($queryAll('.js-msz-embed-media'));
 
                 previewBtn.removeAttribute('disabled');
                 parserElem.removeAttribute('disabled');
diff --git a/assets/misuzu.js/main.js b/assets/misuzu.js/main.js
index d1e4b222..5f6c5dc9 100644
--- a/assets/misuzu.js/main.js
+++ b/assets/misuzu.js/main.js
@@ -1,6 +1,4 @@
 #include msgbox.jsx
-#include utility.js
-#include xhr.js
 #include embed/embed.js
 #include events/christmas2019.js
 #include events/events.js
@@ -10,7 +8,7 @@
 
 (async () => {
     const initLoginPage = async () => {
-        const forms = Array.from($qa('.js-login-form'));
+        const forms = Array.from($queryAll('.js-login-form'));
         if(forms.length < 1)
             return;
 
@@ -18,7 +16,7 @@
             if(!(avatar instanceof Element) || !(userName instanceof Element))
                 return;
 
-            const { body } = await $x.get(`/auth/login.php?resolve=1&name=${encodeURIComponent(userName.value)}`, { type: 'json' });
+            const { body } = await $xhr.get(`/auth/login.php?resolve=1&name=${encodeURIComponent(userName.value)}`, { type: 'json' });
 
             avatar.src = body.avatar;
             if(body.name.length > 0)
@@ -48,7 +46,7 @@
     };
 
     const initQuickSubmit = () => {
-        const elems = Array.from($qa('.js-quick-submit, .js-ctrl-enter-submit'));
+        const elems = Array.from($queryAll('.js-quick-submit, .js-ctrl-enter-submit'));
         if(elems.length < 1)
             return;
 
@@ -66,7 +64,7 @@
     };
 
     const initXhrActions = () => {
-        const targets = Array.from($qa('a[data-url], button[data-url]'));
+        const targets = Array.from($queryAll('a[data-url], button[data-url]'));
         for(const target of targets) {
             target.onclick = async () => {
                 if(target.disabled)
@@ -89,7 +87,7 @@
                     if(target.dataset.confirm && !await MszShowConfirmBox(target.dataset.confirm, 'Are you sure?'))
                         return;
 
-                    const { status, body } = await $x.send(
+                    const { status, body } = await $xhr.send(
                         target.dataset.method ?? 'GET',
                         url,
                         {
@@ -125,7 +123,7 @@
     };
 
     try {
-        MszSakuya.trackElements($qa('time'));
+        MszSakuya.trackElements($queryAll('time'));
         hljs.highlightAll();
 
         MszEmbed.init(`${location.protocol}//uiharu.${location.host}`);
@@ -134,7 +132,7 @@
 
         // only used by the forum posting form
         initQuickSubmit();
-        const forumPostingForm = $q('.js-forum-posting');
+        const forumPostingForm = $query('.js-forum-posting');
         if(forumPostingForm !== null)
             MszForumEditor(forumPostingForm);
 
@@ -146,7 +144,7 @@
 
         MszMessages();
 
-        MszEmbed.handle($qa('.js-msz-embed-media'));
+        MszEmbed.handle($queryAll('.js-msz-embed-media'));
     } catch(ex) {
         console.error(ex);
     }
diff --git a/assets/misuzu.js/messages/list.js b/assets/misuzu.js/messages/list.js
index 45f12c1a..8a43bb7b 100644
--- a/assets/misuzu.js/messages/list.js
+++ b/assets/misuzu.js/messages/list.js
@@ -1,4 +1,3 @@
-#include utility.js
 #include watcher.js
 
 const MsgMessagesList = function(list) {
@@ -55,8 +54,8 @@ const MsgMessagesList = function(list) {
             return selected;
         },
         removeItem: item => {
-            $ari(items, item);
-            $r(item.getElement());
+            $arrayRemoveValue(items, item);
+            item.getElement().remove();
             recountSelected();
             watchers.call('select', selectedCount, items.length);
         },
diff --git a/assets/misuzu.js/messages/messages.js b/assets/misuzu.js/messages/messages.js
index 4f8f4d7c..597af8ec 100644
--- a/assets/misuzu.js/messages/messages.js
+++ b/assets/misuzu.js/messages/messages.js
@@ -1,6 +1,4 @@
 #include msgbox.jsx
-#include utility.js
-#include xhr.js
 #include messages/actbtn.js
 #include messages/list.js
 #include messages/recipient.js
@@ -40,7 +38,7 @@ const MszMessages = () => {
         formData.append('recipient', recipient);
         formData.append('reply', replyTo);
 
-        const { body } = await $x.post('/messages/create', { type: 'json', csrf: true }, formData);
+        const { body } = await $xhr.post('/messages/create', { type: 'json', csrf: true }, formData);
         if(body.error !== undefined)
             throw body.error;
 
@@ -54,7 +52,7 @@ const MszMessages = () => {
         formData.append('parser', parser);
         formData.append('draft', draft);
 
-        const { body } = await $x.post(`/messages/${encodeURIComponent(messageId)}`, { type: 'json', csrf: true }, formData);
+        const { body } = await $xhr.post(`/messages/${encodeURIComponent(messageId)}`, { type: 'json', csrf: true }, formData);
         if(body.error !== undefined)
             throw body.error;
 
@@ -62,7 +60,7 @@ const MszMessages = () => {
     };
 
     const msgsMark = async (msgs, state) => {
-        const { body } = await $x.post('/messages/mark', { type: 'json', csrf: true }, {
+        const { body } = await $xhr.post('/messages/mark', { type: 'json', csrf: true }, {
             type: state,
             messages: msgs.map(extractMsgIds).join(','),
         });
@@ -73,7 +71,7 @@ const MszMessages = () => {
     };
 
     const msgsDelete = async msgs => {
-        const { body } = await $x.post('/messages/delete', { type: 'json', csrf: true }, {
+        const { body } = await $xhr.post('/messages/delete', { type: 'json', csrf: true }, {
             messages: msgs.map(extractMsgIds).join(','),
         });
         if(body.error !== undefined)
@@ -83,7 +81,7 @@ const MszMessages = () => {
     };
 
     const msgsRestore = async msgs => {
-        const { body } = await $x.post('/messages/restore', { type: 'json', csrf: true }, {
+        const { body } = await $xhr.post('/messages/restore', { type: 'json', csrf: true }, {
             messages: msgs.map(extractMsgIds).join(','),
         });
         if(body.error !== undefined)
@@ -93,7 +91,7 @@ const MszMessages = () => {
     };
 
     const msgsNuke = async msgs => {
-        const { body } = await $x.post('/messages/nuke', { type: 'json', csrf: true }, {
+        const { body } = await $xhr.post('/messages/nuke', { type: 'json', csrf: true }, {
             messages: msgs.map(extractMsgIds).join(','),
         });
         if(body.error !== undefined)
@@ -102,28 +100,28 @@ const MszMessages = () => {
         return true;
     };
 
-    const msgsUserBtns = Array.from($qa('.js-header-pms-button'));
+    const msgsUserBtns = Array.from($queryAll('.js-header-pms-button'));
     if(msgsUserBtns.length > 0)
-        $x.get('/messages/stats', { type: 'json' }).then(result => {
+        $xhr.get('/messages/stats', { type: 'json' }).then(result => {
             const body = result.body;
             if(typeof body === 'object' && typeof body.unread === 'number')
                 if(body.unread > 0)
                     for(const msgsUserBtn of msgsUserBtns)
-                        msgsUserBtn.append($e({ child: body.unread.toLocaleString(), attrs: { className: 'header__desktop__user__button__count' } }));
+                        msgsUserBtn.append($create({ child: body.unread.toLocaleString(), attrs: { className: 'header__desktop__user__button__count' } }));
         });
 
-    const msgsListElem = $q('.js-messages-list');
+    const msgsListElem = $query('.js-messages-list');
     const msgsList = msgsListElem instanceof Element ? new MsgMessagesList(msgsListElem) : undefined;
 
-    const msgsListEmptyNotice = $q('.js-messages-folder-empty');
+    const msgsListEmptyNotice = $query('.js-messages-folder-empty');
 
-    const msgsThreadElem = $q('.js-messages-thread');
+    const msgsThreadElem = $query('.js-messages-thread');
     const msgsThread = msgsThreadElem instanceof Element ? new MszMessagesThread(msgsThreadElem) : undefined;
 
-    const msgsRecipientElem = $q('.js-messages-recipient');
+    const msgsRecipientElem = $query('.js-messages-recipient');
     const msgsRecipient = msgsRecipientElem instanceof Element ? new MszMessagesRecipient(msgsRecipientElem) : undefined;
 
-    const msgsReplyElem = $q('.js-messages-reply');
+    const msgsReplyElem = $query('.js-messages-reply');
     const msgsReply = msgsReplyElem instanceof Element ? new MszMessagesReply(msgsReplyElem) : undefined;
 
     if(msgsReply !== undefined) {
@@ -165,7 +163,7 @@ const MszMessages = () => {
 
     let actSelectAll, actMarkRead, actMoveTrash, actNuke;
 
-    const actSelectAllBtn = $q('.js-messages-actions-select-all');
+    const actSelectAllBtn = $query('.js-messages-actions-select-all');
     if(actSelectAllBtn instanceof Element) {
         actSelectAll = new MszMessagesActionButton(actSelectAllBtn);
 
@@ -181,7 +179,7 @@ const MszMessages = () => {
         }
     }
 
-    const actMarkReadBtn = $q('.js-messages-actions-mark-read');
+    const actMarkReadBtn = $query('.js-messages-actions-mark-read');
     if(actMarkReadBtn instanceof Element) {
         actMarkRead = new MszMessagesActionButton(actMarkReadBtn);
 
@@ -241,7 +239,7 @@ const MszMessages = () => {
         }
     }
 
-    const actMoveTrashBtn = $q('.js-messages-actions-move-trash');
+    const actMoveTrashBtn = $query('.js-messages-actions-move-trash');
     if(actMoveTrashBtn instanceof Element) {
         actMoveTrash = new MszMessagesActionButton(actMoveTrashBtn);
 
@@ -303,7 +301,7 @@ const MszMessages = () => {
         }
     }
 
-    const actNukeBtn = $q('.js-messages-actions-nuke');
+    const actNukeBtn = $query('.js-messages-actions-nuke');
     if(actNukeBtn instanceof Element) {
         actNuke = new MszMessagesActionButton(actNukeBtn, true);
 
diff --git a/assets/misuzu.js/messages/recipient.js b/assets/misuzu.js/messages/recipient.js
index 4dcb2591..3ebfd9f5 100644
--- a/assets/misuzu.js/messages/recipient.js
+++ b/assets/misuzu.js/messages/recipient.js
@@ -1,5 +1,3 @@
-#include xhr.js
-
 const MszMessagesRecipient = function(element) {
     if(!(element instanceof Element))
         throw 'element must be an instance of Element';
@@ -9,7 +7,7 @@ const MszMessagesRecipient = function(element) {
 
     let updateHandler = undefined;
     const update = async () => {
-        const { body } = await $x.post(element.dataset.msgLookup, { type: 'json', csrf: true }, {
+        const { body } = await $xhr.post(element.dataset.msgLookup, { type: 'json', csrf: true }, {
             name: nameInput.value,
         });
 
diff --git a/assets/misuzu.js/messages/reply.jsx b/assets/misuzu.js/messages/reply.jsx
index 4660a8f5..3d41cb7d 100644
--- a/assets/misuzu.js/messages/reply.jsx
+++ b/assets/misuzu.js/messages/reply.jsx
@@ -1,4 +1,5 @@
 #include parsing.js
+#include utility.js
 #include ext/eeprom.js
 
 const MszMessagesReply = function(element) {
@@ -46,7 +47,7 @@ const MszMessagesReply = function(element) {
     });
 
     const switchButtons = parser => {
-        $rc(actsElem);
+        $removeChildren(actsElem);
 
         const tags = MszParsing.getTagsFor(parser);
         actsElem.hidden = tags.length < 1;
diff --git a/assets/misuzu.js/msgbox.jsx b/assets/misuzu.js/msgbox.jsx
index 0051eefa..4afa4377 100644
--- a/assets/misuzu.js/msgbox.jsx
+++ b/assets/misuzu.js/msgbox.jsx
@@ -1,5 +1,3 @@
-#include utility.js
-
 const MszShowConfirmBox = async (text, title, target) => {
     let result = false;
 
diff --git a/assets/misuzu.js/utility.js b/assets/misuzu.js/utility.js
index cf35cc39..15a8d657 100644
--- a/assets/misuzu.js/utility.js
+++ b/assets/misuzu.js/utility.js
@@ -1,174 +1,3 @@
-const $i = document.getElementById.bind(document);
-const $c = document.getElementsByClassName.bind(document);
-const $q = document.querySelector.bind(document);
-const $qa = document.querySelectorAll.bind(document);
-const $t = document.createTextNode.bind(document);
-
-const $r = function(element) {
-    if(element && element.parentNode)
-        element.parentNode.removeChild(element);
-};
-
-const $ri = function(name) {
-    $r($i(name));
-};
-
-const $rq = function(query) {
-    $r($q(query));
-};
-
-const $ib = function(ref, elem) {
-    ref.parentNode.insertBefore(elem, ref);
-};
-
-const $rc = function(element) {
-    while(element.lastChild)
-        element.removeChild(element.lastChild);
-};
-
-const $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;
-    }
-
-    const elem = document.createElement(info.tag || 'div');
-
-    if(info.attrs) {
-        const attrs = info.attrs;
-
-        for(let key in attrs) {
-            const 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';
-
-                        const prop = elem[key];
-                        let addFunc = null;
-
-                        if(prop instanceof Array)
-                            addFunc = prop.push.bind(prop);
-                        else if(prop instanceof DOMTokenList)
-                            addFunc = prop.add.bind(prop);
-
-                        if(addFunc !== null) {
-                            for(let j = 0; j < attr.length; ++j)
-                                addFunc(attr[j]);
-                        } else {
-                            if(key === 'classList')
-                                key = 'class';
-                            elem.setAttribute(key, attr.toString());
-                        }
-                    } else {
-                        for(const 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) {
-        let children = info.child;
-
-        if(!Array.isArray(children))
-            children = [children];
-
-        for(const child of children) {
-            switch(typeof child) {
-                case 'string':
-                    elem.appendChild(document.createTextNode(child));
-                    break;
-
-                case 'object':
-                    if(child instanceof Element) {
-                        elem.appendChild(child);
-                    } else if('element' in child) {
-                        const childElem = child.element;
-                        if(childElem instanceof Element)
-                            elem.appendChild(childElem);
-                        else
-                            elem.appendChild($e(child));
-                    } else if('getElement' in child) {
-                        const childElem = child.getElement();
-                        if(childElem instanceof Element)
-                            elem.appendChild(childElem);
-                        else
-                            elem.appendChild($e(child));
-                    } else {
-                        elem.appendChild($e(child));
-                    }
-                    break;
-
-                default:
-                    elem.appendChild(document.createTextNode(child.toString()));
-                    break;
-            }
-        }
-    }
-
-    if(info.created)
-        info.created(elem);
-
-    return elem;
-};
-const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });
-
-const $ar = function(array, index) {
-    array.splice(index, 1);
-};
-const $ari = function(array, item) {
-    let index;
-    while(array.length > 0 && (index = array.indexOf(item)) >= 0)
-        $ar(array, index);
-};
-const $arf = function(array, predicate) {
-    let index;
-    while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
-        $ar(array, index);
-};
-
-const $as = function(array) {
-    if(array.length < 2)
-        return;
-
-    for(let i = array.length - 1; i > 0; --i) {
-        let j = Math.floor(Math.random() * (i + 1)),
-            tmp = array[i];
-        array[i] = array[j];
-        array[j] = tmp;
-    }
-};
-
 const $insertTags = function(target, tagOpen, tagClose) {
     tagOpen = tagOpen || '';
     tagClose = tagClose || '';
diff --git a/assets/misuzu.js/watcher.js b/assets/misuzu.js/watcher.js
index fdca3c17..2b6fbc71 100644
--- a/assets/misuzu.js/watcher.js
+++ b/assets/misuzu.js/watcher.js
@@ -13,7 +13,7 @@ const MszWatcher = function() {
     };
 
     const unwatch = handler => {
-        $ari(handlers, handler);
+        $arrayRemoveValue(handlers, handler);
     };
 
     return {
diff --git a/assets/misuzu.js/xhr.js b/assets/misuzu.js/xhr.js
deleted file mode 100644
index c6e4b870..00000000
--- a/assets/misuzu.js/xhr.js
+++ /dev/null
@@ -1,117 +0,0 @@
-#include csrf.js
-
-const $x = (function() {
-    const send = function(method, url, options, body) {
-        if(options === undefined)
-            options = {};
-        else if(typeof options !== 'object')
-            throw 'options must be undefined or an object';
-
-        Object.freeze(options);
-
-        const xhr = new XMLHttpRequest;
-        const requestHeaders = new Map;
-
-        if('headers' in options && typeof options.headers === 'object')
-            for(const name in options.headers)
-                if(options.headers.hasOwnProperty(name))
-                    requestHeaders.set(name.toLowerCase(), options.headers[name]);
-
-        if(options.csrf)
-            requestHeaders.set('x-csrf-token', MszCSRF.token);
-
-        if(typeof options.download === 'function') {
-            xhr.onloadstart = ev => options.download(ev);
-            xhr.onprogress = ev => options.download(ev);
-            xhr.onloadend = ev => options.download(ev);
-        }
-
-        if(typeof options.upload === 'function') {
-            xhr.upload.onloadstart = ev => options.upload(ev);
-            xhr.upload.onprogress = ev => options.upload(ev);
-            xhr.upload.onloadend = ev => options.upload(ev);
-        }
-
-        if(options.authed)
-            xhr.withCredentials = true;
-
-        if(typeof options.timeout === 'number')
-            xhr.timeout = options.timeout;
-
-        if(typeof options.type === 'string')
-            xhr.responseType = options.type;
-
-        if(typeof options.abort === 'function')
-            options.abort(() => xhr.abort());
-
-        if(typeof options.xhr === 'function')
-            options.xhr(() => xhr);
-
-        if(typeof body === 'object') {
-            if(body instanceof URLSearchParams) {
-                requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
-            } else if(body instanceof FormData) {
-                // content-type is implicitly set
-            } else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) {
-                if(!requestHeaders.has('content-type'))
-                    requestHeaders.set('content-type', 'application/octet-stream');
-            } else if(!requestHeaders.has('content-type')) {
-                const bodyParts = [];
-                for(const name in body)
-                    if(body.hasOwnProperty(name))
-                        bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name]));
-                body = bodyParts.join('&');
-                requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
-            }
-        }
-
-        return new Promise((resolve, reject) => {
-            xhr.onload = ev => {
-                const headers = (headersString => {
-                    const headers = new Map;
-
-                    const raw = headersString.trim().split(/[\r\n]+/);
-                    for(const name in raw)
-                        if(raw.hasOwnProperty(name)) {
-                            const parts = raw[name].split(': ');
-                            headers.set(parts.shift(), parts.join(': '));
-                        }
-
-                    return headers;
-                })(xhr.getAllResponseHeaders());
-
-                if(options.csrf && headers.has('x-csrf-token'))
-                    MszCSRF.token = headers.get('x-csrf-token');
-
-                resolve({
-                    get ev() { return ev; },
-                    get xhr() { return xhr; },
-
-                    get status() { return xhr.status; },
-                    get headers() { return headers; },
-                    get body() { return xhr.response; },
-                    get text() { return xhr.responseText; },
-                });
-            };
-
-            xhr.onerror = ev => reject({
-                xhr: xhr,
-                ev: ev,
-            });
-
-            xhr.open(method, url);
-            for(const [name, value] of requestHeaders)
-                xhr.setRequestHeader(name, value);
-            xhr.send(body);
-        });
-    };
-
-    return {
-        send: send,
-        get: (url, options, body) => send('GET', url, options, body),
-        post: (url, options, body) => send('POST', url, options, body),
-        delete: (url, options, body) => send('DELETE', url, options, body),
-        patch: (url, options, body) => send('PATCH', url, options, body),
-        put: (url, options, body) => send('PUT', url, options, body),
-    };
-})();
diff --git a/assets/oauth2.css/main.css b/assets/oauth2.css/main.css
index cf9e4bce..8dbcda53 100644
--- a/assets/oauth2.css/main.css
+++ b/assets/oauth2.css/main.css
@@ -1,25 +1,3 @@
-* {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-    position: relative;
-}
-
-html, body {
-    width: 100%;
-    height: 100%;
-}
-
-[hidden],
-.hidden {
-    display: none !important;
-}
-
-:root {
-    --font-regular: Verdana, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
-    --font-monospace: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
-}
-
 body {
     background-color: #111;
     color: #fff;
diff --git a/assets/oauth2.js/authorise.js b/assets/oauth2.js/authorise.js
index 1a61babe..f76f30c4 100644
--- a/assets/oauth2.js/authorise.js
+++ b/assets/oauth2.js/authorise.js
@@ -1,5 +1,4 @@
 #include loading.jsx
-#include xhr.js
 #include app/info.jsx
 #include app/scope.jsx
 #include header/header.js
@@ -158,7 +157,7 @@ const MszOAuth2Authorise = async () => {
     }
 
     try {
-        const { body } = await $x.get(`/oauth2/resolve-authorise-app?${resolveParams}`, { authed: true, csrf: true, type: 'json' });
+        const { body } = await $xhr.get(`/oauth2/resolve-authorise-app?${resolveParams}`, { authed: true, csrf: true, type: 'json' });
         if(!body)
             throw 'authorisation resolve failed';
         if(typeof body.error === 'string') {
@@ -185,7 +184,7 @@ const MszOAuth2Authorise = async () => {
                 params.scope = scope;
 
             try {
-                const { body } = await $x.post('/oauth2/authorise', { authed: true, csrf: true, type: 'json' }, params);
+                const { body } = await $xhr.post('/oauth2/authorise', { authed: true, csrf: true, type: 'json' }, params);
                 if(!body)
                     throw 'authorisation failed';
                 if(typeof body.error === 'string')
diff --git a/assets/oauth2.js/csrf.js b/assets/oauth2.js/csrf.js
deleted file mode 100644
index 7df0610b..00000000
--- a/assets/oauth2.js/csrf.js
+++ /dev/null
@@ -1,24 +0,0 @@
-#include utility.js
-
-const MszCSRF = (() => {
-    let elem;
-    const getElement = () => {
-        if(elem === undefined)
-            elem = $q('meta[name="csrf-token"]');
-        return elem;
-    };
-
-    return {
-        get token() {
-            return getElement()?.content ?? '';
-        },
-        set token(token) {
-            if(typeof token !== 'string')
-                throw 'token must be a string';
-
-            const elem = getElement();
-            if(elem instanceof HTMLMetaElement)
-                elem.content = token;
-        },
-    };
-})();
diff --git a/assets/oauth2.js/main.js b/assets/oauth2.js/main.js
index e7238927..382b76b0 100644
--- a/assets/oauth2.js/main.js
+++ b/assets/oauth2.js/main.js
@@ -1,4 +1,3 @@
-#include utility.js
 #include authorise.js
 #include verify.js
 
diff --git a/assets/oauth2.js/verify.js b/assets/oauth2.js/verify.js
index 5b8ddb2c..9921ef84 100644
--- a/assets/oauth2.js/verify.js
+++ b/assets/oauth2.js/verify.js
@@ -1,5 +1,4 @@
 #include loading.jsx
-#include xhr.js
 #include app/info.jsx
 #include app/scope.jsx
 #include header/header.js
@@ -22,7 +21,7 @@ const MszOAuth2Verify = () => {
 
     const verifyAuthsRequest = async approve => {
         try {
-            const { body } = await $x.post('/oauth2/verify', { authed: true, csrf: true, type: 'json' }, {
+            const { body } = await $xhr.post('/oauth2/verify', { authed: true, csrf: true, type: 'json' }, {
                 code: userCode,
                 approve: approve === true ? 'yes' : 'no',
             });
@@ -91,7 +90,7 @@ const MszOAuth2Verify = () => {
         fCode.classList.add('hidden');
 
         userCode = encodeURIComponent(eUserCode.value);
-        $x.get(`/oauth2/resolve-verify?code=${userCode}`, { authed: true, csrf: true, type: 'json' })
+        $xhr.get(`/oauth2/resolve-verify?code=${userCode}`, { authed: true, csrf: true, type: 'json' })
             .then(result => {
                 const body = result.body;
 
diff --git a/build.js b/build.js
index 35fe521b..6c9a5ee4 100644
--- a/build.js
+++ b/build.js
@@ -12,11 +12,13 @@ const fs = require('fs');
         debug: isDebug,
         swc: {
             es: 'es2021',
+            jsx: '$jsx',
         },
     };
 
     const tasks = {
         js: [
+            { source: 'common.js', target: '/assets', name: 'common.{hash}.js', },
             { source: 'misuzu.js', target: '/assets', name: 'misuzu.{hash}.js', },
             { source: 'oauth2.js', target: '/assets', name: 'oauth2.{hash}.js', },
             { source: 'redir-bsky.js', target: '/assets', name: 'redir-bsky.{hash}.js', },
@@ -24,6 +26,7 @@ const fs = require('fs');
         ],
         css: [
             { source: 'errors.css', target: '/', name: 'errors.css', },
+            { source: 'common.css', target: '/assets', name: 'common.{hash}.css', },
             { source: 'misuzu.css', target: '/assets', name: 'misuzu.{hash}.css', },
             { source: 'oauth2.css', target: '/assets', name: 'oauth2.{hash}.css', },
         ],
diff --git a/templates/errors/master.twig b/templates/errors/master.twig
index 2c264bc8..89db9d99 100644
--- a/templates/errors/master.twig
+++ b/templates/errors/master.twig
@@ -7,7 +7,7 @@
 {% block html_head %}
     <meta name="description" content="{{ error_blerb }}">
     <link href="/vendor/fontawesome/css/all.min.css" type="text/css" rel="stylesheet">
-    <link href="{{ asset('errors.css') }}" rel="stylesheet">
+    <link href="/errors.css" rel="stylesheet">
     <style>
         :root {
             --error-colour: {{ error_colour }};
diff --git a/templates/manage/users/ban.twig b/templates/manage/users/ban.twig
index c1b1ca4a..79e320da 100644
--- a/templates/manage/users/ban.twig
+++ b/templates/manage/users/ban.twig
@@ -51,10 +51,10 @@
 
     <script>
         window.addEventListener('load', function() {
-            const ubExpires = $i('ub_expires'),
-                ubExpiresCustom = $i('ub_expires_custom'),
-                ubSeverity = $i('ub_severity'),
-                ubSeverityDisplay = $i('ub_severity_display');
+            const ubExpires = $id('ub_expires'),
+                ubExpiresCustom = $id('ub_expires_custom'),
+                ubSeverity = $id('ub_severity'),
+                ubSeverityDisplay = $id('ub_severity_display');
 
             const updateExpires = function() {
                 ubExpiresCustom.parentNode.classList.toggle('manage__ban__duration__value__custom--hidden', ubExpires.value != '-2');
diff --git a/templates/master.twig b/templates/master.twig
index 1b014ca5..962c13e0 100644
--- a/templates/master.twig
+++ b/templates/master.twig
@@ -3,6 +3,7 @@
 {% block html_head %}
     {% include '_layout/meta.twig' %}
     <link href="/vendor/fontawesome/css/all.min.css" type="text/css" rel="stylesheet">
+    <link href="{{ asset('common.css') }}" type="text/css" rel="stylesheet">
     <link href="{{ asset('misuzu.css') }}" type="text/css" rel="stylesheet">
     {% if site_background is defined %}
         <style>
@@ -62,5 +63,6 @@
     {% endblock %}
 
     <script src="/vendor/highlightjs/highlight.min.js" type="text/javascript"></script>
+    <script src="{{ asset('common.js') }}" type="text/javascript"></script>
     <script src="{{ asset('misuzu.js') }}" type="text/javascript"></script>
 {% endblock %}
diff --git a/templates/oauth2/master.twig b/templates/oauth2/master.twig
index cecb0cde..342c7a93 100644
--- a/templates/oauth2/master.twig
+++ b/templates/oauth2/master.twig
@@ -3,6 +3,7 @@
 {% set html_title = (title is defined ? (title ~ ' :: ') : '') ~ globals.site_info.name %}
 
 {% block html_head %}
+    <link href="{{ asset('common.css') }}" rel="stylesheet">
     <link href="{{ asset('oauth2.css') }}" rel="stylesheet">
 {% endblock %}
 
@@ -35,5 +36,6 @@
             </div>
         </div>
     </div>
+    <script src="{{ asset('common.js') }}"></script>
     <script src="{{ asset('oauth2.js') }}"></script>
 {% endblock %}