diff --git a/static/js/rot.js b/static/js/rot.js
new file mode 100644
index 00000000..c3bdf431
--- /dev/null
+++ b/static/js/rot.js
@@ -0,0 +1,302 @@
+// Rot.js
+// one rotten apple spoils the bunch
+
+(() => {
+    //Helper functions
+    function getPromiseFromEvent(item, event) {
+        return new Promise((resolve) => {
+            const listener = () => {
+                item.removeEventListener(event, listener);
+                resolve();
+            }
+            item.addEventListener(event, listener);
+        });
+    }
+
+    // MIT Licensed
+    // Author: jwilson8767
+    function waitUntil(selector) {
+        return new Promise((resolve, reject) => {
+            const el = document.querySelector(selector);
+            if (el) {
+                resolve(el);
+                return;
+            }
+            new MutationObserver((mutationRecords, observer) => {
+                // Query for elements matching the specified selector
+                Array.from(document.querySelectorAll(selector)).forEach((element) => {
+                    resolve(element);
+                    //Once we have resolved we don't need the observer anymore.
+                    observer.disconnect();
+                });
+            }).observe(document.documentElement, {
+                childList: true,
+                subtree: true
+            });
+        });
+    }
+
+    function sex(sex) {
+        return sex // Sex
+    }
+
+    function rand(list) {
+        return list ? list[list.length * Math.random() | 0] : null;
+    }
+
+    //Music functions
+    function setMusic(url) {
+        if (audio.src == url || (!url && audio.src === window.location.toString()))
+            return;
+
+        audio.pause();
+
+        if (url) {
+            console.log("Setting music: " + url);
+            audio.src = url;
+            playMusic();
+        } else {
+            // This line will cause a lot of errors.
+            // Setting src to "" will cause any pending .play()s to fail.
+            audio.src = "";
+        }
+    }
+
+    function playMusic() {
+        //Starts playing the music if it isn't muted and isn't already playing
+        if (audio.src && audio.src != "" && audio.paused && !audio.muted)
+            audio.play().catch(() => getPromiseFromEvent(window, 'click').then(audio.play));
+    }
+
+    function setImage(url) {
+        if (url) {
+            console.log("Setting background: " + url);
+            let apploaded = document.getElementById("app-loaded");
+            if (apploaded)
+                apploaded.style.setProperty("--body-background-image",  "url(" + url + ")");
+            else
+                console.log("Couldn't set background, app not yet loaded");
+        }
+    }
+
+    function volumeSet(number) {
+        audio.volume = number; //Set
+        localStorage.audiovolume = number; //Save
+        updateVolumeLabel();
+    }
+    function volumeAdd(number) {
+        volumeSet(Math.min(1, Math.max(0, audio.volume + number)));
+    }
+    function updateVolumeLabel() {
+        document.getElementById("user-audio-percentage").innerHTML = Math.round(audio.volume * 100) + "%";
+    }
+
+    //Registers a mutation observer for an element. Upon triggering and the callback
+    //returning true, observation ceases. All observers are disconnected and the array is cleared.
+    const observers = [];
+    function waitUntilSpecial(selector, callback) {
+        const newObserver = new MutationObserver((mutationRecords, observer) => {
+            // Query for elements matching the specified selector
+            Array.from(document.querySelectorAll(selector)).forEach((element) => {
+                //Callback
+                if (!callback(element))
+                    return;
+
+                //Clean up
+                console.log("Cleaning up");
+                for (const o of observers)
+                    o.disconnect();
+                observers.length = 0;
+            });
+        });
+        observers.push(newObserver);
+        newObserver.observe(document.documentElement, {
+            childList: true,
+            subtree: true
+        });
+    }
+
+    //Theme application
+    function applyMainTheme() {
+        console.log("Applying main theme");
+        setMusic(null);
+
+        //<meta name="pageMusic" content="/assets/music.ogg">
+        waitUntilSpecial('meta[name="pageMusic"]', (pageMusic) => {
+            setMusic(pageMusic.content);
+            playMusic();
+            return true;
+        });
+
+        //<a id="pageMusic" href="/assets/music.ogg"></a>
+        waitUntilSpecial("#pageMusic", (pageMusic) => {
+            setMusic(pageMusic.getAttribute("href"));
+            playMusic();
+            return true;
+        });
+    }
+
+    function applyUserTheme() {
+        console.log("Applying user theme");
+        setMusic(null);
+
+        //Configure by post
+        waitUntilSpecial(".pin", (pinnedPost) => {
+            if (pinnedPost.nextElementSibling
+                          .querySelector(".StatusBody")
+                          .querySelector(".text")
+                          .innerHTML
+                          .replace(/(<([^>]+)>)/ig, '')
+                          .search(/profile theming post/ig) == -1)
+                return false;
+
+            const ptp = pinnedPost.nextElementSibling.querySelector(".StatusBody");
+            if (!ptp)
+                return false;
+
+            const musicContainer = ptp.querySelector(".audio-container");
+            if (musicContainer)
+                setMusic(rand(musicContainer.children).src);
+            else
+                setMusic(null);
+
+            const imageContainer = ptp.querySelector(".image-container");
+            if (imageContainer)
+                setImage(rand(imageContainer.getElementsByTagName("img")).src);
+            else
+                setImage(null);
+
+            return musicContainer || imageContainer;
+        });
+
+        //Configure by fields
+        waitUntilSpecial(".user-profile-field-name", () => {
+            const fields = [...document.getElementsByClassName("user-profile-field-name")]
+            if (fields.length == 0)
+                return false;
+
+            const musicFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "music")
+            if (musicFields.length > 0)
+                setMusic(rand(musicFields).nextElementSibling.title);
+            else
+                setMusic(null);
+
+            const imageFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "image")
+            if (imageFields.length > 0)
+                setImage(rand(imageFields).nextElementSibling.title);
+            else
+                setImage(null);
+
+            return musicFields.length > 0 || imageFields.length > 0;
+        });
+    }
+
+    //Switch-based monkey patching router bullshit
+    let lastPath = null;
+    function updateRot() {
+        try {
+            let newPath = window.location.pathname;
+            if (lastPath == newPath)
+                return;
+            lastPath = newPath;
+
+            let pathSpl = newPath.split("/");
+            switch (pathSpl.length) {
+                case 1:
+                    applyMainTheme(); //Root
+                    break;
+
+                case 2:
+                    switch (pathSpl[1]) {
+                        case "about":
+                        case "announcements":
+                        case "lists":
+                        case "bookmarks":
+                            applyMainTheme();
+                            break;
+
+                        default:
+                            applyUserTheme();
+                            break;
+                    }
+                    break;
+
+                case 3:
+                    switch (pathSpl[1]) {
+                        case "main":
+                            applyMainTheme(); //Main timelines
+                            break;
+
+                        case "users":
+                            applyUserTheme();
+                            break;
+
+                        case "notice":
+                            //Continue playing
+                            break;
+
+                        default:
+                            applyMainTheme();
+                            break;
+                    }
+                    break;
+
+                default:
+                    applyMainTheme();
+                    break;
+            }
+        } catch (e) {
+            console.error(e);
+        }
+    }
+
+    //Rot music player
+    const audio = document.createElement("audio");
+    audio.loop = true;
+    audio.id = "user-music";
+    audio.style = "display:none;";
+    if (localStorage.audiovolume && localStorage.audiovolume >= 0 && localStorage.audiovolume <= 1)
+        audio.volume = localStorage.audiovolume; //Load volume
+    else
+        audio.volume = 0.2; //Default volume
+
+    //Initialize audio controls and event listeners
+    waitUntil("#music-controls").then((controls) => {
+        updateVolumeLabel();
+        controls.querySelector("#music-up").onclick = () => volumeAdd(0.05);
+        controls.querySelector("#music-down").onclick = () => volumeAdd(-0.05);
+    });
+
+    waitUntil("#music-slider").then((slider) => {
+        updateVolumeLabel();
+        slider.oninput = () => volumeSet(slider.value / 100);
+    });
+
+    waitUntil("#music-mute").then((box) => {
+        audio.muted = box.checked = localStorage.audiomuted === "true";
+        box.addEventListener('click', () => {
+            localStorage.audiomuted = audio.muted = box.checked;
+            playMusic();
+        })
+    });
+
+    //Monkey patches and event listeners
+    const oldPushState = history.pushState;
+    history.pushState = function pushState() {
+        const ret = oldPushState.apply(this, arguments);
+        updateRot();
+        return ret;
+    };
+
+    const oldReplaceState = history.replaceState;
+    history.replaceState = function replaceState() {
+        const ret = oldReplaceState.apply(this, arguments);
+        updateRot();
+        return ret;
+    };
+
+    addEventListener('locationchange', updateRot);
+    addEventListener('popstate', updateRot);
+})();
+
+console.log("rot.js loaded");
\ No newline at end of file