Tinychat Enhancement Suite (TES)

Fixes some Tinychat room shortcomings and adds useful features.

נכון ליום 12-05-2020. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Tinychat Enhancement Suite (TES)
// @namespace    https://greasyfork.org/en/users/80816-james-koss
// @version      2019.09.04v142
// @description  Fixes some Tinychat room shortcomings and adds useful features.
// @author       MutationObserver, phuein, legend
// @match        https://tinychat.com/room/*
// @match        https://tinychat.com/*
// @exclude      https://tinychat.com/room/*?1
// @exclude      https://tinychat.com/settings/*
// @exclude      https://tinychat.com/subscription/*
// @exclude      https://tinychat.com/promote/*
// @exclude      https://tinychat.com/gifts
// @exclude      https://tinychat.com/home
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues

// ==/UserScript==
/* jshint -W097 */

TESwsParser ``;
var waitingForDom = 0;
var initInterval = setInterval(function () {
    if (document.querySelector("tinychat-webrtc-app") && document.querySelector("tinychat-webrtc-app").shadowRoot) TESapp = runTES ``;
    else if(waitingForDom < 3) {
        tcl("Waiting for DOM...");
        waitingForDom += 1;
    }
}, 500);

function triggerMouseEvent(node, eventType) {
    var mouseEvent = document.createEvent('MouseEvent');
    mouseEvent.initMouseEvent(eventType, true, true);
    node.dispatchEvent(mouseEvent);
}

function runTES() {
    clearInterval(initInterval);
    try {
        /* Begin main function */
        var tinychat = TinychatApp.getInstance();
        var packetWorker = tinychat.defaultChatroom.packetWorker;
        var userlist = tinychat.defaultChatroom.userlist;

        // packetWorker.tcSocket.chatRoom._chatlog.chatroom._videolist.needToBlurOtherVids = false;
        packetWorker.tcSocket.chatRoom._chatlog.chatroom._videolist.blurOtherVids = function () {};

        var bodyElem = document.querySelector("body");

        bodyElem.style.overflow = "hidden"; // NOTE: Any issues with this??

        var webappOuter = document.querySelector("tinychat-webrtc-app");
        var webappElem = webappOuter.shadowRoot;
        var chatlogElem = webappElem.querySelector("tc-chatlog").shadowRoot;
        var titleElem = webappElem.querySelector("tc-title").shadowRoot;
        var sidemenuElem = webappElem.querySelector("tc-sidemenu").shadowRoot;
        var videomoderationElem = sidemenuElem.querySelector("tc-video-moderation").shadowRoot;
        var videolistElem = webappElem.querySelector("tc-videolist").shadowRoot;

        var chatlistElem = sidemenuElem.querySelector("tc-chatlist").shadowRoot;
        var userlistElem = sidemenuElem.querySelector("tc-userlist").shadowRoot;
        var userContextmenuElem = userlistElem.querySelector("tc-user-contextmenu").shadowRoot;

        var chatlogCSS = chatlogElem.querySelector("#chat-wrapper");
        var sidemenuCSS = sidemenuElem.querySelector("#sidemenu");
        var videomoderationCSS = videomoderationElem.querySelector("#moderatorlist");
        var webappCSS = webappElem.querySelector("#room");
        var chatlistCSS = chatlistElem.querySelector("#chatlist");
        var userlistCSS = userlistElem.querySelector("#userlist");
        var userContextmenuCSS = userContextmenuElem.querySelector("#main");
        var titleCSS = titleElem.querySelector("#room-header");
        var videolistCSS = videolistElem.querySelector("#videolist");
        var bodyCSS = document.querySelector("body");

        var userinfoCont = sidemenuElem.querySelector("#user-info > div");
        var scrollbox = chatlogElem.querySelector("#chat");
        var unreadbubble = chatlogElem.querySelector("#input-unread");

        var resourceDirectory = document.querySelector('link[rel="manifest"]').getAttribute("href").split("manifest")[0]; // \/([\d\.\-])+\/
        var audioPop = new Audio(resourceDirectory + 'sound/pop.mp3');
        var settingMentions = [];
        var settingIgnoredUsers = {};
        var settingBannedUsers = {};
        var giftsElemWidth = 127;
        var messageHeight;
        var chatboxHeight;
        var totalScrolledUp = 0;
        var freshInstall = (GM_listValues().length == 0);
        var isModerator = (!userlistElem.querySelector("#button-banlist").classList.contains("hidden"));
        var isPaidAccount = (sidemenuElem.querySelector("#sidemenu-wider"));

        var browserFirefox = navigator.userAgent.includes("Firefox/");
        var urlAddress = new URL(window.location.href);
        var urlPars = urlAddress.searchParams;
        var modder = 'memes'

        var messageQueryString = "#chat-content .message";
        var camQueryString = ".videos-items > div:not([id='youtube'])";

        var userCount = 0;
        var messageCount = 0;
        var camMaxedCurrent = null;
        //var autoScrollStatus = true;
        //var userScrolledChat = false;
        var roomName = webappOuter.getAttribute("roomname");
        var joinTime = getFormattedDateTime(new Date(), "time");
        var joinDate = getFormattedDateTime(new Date());
        var myNick;
        var updBaLi = ()=>{tinychat.defaultChatroom.Banlist(()=>{})}
        var BaLi = ()=>{return tinychat.defaultChatroom.banlist};
        var unB = (a)=>{packetWorker.send(tinychat.defaultChatroom.tcPkt_Unban(a.id, ()=>{}))};

        // Add new chatbox.
        var node = document.createElement("div");
        node.id = "chat-content-new";
        node.style.display = 'none';
        chatlogElem.querySelector('#chat').appendChild(node);
        // Load previously used chatbox.
        if (GM_getValue("defaultChat") === false) toggleDefaultChat();

        // Remember ignored users.
        var manuallyIgnored = GM_getValue("manuallyIgnored") || [];
        // Ignore from saved list.
        setTimeout(()=>{
            for (let i=0; i < userlist.items.length; i++) {
                let u = userlist.items[i];
                let nick = u.nickname;
                let username = u.username;

                ignoreFromManualList(username, nick);
            }
        }, 2000)

        var ignoreOriginal = userlist.ignore;
        userlist.ignore = function() {
            let a = arguments[0];
            let b = a.isUsername ? a.username : a.nickname;
            b = b.toUpperCase();

            ignoreOriginal.apply(this, arguments);

            let i = manuallyIgnored.indexOf(b);
            // Add ignored.
            if (userlist.isIgnored(a) && i === -1) {
                manuallyIgnored.push(b);
                GM_setValue("manuallyIgnored", manuallyIgnored);
                //console.log('added to TES ignored list: ' + b)
            }
        }

        var unignoreOriginal = userlist.unignore;
        userlist.unignore = function() {
            let a = arguments[0];
            let b = a.isUsername ? a.username : a.nickname;
            b = b.toUpperCase();

            unignoreOriginal.apply(this, arguments);

            let i = manuallyIgnored.indexOf(b);
            // Remove unignored.
            if (!userlist.isIgnored(a) && i > -1) {
                manuallyIgnored.splice(i);
                GM_setValue("manuallyIgnored", manuallyIgnored);
                //console.log('removed from TES ignored list: ' + b)
            }
        }

        var showToast = tinychat.defaultChatroom.app.showToast;
        function ignoreFromManualList(username, nick) {
            let u = userlist.getByNickname(nick);

            username = username.toUpperCase();
            nick = nick.toUpperCase();

            let i = manuallyIgnored.indexOf(username);
            let j = manuallyIgnored.indexOf(nick);

            // Always ignore by username, but only ignore nickname if there is no username.
            if (i > -1 || (j > -1 && !username)) {
                // Temporary disable the annoying toast popup notification.
                tinychat.defaultChatroom.app.showToast = ()=>{};
                userlist.ignore(u);
                tinychat.defaultChatroom.app.showToast = showToast;
                tcl('Autoignoring ' + nick + ' (' + username + ')')
            }
        }

        document.title = roomName + " - Tinychat";
        declareGlobalVars();
        var settingsWaitInterval = setInterval(waitForSettings, 1000);

        injectCSS();
        injectElements();

        if (userinfoCont.hasAttribute("title")) {
            bodyCSS.classList.add("logged-in");
            sidemenuElem.querySelector("#sidemenu").classList.add("logged-in");
        }
        if (isModerator) {
            userlistElem.querySelector("#userlist").classList.add("tes-mod");
            chatlistElem.querySelector("#chatlist").classList.add("tes-mod");
        }

        // Add open-mic option by pressing middle-mouse button on mic button.
        var talkBtn = videolistElem.querySelector("#videos-footer-push-to-talk");
        talkBtn.addEventListener("mousedown", function (e) {
            if (e.which == 2) {
                e.preventDefault();
                e.stopPropagation();
                triggerMouseEvent(talkBtn, "mousedown");
            }
        });
        // Ignore further events relating to this click.
        talkBtn.addEventListener("mouseup", function (e) {
            if (e.which == 2) {
                e.preventDefault();
                e.stopPropagation();
            }
        });
        talkBtn.addEventListener("click", function (e) {
            if (e.which == 2) {
                e.preventDefault();
                e.stopPropagation();
            }
        });
        // Add instructions in title (tooltip.)
        talkBtn.setAttribute("title", "Wheel-Click to talk freely.");

        // Add talking tracking.
        setInterval(function () {
            for (let i = 0; i < 12; i++) {
                try {
                    let audioLevel = packetWorker.tcSocket.chatRoom._chatlog.chatroom._videolist.items[i].audiolevel;
                    let nick = packetWorker.tcSocket.chatRoom._chatlog.chatroom._videolist.items[i].userentity.nickname;
                    let indicator = videolistElem.querySelector('div#videolist > div#videos-content > div#videos > div.videos-items:last-child > div#camUser-' + nick +
                        ' > tc-video-item').shadowRoot.querySelector('div.video > div > div.overlay > .icon-tes-talking');

                    indicator.style.opacity = audioLevel;
                } catch (e) {
                    // pass
                }
            }
        }, 250);

        // Track chat input box.
        var inputBox = chatlogCSS.querySelector('div#chat-position > form#input > textarea');

        var advert = "BIG CAMS - MIC INDICATOR - OPENMIC - ANTISPAM: https://greasyfork.org/en/scripts/389657-tinychat-enhancement-suite-tes";
        var discord = "don't lose contact with your chat friends https://discord.gg/Me4xZ5W 50+ chatters in server, cause we all get banned sometimes";
        var inputBoxHeight = inputBox.clientHeight;
        //var lastInputBoxHeight = inputBoxHeight;

        var toastLength = 3000;

        inputBox.onkeyup = function(e) {
            // Restore last message if empty.
            if (e.key == 'ArrowUp' && !inputBox.value) {
                inputBox.value = lastV;
            }

            if (!inputBox.value) {
                // Restore height if text deleted.
                inputBox.style.height = inputBoxHeight + "px";

                // Catch up if autoscrolling, after msg sent.
                /*if (autoScrollStatus === true && settingsQuick["Autoscroll"]) {
                    updateScroll();
                }*/
            }

            // Advertise the extension.
            if (inputBox.value === '/ad') {
                inputBox.value = advert;
                return;
            }

            // Advertise the discord.
            if (inputBox.value === '/discord') {
                inputBox.value = discord;
                return;
            }

            // Clean manual-ignore list.
            if (inputBox.value === '/ignores') {
                inputBox.value = '';

                GM_setValue("manuallyIgnored", []);
                manuallyIgnored = [];
                return;
            }

            // Kick all users.
            if (inputBox.value === '/nuke') {
                inputBox.value = '';

                if (!tinychat.defaultChatroom.isOperator()) return;

                try {
                    for (let i=0; i < userlist.items.length; i++) {
                        let user = userlist.items[i];

                        if (user.isOperator) continue;

                        userlist.kick(user);
                    }
                } catch(e) { console.log('autokicker nuke:', e);}

                return;
            }

            // Unban all users.
            if (inputBox.value === '/clear') {
                inputBox.value = '';

                if (!tinychat.defaultChatroom.isOperator()) return;

                try {
                    // Update and clear banlist.
                    updBaLi();

                    setTimeout(() => {
                        let bl = BaLi();

                        for (let i=0; i < bl.length; i++) {
                            let user = bl[i];
                            unB(user);
                        }

                        console.log("Done clearing the banlist!");
                    }, 2000);
                } catch(e) { console.log('clear banlist:', e);}

                return;
            }
        };

        // Catch a chat message before it's sent.
        let lastV = '';
        inputBox.onkeydown = function(e) {
            if (!inputBox.value) return;

            if (e.key == "Enter") {
                let v = inputBox.value;

                // Ignore value repetitions.
                if (lastV == v) return;
                lastV = v;

                // let va = v.split(' ');
            }
        }

        // Set the whitelister vars.
        var whitelister = GM_getValue("tes-Whitelister") || false;
        var whitelist = GM_getValue("tes-Whitelist") || {};

        var settingsQuick = {
            "RememberIgnore": (GM_getValue("tes-RememberIgnore") == "true" || GM_getValue("tes-RememberIgnore") == undefined),
            "MentionsMonitor": (GM_getValue("tes-MentionsMonitor") == "true" || GM_getValue("tes-MentionsMonitor") == undefined),
            "NotificationsOff": (GM_getValue("tes-NotificationsOff") == "true"),
            "ChangeFont": (GM_getValue("tes-ChangeFont") == "true" || GM_getValue("tes-ChangeFont") == undefined),
            "NightMode": (GM_getValue("tes-NightMode") == "true"),
            "NightModeBlack": (GM_getValue("tes-NightModeBlack") == "true" || GM_getValue("tes-NightModeBlack") == undefined),
            "MaxedCamLeft": (GM_getValue("tes-MaxedCamLeft") == "true" || GM_getValue("tes-MaxedCamLeft") == undefined),
            "BorderlessCams": (GM_getValue("tes-BorderlessCams") == "true"),
            "HideNewCams": (GM_getValue("tes-HideNewCams") == "true"),
            "Ignored": (GM_getValue("tes-Ignored") == "true"),
            "IgnoredMonitor": (GM_getValue("tes-IgnoredMonitor") == "true" || GM_getValue("tes-IgnoredMonitor") == undefined),
            "Banned": (GM_getValue("tes-Banned") == "true"),
            "BannedMonitor": (GM_getValue("tes-BannedMonitor") == "true" || GM_getValue("tes-BannedMonitor") == undefined),
            "BannedMonitorWhitelist": (GM_getValue("tes-BannedMonitorWhitelist") == "true" || GM_getValue("tes-BannedMonitorWhitelist") == undefined),
            "ToggleHiddenMsgs": (GM_getValue("tes-ToggleHiddenMsgs") == "true" || GM_getValue("tes-ToggleHiddenMsgs") == undefined),
        };
        if (settingsQuick["ChangeFont"]) bodyElem.classList.add("tes-changefont");
        if (settingsQuick["NightMode"]) nightmodeToggle(true);
        if (settingsQuick["MaxedCamLeft"]) videolistCSS.classList.add("tes-leftcam");
        if (settingsQuick["BorderlessCams"]) borderlessCamsToggle(true);

        if (browserFirefox && 1 == 2) {
            titleElem.querySelector("#room-header-info").insertAdjacentHTML("afterend", `
	<div id="tes-firefoxwarning" class="style-scope tinychat-title"
	style="font-size: .75em; background: white; color: grey; width: 200px; padding: 5px; line-height: 13px;vertical-align: middle;border: #ddd 1px solid;border-width: 0px 1px 0px 1px;">
		<div class="style-scope tinychat-title" style="display: table;height: 100%;">
			<span style="display: table-cell; vertical-align: middle;top: 16%;" class="style-scope tinychat-title">
			Tinychat Enhancement Suite requires Chrome. Other browsers only have autoscroll & cam maxing.
			</span>
		</div>
	</div>
	`);
        }

        function waitForSettings() {
            try {
                settingsVisible = false;
                if (titleElem.querySelector("#room-header-gifts") != null) {
                    clearInterval(settingsWaitInterval);

                    giftsElemWidth = titleElem.querySelector("#room-header-gifts").offsetWidth;
                    if (titleElem.querySelector("#room-header-gifts-items") == null) {
                        giftsElemWidth1000 = giftsElemWidth + 45;
                    } else {
                        giftsElemWidth1000 = giftsElemWidth;
                    }
                    if (titleCSS.querySelector("#titleCSS")) {
                        titleCSS.querySelector("#titleCSS").innerHTML += `
				#tes-settings {
					right: ` + giftsElemWidth + `px;
				}
			`;
                    }

                    var sidemenuFakeBorder = document.createElement("span");
                    sidemenuFakeBorder.setAttribute("id", "tes-sidemenufakeborder");
                    sidemenuCSS.insertAdjacentElement("beforeend", sidemenuFakeBorder);

                    settingsElem = titleElem.querySelector("#room-header-gifts").insertAdjacentHTML("beforebegin", `
		<div id="tes-settings">
			<div id="tes-settingsBox" class="hidden">
				<p id="title"><a href="https://greasyfork.org/en/scripts/32964-tinychat-enhancement-suite" target="_blank">Tinychat Enhancement Suite</a></p>
				<div id="tes-settings-mentions" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						Alert phrases
						<span class="info" data-title='A comma-separated list of phrases to alert/highlight for. Example of 3 phrases: "hello, Google Chrome, sky"'>?</span>
					</span>
					<div class="inputcontainer">
						<input class="text" placeholder="enter alert phrases here"><button class="save blue-button">save</button>
					</div>
				</div>
				<div id="tes-settings-rememberignore" class="tes-setting-container" style="display: none;">
					<input type="checkbox">
					<span class="label">
						Remember Ignore
					</span>
				</div>
				<div id="tes-settings-notifications" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						Hide notifications
					</span>
				</div>
				<div id="tes-settings-changefont" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						Improve font
						<span class="info" data-title='The default font is too thin in some browsers'>?</span>
					</span>
				</div>
				<div id="tes-settings-nightmode" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						Night mode
					</span>
					<span id="black" class="colorCont"><input type="radio" class="nightmode-colors"><span class="sublabel">Black</span></span>
					<span id="gray" class="colorCont"><input type="radio" class="nightmode-colors"><span class="sublabel">Gray</span></span>
				</div>
				<div id="tes-settings-maxcamposition" class="tes-setting-container right">
					<input type="checkbox">
					<span class="label">Maxed cam on left
					</span>
				</div>
				<div id="tes-settings-borderlesscams" class="tes-setting-container right">
					<input type="checkbox">
					<span class="label">Remove cam spacing
					</span>
				</div>
                <div id="tes-settings-hidenewcams" class="tes-setting-container right">
					<input type="checkbox">
					<span class="label">Auto hide new cams
					</span>
				</div>
                <div id="tes-settings-ignored" class="tes-setting-container rightright">
					<input type="checkbox">
					<span class="label">
						Cam auto-hide users
						<span class="info" data-title='A comma-separated list of users to auto-hide their cam. Example of 3 users: "bob, mike, jim"'>?</span>
					</span>
					<div class="inputcontainer">
						<input class="text" placeholder="enter nicknames here"><button class="save blue-button">save</button>
					</div>
				</div>
                <div id="tes-settings-banned" class="tes-setting-container rightright">
					<input type="checkbox">
					<span class="label">
						Auto-ban
						<span class="info" data-title='A comma-separated list of users to auto-ban on join. Example of 3 users: "bob, mike, jim"'>?</span>
					</span>
                    <input type="checkbox">
                    <span class="label" style="margin: 0;">
						Whitelist
					</span>
					<div class="inputcontainer">
						<input class="text" placeholder="enter nicknames here"><button class="save blue-button">save</button>
					</div>
				</div>
                <!-- BROKEN <div id="tes-settings-togglehiddenmsgs" class="tes-setting-container rightright">
					<input type="checkbox">
					<span class="label">Hide spam chat messages
					</span>
				</div> -->
				<!--
					<div id="tes-settings-messageanim" class="tes-setting-container">
						<input type="checkbox">
						<span class="label">
							Disable message animation
						</span>
					</div>
				-->
			</div>
			<div id="tes-updateNotifier"><a class="tes-closeButtonSmall">✕</a><span>TES now works in Firefox!</span></div>
			<div id="tes-settingsGear" title="Tinychat Enhancement Suite settings"><span>✱</span></div>
		</div>
		`);

                    titleElem.getElementById("tes-settingsGear").addEventListener("click", toggleSettingsDisplay);
                    titleElem.getElementById("tes-updateNotifier").addEventListener("click", function () {
                        toggleSettingsDisplay("updateNotifier");
                    });
                    if (!freshInstall && GM_getValue("tes-updateNotifSeen") != "2019.03.16v64") titleElem.getElementById("tes-updateNotifier").classList.add("visible");

                    titleElem.querySelector("#tes-settings #tes-settings-mentions button.save").addEventListener("click", function () {
                        mentionsManager("save");
                    });
                    mentionsManager("load");

                    titleElem.querySelector("#tes-settings #tes-settings-ignored button.save").addEventListener("click", function () {
                        ignoredManager("save");
                    });
                    ignoredManager("load");

                    titleElem.querySelector("#tes-settings #tes-settings-banned button.save").addEventListener("click", function () {
                        bannedManager("save");
                    });
                    bannedManager("load");

                    settingsCheckboxUpdate();
                    titleElem.querySelector("#tes-settings-rememberignore input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-rememberignore");
                    });
                    titleElem.querySelector("#tes-settings-mentions input:first-of-type").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-mentions");
                    });
                    titleElem.querySelector("#tes-settings-notifications input:first-of-type").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-notifications");
                    });
                    titleElem.querySelector("#tes-settings-changefont input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-changefont");
                    });
                    titleElem.querySelector("#tes-settings-nightmode input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-nightmode");
                    });
                    titleElem.querySelector("#tes-settings-nightmode #black").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-nightmode-black");
                    });
                    titleElem.querySelector("#tes-settings-nightmode #gray").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-nightmode-gray");
                    });
                    titleElem.querySelector("#tes-settings-maxcamposition input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-maxcamposition");
                    });
                    titleElem.querySelector("#tes-settings-borderlesscams input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-borderlesscams");
                    });
                    titleElem.querySelector("#tes-settings-hidenewcams input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-hidenewcams");
                    });
                    titleElem.querySelector("#tes-settings-ignored input:first-of-type").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-ignored");
                    });
                    titleElem.querySelector("#tes-settings-banned input:first-of-type").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-banned");
                    });
                    titleElem.querySelector("#tes-settings-banned input:nth-of-type(2)").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-banned-whitelist");
                    });
                    /*titleElem.querySelector("#tes-settings-togglehiddenmsgs input").addEventListener("click", function () {
                        settingsCheckboxUpdate("tes-settings-togglehiddenmsgs");
                    });*/

                    notificationHider();

                    newUserAdded();
                    newMessageAdded();
                    setTimeout(newCamAdded, 2000); // Delay to wait for cameras to load.
                    setTimeout(messageParserCheckCSS, 2000);
                }
            } catch (e) {
                tcl("error waitForSettings: " + e.message);
            }
        }

        function nightmodeToggle(arg) {
            try {
                var nightmodeClasses = ["tes-nightmode"];

                if (settingsQuick["NightModeBlack"]) nightmodeClasses.push("blacknight");

                if (arg == true) {
                    bodyElem.classList.add(...nightmodeClasses);
                    titleCSS.classList.add(...nightmodeClasses);
                    sidemenuCSS.classList.add(...nightmodeClasses);
                    userlistCSS.classList.add(...nightmodeClasses);
                    webappCSS.classList.add(...nightmodeClasses);
                    videolistCSS.classList.add(...nightmodeClasses);
                    videomoderationCSS.classList.add(...nightmodeClasses);
                    chatlistCSS.classList.add(...nightmodeClasses);
                    chatlogCSS.classList.add(...nightmodeClasses);
                    chatlogElem.querySelector("#chat-wider").classList.add(...nightmodeClasses);
                    // Messages:
                    if (chatlogElem.querySelector(messageQueryString) != null) {
                        var messageElems = chatlogElem.querySelectorAll(messageQueryString);
                        for (var i = 0; i < messageElems.length; i++) {
                            var messageItem = messageElems[i].querySelector("tc-message-html").shadowRoot;
                            var messageItemCSS = messageItem.querySelector(".message");
                            //messageItemCSS.classList.add(...nightmodeClasses);
                            messageItem.appendChild(messageParserAddCSSNightmode());
                            if (settingsQuick["NightModeBlack"]) messageItem.appendChild(messageParserAddCSSBlacknight());
                        }
                    }
                    // Cams:
                    if (videolistElem.querySelector(camQueryString) != null) {
                        var camElems = videolistElem.querySelectorAll(camQueryString);
                        for (var i = 0; i < camElems.length; i++) {
                            var camItem = camElems[i].querySelector("tc-video-item").shadowRoot;
                            var camItemCSS = camItem.querySelector(".video");
                            camItemCSS.classList.add(...nightmodeClasses);

                            camItem.appendChild(camParserAddCSSNightmode());
                            if (settingsQuick["NightModeBlack"]) camItem.appendChild(camParserAddCSSBlacknight());

                            if (camItemCSS.querySelector("#camItemCSS") == null) camItemCSS.insertAdjacentHTML("afterbegin", camItemCSShtml);
                        }
                    }
                }
                if (arg == false) {
                    if (!settingsQuick["NightModeBlack"] && settingsQuick["NightMode"]) nightmodeClasses = ["blacknight"];

                    bodyElem.classList.remove(...nightmodeClasses);
                    titleCSS.classList.remove(...nightmodeClasses);
                    sidemenuCSS.classList.remove(...nightmodeClasses);
                    userlistCSS.classList.remove(...nightmodeClasses);
                    webappCSS.classList.remove(...nightmodeClasses);
                    videolistCSS.classList.remove(...nightmodeClasses);
                    videomoderationCSS.classList.remove(...nightmodeClasses);
                    chatlistCSS.classList.remove(...nightmodeClasses);
                    chatlogCSS.classList.remove(...nightmodeClasses);
                    chatlogElem.querySelector("#chat-wider").classList.remove(...nightmodeClasses);
                    // Messages:
                    if (chatlogElem.querySelector(messageQueryString) != null) {
                        var messageElems = chatlogElem.querySelectorAll(messageQueryString);
                        for (i = 0; i < messageElems.length; i++) {
                            var messageItem = messageElems[i].querySelector("tc-message-html").shadowRoot;
                            //var messageItemCSS = messageItem.querySelector(".message");
                            //messageItemCSS.classList.remove(...nightmodeClasses);

                            var child = messageItem.getElementById("messageCSSNightmode");
                            if (child) {
                                messageItem.removeChild(child);
                            }
                            child = messageItem.getElementById("messageCSSBlacknight");
                            if (child) {
                                messageItem.removeChild(child);
                            }
                        }
                    }
                    // Cams:
                    if (videolistElem.querySelector(camQueryString) != null) {
                        var camElems = videolistElem.querySelectorAll(camQueryString);
                        for (var i = 0; i < camElems.length; i++) {
                            var camItem = camElems[i].querySelector("tc-video-item").shadowRoot;
                            var camItemCSS = camItem.querySelector(".video");
                            camItemCSS.classList.remove(...nightmodeClasses);

                            var child = camItem.getElementById("camItemCSShtmlNightmode");
                            if (child) {
                                camItem.removeChild(child);
                            }

                            var child = camItem.getElementById("camItemCSShtmlBlacknight");
                            if (child) {
                                camItem.removeChild(child);
                            }
                        }
                    }
                }
            } catch (e) {
                tcl("error nightmodeToggle: " + e.message);
            }
        }

        function showUpdateNotifier(text) {
            try {

                var updateNotifier = titleElem.querySelector("#tes-updateNotifier");
                updateNotifier.querySelector("span").innerHTML = text;
                updateNotifier.classList.add("visible");
                if (settingsVisible == true) toggleSettingsDisplay();

            } catch (e) {
                tcl("error showUpdateNotifier: " + e.message);
            }
        }

        function toggleSettingsDisplay(arg) {
            try {
                if (arg == "updateNotifier") {
                    titleElem.querySelector("#tes-updateNotifier").classList.remove("visible");
                    GM_setValue("tes-updateNotifSeen", "2019.03.16v64");
                }

                if (settingsVisible == true) {
                    titleElem.getElementById("tes-settingsBox").classList.add("hidden");
                    titleElem.getElementById("tes-settings").classList.remove("tes-open");
                    settingsVisible = false;
                } else {
                    titleElem.getElementById("tes-settingsBox").classList.remove("hidden");
                    titleElem.getElementById("tes-settings").classList.add("tes-open");
                    settingsVisible = true;
                }
            } catch (e) {
                tcl("error toggleSettingsDisplay: " + e.message);
            }
        }

        function settingsCheckboxUpdate(settingName = null, value = null) {
            try {
                if (settingName == null && value == null) {
                    titleElem.getElementById("tes-settings-rememberignore").querySelector("input").checked = settingsQuick["RememberIgnore"];
                    titleElem.getElementById("tes-settings-mentions").querySelector("input").checked = settingsQuick["MentionsMonitor"];
                    titleElem.getElementById("tes-settings-notifications").querySelector("input").checked = settingsQuick["NotificationsOff"];
                    titleElem.getElementById("tes-settings-changefont").querySelector("input").checked = settingsQuick["ChangeFont"];
                    titleElem.getElementById("tes-settings-nightmode").querySelector("input").checked = settingsQuick["NightMode"];
                    titleElem.getElementById("tes-settings-maxcamposition").querySelector("input").checked = settingsQuick["MaxedCamLeft"];
                    titleElem.getElementById("tes-settings-borderlesscams").querySelector("input").checked = settingsQuick["BorderlessCams"];
                    titleElem.getElementById("tes-settings-hidenewcams").querySelector("input").checked = settingsQuick["HideNewCams"];
                    titleElem.getElementById("tes-settings-ignored").querySelector("input").checked = settingsQuick["IgnoredMonitor"];
                    titleElem.getElementById("tes-settings-banned").querySelector("input:first-of-type").checked = settingsQuick["BannedMonitor"];
                    titleElem.getElementById("tes-settings-banned").querySelector("input:nth-of-type(2)").checked = settingsQuick["BannedMonitorWhitelist"];
                    //titleElem.getElementById("tes-settings-togglehiddenmsgs").querySelector("input").checked = settingsQuick["ToggleHiddenMsgs"];

                    titleElem.querySelector("#tes-settings-nightmode #black input").checked = settingsQuick["NightModeBlack"];
                    titleElem.querySelector("#tes-settings-nightmode #gray input").checked = (settingsQuick["NightModeBlack"] == false);

                    return;
                }
                if (settingName == "tes-settings-rememberignore") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-rememberignore").querySelector("input").checked;
                        settingsQuick["RememberIgnore"] = newValue;
                        GM_setValue("tes-RememberIgnore", newValue.toString());
                    }
                }
                if (settingName == "tes-settings-mentions") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-mentions").querySelector("input:first-of-type").checked;
                        // if (newValue) {
                        // titleElem.getElementById("tes-settings-mentions").getAttribute("class").includes("setting-disabled");
                        // }
                        settingsQuick["MentionsMonitor"] = newValue;
                        GM_setValue("tes-MentionsMonitor", newValue.toString());
                    }
                }
                if (settingName == "tes-settings-ignored") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-ignored").querySelector("input:first-of-type").checked;
                        // if (newValue) {
                        // titleElem.getElementById("tes-settings-ignored").getAttribute("class").includes("setting-disabled");
                        // }
                        settingsQuick["IgnoredMonitor"] = newValue;
                        GM_setValue("tes-IgnoredMonitor", newValue.toString());
                    }
                }
                if (settingName == "tes-settings-banned") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-banned").querySelector("input:first-of-type").checked;
                        settingsQuick["BannedMonitor"] = newValue;
                        GM_setValue("tes-BannedMonitor", newValue.toString());
                        // If switched on, then toggle off whitelist.
                        let whitelist = titleElem.getElementById("tes-settings-banned").querySelector("input:nth-of-type(2)");
                        if (newValue && whitelist.checked) whitelist.click();
                    }
                }
                if (settingName == "tes-settings-banned-whitelist") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-banned").querySelector("input:nth-of-type(2)").checked;
                        settingsQuick["BannedMonitorWhitelist"] = newValue;
                        GM_setValue("tes-BannedMonitorWhitelist", newValue.toString());
                        // If switched on, then toggle off blacklist.
                        let blacklist = titleElem.getElementById("tes-settings-banned").querySelector("input:first-of-type");
                        if (newValue && blacklist.checked) blacklist.click();
                    }
                }
                if (settingName == "tes-settings-notifications") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-notifications").querySelector("input").checked;
                        settingsQuick["NotificationsOff"] = newValue;
                        GM_setValue("tes-NotificationsOff", newValue.toString());
                        notificationHider();
                    }
                }
                if (settingName == "tes-settings-changefont") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-changefont").querySelector("input").checked;
                        settingsQuick["ChangeFont"] = newValue;
                        GM_setValue("tes-ChangeFont", newValue.toString());
                        fontToggle(newValue);
                    }
                }
                if (settingName == "tes-settings-nightmode") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-nightmode").querySelector("input").checked;
                        settingsQuick["NightMode"] = newValue;
                        GM_setValue("tes-NightMode", newValue.toString());
                        nightmodeToggle(newValue);
                    }
                }
                if (settingName == "tes-settings-nightmode-black") {
                    if (value == null) {
                        let newValue = titleElem.querySelector("#tes-settings-nightmode #black input").checked;
                        titleElem.querySelector("#tes-settings-nightmode #gray input").checked = (!newValue);
                        settingsQuick["NightModeBlack"] = newValue;
                        GM_setValue("tes-NightModeBlack", newValue.toString());
                        //nightmodeToggle(newValue);
                        //nightmodeToggle(true);

                        if (titleElem.querySelector("#tes-settings-nightmode #black input").checked || titleElem.querySelector("#tes-settings-nightmode #gray input").checked) {
                            titleElem.querySelector("#tes-settings-nightmode > input").checked = true;
                            GM_setValue("tes-NightMode", true.toString());
                            settingsQuick["NightMode"] = true;
                            nightmodeToggle(false);
                            nightmodeToggle(true);
                        }
                    }
                }
                if (settingName == "tes-settings-nightmode-gray") {
                    if (value == null) {
                        let newValue = (!titleElem.querySelector("#tes-settings-nightmode #gray input").checked);
                        titleElem.querySelector("#tes-settings-nightmode #black input").checked = newValue;
                        settingsQuick["NightModeBlack"] = newValue;
                        GM_setValue("tes-NightModeBlack", newValue.toString());
                        //nightmodeToggle(newValue);

                        if (titleElem.querySelector("#tes-settings-nightmode #black input").checked || titleElem.querySelector("#tes-settings-nightmode #gray input").checked) {
                            titleElem.querySelector("#tes-settings-nightmode > input").checked = true;
                            GM_setValue("tes-NightMode", true.toString());
                            settingsQuick["NightMode"] = true;
                            nightmodeToggle(false);
                            nightmodeToggle(true);
                        }
                    }
                }
                if (settingName == "tes-settings-maxcamposition") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-maxcamposition").querySelector("input").checked;
                        settingsQuick["MaxedCamLeft"] = newValue;
                        GM_setValue("tes-MaxedCamLeft", newValue.toString());
                        maxCamPositionToggle(newValue);
                    }
                }
                if (settingName == "tes-settings-borderlesscams") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-borderlesscams").querySelector("input").checked;
                        settingsQuick["BorderlessCams"] = newValue;
                        GM_setValue("tes-BorderlessCams", newValue.toString());
                        borderlessCamsToggle(newValue);
                    }
                }
                if (settingName == "tes-settings-hidenewcams") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-hidenewcams").querySelector("input").checked;
                        settingsQuick["HideNewCams"] = newValue;
                        GM_setValue("tes-HideNewCams", newValue.toString());
                    }
                }
                /*if (settingName == "tes-settings-togglehiddenmsgs") {
                    if (value == null) {
                        let newValue = titleElem.getElementById("tes-settings-togglehiddenmsgs").querySelector("input").checked;
                        settingsQuick["ToggleHiddenMsgs"] = newValue;
                        GM_setValue("tes-ToggleHiddenMsgs", newValue.toString());

                        // Toggle display of all elements with relevant class.
                        //var elems = chatlogElem.querySelectorAll(".hideSpamChat");
                        //console.log('elems', elems)
                        //if (elems) {
                            // Get state from checkbox.
                            //var state = 'block';
                            //console.log('checkbox', settingsQuick["ToggleHiddenMsgs"], newValue)
                            //if (settingsQuick["ToggleHiddenMsgs"]) {
                            //    state = 'none';
                            //}

                            //for (var i = 0; i < elems.length; i++) {
                            //    var el = elems[i];
                            //    el.style.display = state;
                            //}
                        //}

                        if (newValue) {
                            messageParserAddCSSSpam(true)
                        } else {
                            messageParserAddCSSSpam(false)
                        }

                    }
                }*/
            } catch (e) {
                tcl("error settingsCheckboxUpdate: " + e.message);
            }
        }

        function fontToggle(arg) {
            try {
                arg ? bodyElem.classList.add("tes-changefont") : bodyElem.classList.remove("tes-changefont");
            } catch (e) {
                tcl("error fontToggle: " + e.message);
            }
        }

        function borderlessCamsToggle(arg) {
            try {
                if (videolistElem.querySelector(camQueryString) != null) {
                    var camElems = videolistElem.querySelectorAll(camQueryString);
                    for (i = 0; i < camElems.length; i++) {
                        var camItem = camElems[i].querySelector("tc-video-item").shadowRoot;
                        var camElem = camItem.querySelector(".video");
                        arg ? camElem.classList.add("tes-borderlesscams") : camElem.classList.remove("tes-borderlesscams");

                        if (arg) {
                            camItem.appendChild(camParserAddCSSBorderless());
                        } else {
                            var child = camItem.getElementById("camItemCSShtmlBorderless");
                            if (child) {
                                camItem.removeChild(child);
                            }
                        }
                    }
                }
                arg ? videolistCSS.classList.add("tes-borderlesscams") : videolistCSS.classList.remove("tes-borderlesscams");
            } catch (e) {
                tcl("error borderlessCamsToggle: " + e.message);
            }
        }

        function maxCamPositionToggle(arg) {
            try {
                arg ? videolistCSS.classList.add("tes-leftcam") : videolistCSS.classList.remove("tes-leftcam");
            } catch (e) {
                tcl("error maxCamPositionToggle: " + e.message);
            }
        }

        function notificationHider() {
            try {
                chatlogContainer = chatlogElem.querySelector("#chat-content");
                settingsQuick["NotificationsOff"] ? chatlogContainer.classList.add("tes-notif-off") : chatlogContainer.classList.remove("tes-notif-off");
            } catch (e) {
                tcl("error notificationHider: " + e.message);
            }
        }

        function copyChatlog(opt = null) {
            try {
                if (opt == "close") {
                    chatlogDisplayElem.classList.remove("show");
                    chatlogDisplayClose.classList.remove("show");
                    setTimeout(function () {
                        chatlogDisplayCont.classList.remove("show");
                    }, 300);
                    return;
                }

                var filename = "tinychat_" + roomName + "_" + joinDate + "_" + joinTime.replace(/:/g, ".") + "-chatlog.log";
                var chatlogText = "Tinychat room " + roomName + " on " + joinDate + " " + joinTime + newline + "Users (" + usersCountInitial + "): " + userlistInitial + newline + chatlogMain;

                var downloadLink = 'data:text/plain;charset=utf-8,' + encodeURIComponent(chatlogText);
                var downloadElem = document.createElement('a');
                downloadElem.setAttribute("href", downloadLink);

                downloadElem.setAttribute("download", filename);

                if (opt == "download") {
                    // if (browserFirefox) showUpdateNotifier("Chat log downloading doesn't work in Firefox yet.");
                    // else downloadElem.click();
                    downloadElem.click();
                }
                if (opt == "view" || opt == null) {
                    if (typeof chatlogDisplayCont == "undefined") {
                        chatlogDisplayCont = chatlogElem.querySelector("#tes-chatlogDisplay");
                        chatlogDisplayElem = chatlogDisplayCont.querySelector("textarea");
                        chatlogDisplayClose = chatlogDisplayCont.querySelector("#close");
                    }
                    chatlogDisplayElem.value = chatlogText;
                    chatlogDisplayCont.classList.add("show");
                    setTimeout(function () {
                        chatlogDisplayElem.classList.add("show");
                        chatlogDisplayClose.classList.add("show");
                    }, 50);
                    chatlogDisplayElem.scrollTop = chatlogDisplayElem.scrollHeight;
                }
            } catch (e) {
                tcl("error copyChatlog: " + e.message);
            }
        }

        // Toggle between old chatbox and new one.
        function toggleDefaultChat() {
            if (chatlogElem.querySelector('#chat-content').style.display != 'none') {
                chatlogElem.querySelector('#chat-content').style.display = 'none';
                chatlogElem.querySelector('#chat-content-new').style.display = '';
                GM_setValue("defaultChat", false);
            } else {
                chatlogElem.querySelector('#chat-content').style.display = '';
                chatlogElem.querySelector('#chat-content-new').style.display = 'none';
                GM_setValue("defaultChat", true);
            }
        }

        function getFormattedDateTime(d, opt = null) {
            try {
                if (opt == "time") return d.toLocaleTimeString('en-US', {
                    hour12: false
                });
                else return d.toLocaleDateString('zh-CN', {
                    'year': 'numeric',
                    'month': '2-digit',
                    'day': '2-digit'
                }).replace(/\//g, "-");
            } catch (e) {
                tcl("error getFormattedDateTime: " + e.message);
            }
        }

        function mentionsManager(mode) {
            try {
                var inputElem = titleElem.querySelector("#tes-settings #tes-settings-mentions input.text");
                // phrases = inputElem.value.split(",");
                var phrase = inputElem.value;
                if (phrase.endsWith(",")) {
                    phrase = phrase.slice(0, -1);
                }
                if (phrase.startsWith(",")) {
                    phrase = phrase.slice(1);
                }

                if (mode == "save") {
                    GM_setValue("tes-Mentions", phrase);
                    settingMentions = phrase.split(",").map(x => x.trim());
                    inputElem.value = phrase;
                }
                if (mode == "load") {
                    var loadedMentions = GM_getValue("tes-Mentions");
                    if (loadedMentions != undefined) {
                        inputElem.value = loadedMentions;
                        settingMentions = loadedMentions.split(",").map(x => x.trim());
                    }
                }

                /*var phrase = phrase.toString();
                if (mode == "save") {
                    settingMentions.push(phrases);
                    GM_setValue("tes-Mentions", JSON.stringify(setting_Mentions));
                }
                if (mode == "load") {
                    var mentions = JSON.parse(GM_getValue("tes-Mentions"));
                    settingMentions = mentions;
                    inputElem.value = settingMentions.join();
                }*/
            } catch (e) {
                tcl("error mentionsManager: " + e.message);
            }
        }

        function ignoredManager(mode) {
            try {
                var inputElem = titleElem.querySelector("#tes-settings #tes-settings-ignored input.text");
                // phrases = inputElem.value.split(",");
                var phrase = inputElem.value;
                if (phrase.endsWith(",")) {
                    phrase = phrase.slice(0, -1);
                }
                if (phrase.startsWith(",")) {
                    phrase = phrase.slice(1);
                }

                if (mode == "save") {
                    GM_setValue("tes-IgnoredUsers", phrase);
                    settingIgnoredUsers = {};
                    let names = phrase.split(",").map(x => x.trim().toUpperCase());

                    for (let i=0; i < names.length; i++) {
                        let name = names[i];
                        settingIgnoredUsers[name] = 1;
                    }

                    inputElem.value = phrase;
                }
                if (mode == "load") {
                    var loadedIgnoredUsers = GM_getValue("tes-IgnoredUsers");
                    if (loadedIgnoredUsers != undefined) {
                        inputElem.value = loadedIgnoredUsers;

                        settingIgnoredUsers = {};
                        let names = loadedIgnoredUsers.split(",").map(x => x.trim().toUpperCase());

                        for (let i=0; i < names.length; i++) {
                            let name = names[i];
                            settingIgnoredUsers[name] = 1;
                        }
                    }
                }
            } catch (e) {
                tcl("error ignoredManager: " + e.message);
            }
        }

        function bannedManager(mode) {
            try {
                var inputElem = titleElem.querySelector("#tes-settings #tes-settings-banned input.text");
                // phrases = inputElem.value.split(",");
                var phrase = inputElem.value;
                if (phrase.endsWith(",")) {
                    phrase = phrase.slice(0, -1);
                }
                if (phrase.startsWith(",")) {
                    phrase = phrase.slice(1);
                }

                if (mode == "save") {
                    GM_setValue("tes-BannedUsers", phrase);

                    settingBannedUsers = {};
                    let names = phrase.split(",").map(x => x.trim().toUpperCase());

                    for (let i=0; i < names.length; i++) {
                        let name = names[i];
                        settingBannedUsers[name] = 1;
                    }
                    
                    inputElem.value = phrase;
                }
                if (mode == "load") {
                    var loadedBannedUsers = GM_getValue("tes-BannedUsers");
                    if (loadedBannedUsers != undefined) {
                        inputElem.value = loadedBannedUsers;

                        settingBannedUsers = {};
                        let names = loadedBannedUsers.split(",").map(x => x.trim().toUpperCase());

                        for (let i=0; i < names.length; i++) {
                            let name = names[i];
                            settingBannedUsers[name] = 1;
                        }
                    }
                }
            } catch (e) {
                tcl("error bannedManager: " + e.message);
            }
        }

        function declareGlobalVars() {
            try {
                globalCSS = `:root {
		--textcolor: black;
		--bgcolor: white;

		--nightmode-bgcolor: #2d373a;
		--nightmode-trimcolor: #3c4a4e;
		--nightmode-textcolor: #9faaad;
		--nightmode-textSecondarycolor: #4e5f65;
		--nightmode-headerButtonscolor: #3986a7;

		--nightmodeBlack-bgcolor: black;
		--nightmodeBlack-trimcolor: #222;

	}

	* {
	  scrollbar-color: #ccc transparent;
	  scrollbar-width: thin;
	}
	.tes-nightmode * { scrollbar-color: #242C2E transparent; }
	.tes-nightmode.blacknight * { scrollbar-color: #111 transparent; }

	.list-item > span[data-status="gold"] {
		color: unset !important;
	}

	.list-item > span[data-status="extreme"] {
		color: unset !important;
	}

	.list-item > span[data-status="pro"] {
		color: unset !important;
	}

    #notify-bar, #toast, #input-unread {
        display: none;
    }
	`;

                camItemCSShtml = `
		<style id="camItemCSS">` + globalCSS + `
			.icon-tes-max {
				position: absolute;
				top: -40%;
				right: 0;
				z-index: 9;
				background: none;
				border: 0;
			}
			.icon-tes-max:hover { cursor: pointer; }
			.icon-tes-max path { fill: #04caff; }

			.video:hover .icon-tes-max {
				top: 40%;
				transition: top .2s ease .2s,
						left .2s ease .2s,
						right .2s ease .2s,
						opacity .2s;
				}

			/* Disable cam border
			.video:after { border: none; }
			.video > div { background-color: unset; }
			video,
			.video > div > .overlay {
				border-radius: 10px;
			}
			*/
			.video { transition: .4s; }
			.tes-borderlesscams.video { padding: 0; }
			.tes-borderlesscams.video:after { display: none; }

			.tes-nightmode.video:after { border-color: var(--nightmode-bgcolor); }
					.tes-nightmode.blacknight.video:after { border-color: var(--nightmodeBlack-bgcolor); }
			.tes-nightmode.blacknight.video > div > .waiting { background: #111; }
			.tes-nightmode.blacknight.video > div { background-color: #111; }

            /* Fix pro blur. */
            div.video > div > div.blured {
                display: none !important;
            }

            div.video > div > video {
                filter: none !important;
            }

            .icon-resize {opacity: 0 !important;} /* Original resize is unused now. */
		</style>
	`;

                camItemCSShtmlNightmode = `.video:after { border-color: var(--nightmode-bgcolor); }`;
                camItemCSShtmlBlacknight = `
    		.video:after { border-color: var(--nightmodeBlack-bgcolor); }
			.video > div > .waiting { background: #111; }
			.video > div { background-color: #111; }
    `;
                camItemCSShtmlBorderless = `
			.video { padding: 0; }
			.video:after { display: none; }
    `;

                // Includes talking indicator css.
                camMaxCSShtml = `
	<style id="camMaxCSS">` + globalCSS + `
		.tes-max .js-video {
			width: 15%!important;
			z-index: 1;
		}
		.tes-leftcam .tes-max .js-video {
			float: right;
			order: 2;
		}
		.tes-leftcam .tes-max .tes-maxedCam {
			float: left;
			order: 1;
		}

		div[data-video-count="5"] .tes-max .js-video:not(.tes-maxedCam),
		div[data-video-count="6"] .tes-max .js-video:not(.tes-maxedCam),
		div[data-video-count="7"] .tes-max .js-video:not(.tes-maxedCam)
		{ width: 20%!important; }
		.tes-max.tes-camCount2 .js-video { width: 30%!important; }
		.tes-max.tes-camCount10-11 .js-video { width: 16%!important; }
		.tes-max.tes-camCount12 .js-video { width: 14%!important; }

		:not(.hidden) + .tes-max.tes-camCount12 .js-video,
		:not(.hidden) + .tes-max.tes-camCount10-11 .js-video,
		:not(.hidden) + .tes-max .js-video
		{ width: 30%!important; }
		:not(.hidden) + .tes-max.tes-camCount2 .js-video { width: 60%!important; }

		.tes-max .js-video.tes-maxedCam,
		:not(.hidden) + .tes-max .js-video.tes-maxedCam { width: 70%!important; }

		@media screen and (max-width: 1400px) {
			.tes-max .js-video { width: 20%!important; }

			.tes-max.tes-camCount2 .js-video { width: 40%!important; }
			.tes-max.tes-camCount10-11 .js-video { width: 18%!important; }
			.tes-max.tes-camCount12 .js-video { width: 15%!important; }

			.tes-max .js-video.tes-maxedCam,
			:not(.hidden) + .tes-max .js-video.tes-maxedCam { width: 60%!important; }
		}
	</style>
	`;

                camMaxButtonHtml = `
		<button class="icon-tes-max" id="maxbutton-camName">
			<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
				<path d="M14.37 12.95l3.335 3.336a1.003 1.003 0 1 1-1.42 1.42L12.95 14.37a8.028 8.028 0 1 1 1.42-1.42zm-6.342 1.1a6.02 6.02 0 1 0 0-12.042 6.02 6.02 0 0 0 0 12.042zM6.012 9.032a.996.996
				0 0 1-.994-1.004c0-.554.452-1.003.994-1.003h4.033c.55 0 .994.445.994 1.003 0 .555-.454 1.004-.995 1.004H6.012z" fill-rule="evenodd"></path>
				<path d="M0 .99C0 .445.444 0 1 0a1 1 0 0 1 1 .99v4.02C2 5.555 1.556 6 1 6a1 1 0 0 1-1-.99V.99z" transform="translate(7 5)" fill-rule="evenodd"></path>
			</svg>
		</button>
	`;

                camTalkingIndicatorHtml = `
        <div class="icon-tes-talking" id="micIndicator-camName">
			<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
                <path d="M12 2c1.103 0 2 .897 2 2v7c0 1.103-.897 2-2 2s-2-.897-2-2v-7c0-1.103.897-2 2-2zm0-2c-2.209 0-4 1.791-4 4v7c0 2.209 1.791 4 4 4s4-1.791 4-4v-7c0-2.209-1.791-4-4-4zm8 9v2c0 4.418-3.582 8-8 8s-8-3.582-8-8v-2h2v2c0 3.309 2.691 6 6 6s6-2.691 6-6v-2h2zm-7 13v-2h-2v2h-4v2h10v-2h-4z"/>
            </svg>
		</div>
        <style>
            .icon-tes-talking {
              position: absolute;
              height: 22px;
              width: 22px;
              top: 8px;
              padding: 0;
              border: none;
              background-color: transparent;
              cursor: pointer;
              opacity: 0;
              transition: .05s;

              left: 50%;
              margin-left: -11px;
            }

            .icon-tes-talking > svg {
              fill: white;
              filter: drop-shadow(0px 0px 2px black);
            }
        </style>
    `;
            } catch (e) {
                tcl("error declareGlobalVars: " + e.message);
            }
        }

        function injectCSS(cssName = null) {
            try {
                // Indenting is purposely wrong, for readability
                var insertPosition = "beforeend";
                headElem = document.querySelector("head");
                browserSpoofedChrome = (headElem.innerHTML.includes("Shady DOM styles for") ? true : false);
                if (browserSpoofedChrome) tcl("browserSpoofedChrome");
                var firefoxCSS = "";

                { // titleCSS
                    if (browserFirefox) var firefoxCSS = `
		#tes-settings-nightmode .nightmode-colors:after { display: none; }
		#tes-settings-nightmode .nightmode-colors {

		}
		#tes-settings-nightmode > span {
			position: relative;
			top: -4px;
		}
		#tes-settings-nightmode .label { margin-right: 3px; }
		#tes-settings-nightmode .sublabel {
			margin-left: unset;
			position: relative;
			top: -2px;
		}
		#tes-settings-nightmode .colorCont {
			position: relative;
			top: -2px;
		}
	`;

                    titleCSShtml = `
	<style id="titleCSS" scope="tinychat-title">` + globalCSS + `
		@keyframes ease-to-left {
			0% {right: -50px; opacity: 0;}
			100% {right: 0; opacity: 1;}
		}
		@keyframes ease-to-right {
			0% {right:auto;}
			100% {right:0;}
		}
		@keyframes ease-to-bottom-21px {
			0% {top:-300px; opacity: 0;}
			100% {top:0; opacity: 1;}
		}
		#tes-header-grabber {
		    position: absolute;
			top: 88px;
			right: 50%;
			background: white;
			width: 60px;
			height: 20px;
			border: #ddd 1px solid;
			border-radius: 19px;
			text-align: center;
			color: #b4c1c5;
			cursor: pointer;
			transition: .4s;
		}
		#tes-header-grabber:hover {
			background: #e9eaea;
			z-index: 1;
			border-bottom: 0px;
		}
		.tes-headerCollapsed #tes-header-grabber {
			top: 9px;
			background: #f6f6f6;
			border-top: 0;
			z-index: 9;
			border-radius: 11px;
			line-height: 11px;
			border-top-left-radius: 0;
			border-top-right-radius: 0;
			height: 12px;
		}
		.tes-headerCollapsed:hover #tes-header-grabber {
			height: 29px;
			line-height: 43px;
		}
		input {
			border: 1px solid #c4d4dc;
			line-height: 16px;
			padding-left: 3px;
		}
		.label ~ input {
			border-bottom-left-radius: 6px;
			border-top-left-radius: 6px;
		}
		input ~ button {
			border-bottom-right-radius: 6px;
			border-top-right-radius: 6px;
		}
		input[type="button"], button {
			display: inline;
			padding: 0 15px;
			border: 0;
			box-sizing: border-box;
			letter-spacing: 1px;
			font-size: 12px;
			font-weight: bold;
			line-height: 20px;
			text-align: center;
			transition: .2s;
			outline: none;
		}
		.blue-button {
			color: #fff;
			background-color: #41b7ef;
		}
		.blue-button:hover {
		    background-color: #54ccf3;
		}
		.blue-button:active {
		    background-color: #38a8dd;
		}
		.tes-setting-container {
			line-height: initial;
		}
		#tes-settings { color: var(--textcolor); }
		#tes-settings > div {
			/*animation: ease-to-bottom-21px .2s ease 0s 1;*/
			position: relative;
			top: 0;
			height: 100px;
		}
		@media screen and (max-width: 1000px) {
			#tes-settings > div {
				height: 92px;
			}
		}
		#tes-settings .hidden { display: none; }
		#tes-settings #title {
			font-weight: bold;
			margin-top: 6px;
            width: 600px;
		}
		#tes-settings {
			transition: all .4s ease-in-out;
			animation: ease-to-bottom-21px .2s ease 0s 1;
			/*max-height: 10%;*/
			font-size: 11px;
			flex: none;
			overflow: hidden;
			z-index: 7;
			position: absolute;
			top: -2px;
			right: ` + (giftsElemWidth + 10).toString() + `px;
		}
		@media screen and (max-width: 1000px) {
			#tes-settings {
				right: 37px!important;
				top: -20px;
			}
			#tes-settings.tes-open {
				top: 6px;
			}
			#tes-settingsGear {
				font-size: 52px!important;
			}
			#room-header-gifts-buttons > #give-gift {
				width: 102px;
			}
		}
		@media screen and (max-width: 600px) {
			#tes-settings {
				right: -4px!important;
    			top: 19px;
			}
		}
		#tes-settings:hover {
			overflow: visible;
		}
        #tes-settings-mentions {
            max-width: 360px;
        }
		#tes-settings-mentions .inputcontainer {
			float: right;
			position: relative;
			top: -3px;
		}
        #tes-settings-ignored {
            max-width: 360px;
            top: 54px;
        }
		#tes-settings-ignored .inputcontainer {
			float: right;
			position: relative;
			top: 3px;
		}
        #tes-settings-banned {
            max-width: 360px;
            top: 8px;
        }
		#tes-settings-banned .inputcontainer {
			float: right;
			position: relative;
			top: 3px;
		}
		#tes-settingsGear {
			font-size: 70px;
			color: #38cd57;
			color: #53b6ef;
			float: right;
		}
		#tes-settingsGear:hover {
			cursor: pointer;
			color: #7ccefe;
		}
		.tes-open #tes-settingsGear {
			background: white;
			border-bottom-right-radius: 15px;
			border-top-right-radius: 15px;
			border: #ddd 1px solid;
			border-left: 0;
			/*transition: all .2s linear;*/
			}
		#tes-settingsGear span {
			display: block;
			transition: transform 0.4s ease-in-out;
		}
		.tes-open #tes-settingsGear span {
			transform: rotate(-90deg);
		}
		#tes-settingsBox {
			background: white;
			padding: 0px 10px 0px 10px;
			float: left;
			border: #53b6ef 1px solid;
			border-top-left-radius: 12px;
			border-bottom-left-radius: 12px;
			animation: ease-to-left .2s ease 0s 1;
			right: 0;
		}
		#tes-settingsBox.hidden {
			animation: ease-to-right .2s ease 0s 1;
			display: visible;
			/*position: relative; right: -1000px;*/
		}

		#tes-settings-nightmode .nightmode-colors {
			width: 0px;
			height: 0px;
		}
		#tes-settings-nightmode .nightmode-colors:after {
			content: " ";
			border-radius: 3px;
			height: 11px;
			width: 11px;
			margin-left: 3px;
			top: -9px;
			position: relative;
			display: block;
		}
		#tes-settings-nightmode .nightmode-colors:checked:after {
			border: #41a9c1 1px solid;
		}
		#black .nightmode-colors:after { background: black; }
		#gray .nightmode-colors:after { background: #575e60; }
		#tes-settings-nightmode .sublabel { margin-left: 15px; }

		/*** Inline with header ***/
		#tes-settingsBox {
			border-bottom-width: 0;
			border-top-left-radius: 0px;
			border-bottom-left-radius: 0px;
		}
		#tes-settingsGear {
			display: table;
		}
		#tes-settingsGear span {
			display: table-cell;
			vertical-align: middle;
		}
		/*** *************   ***/

		#tes-settings .tes-setting-container > input[type=checkbox]:first-child {
			margin: 0;
			margin-right: 1px;
			float: left;
			position: absolute;
			left: 8px;
		}
		#tes-settings .right {
			position: absolute;
			left: 210px;
		}
        #tes-settings .rightright {
			position: absolute;
			left: 380px;
		}
        .hideSpamChat {
            display: none;
        }
		#tes-settings-maxcamposition { top: 54px; }
		#tes-settings-borderlesscams { top: 67px; }
        #tes-settings-hidenewcams { top: 80px; }
        #tes-settings-togglehiddenmsgs { top: 41px; }
		#tes-settings .label {
			margin-right: 4px;
			margin-left: 16px;
		}
		#tes-settings .right .label, #tes-settings .rightright .label {
			margin-left: 24px;
		}
		#tes-settings .info{
			margin-left: 3px;
			color: #0d94e3;
			font-weight: bold;
			font-family: Arial;
			border: #0d94e3 1px solid;
			border-radius: 16px;
			height: 1em;
			width: 1em;
			text-align: center;
			display: inline-block;
		}
		#tes-settings .info:hover:after{
			font-weight: normal;
			padding: 4px 7px 4px 7px;
			border-radius: 7px;
			color: white;
			background: #61787f;
			content: attr(data-title);
			display: inline-block;
			position: absolute;
			top: 52px;
			left: 0;
			z-index: 9;
		}
		/*#tes-settings .label:hover:before{
			border: solid;
			border-color: #61787f transparent;
			border-width: 0px 6px 6px 6px;
			top: 10px;
			content: "";
			left: 8%;
			position: relative;
			display: inline-block;
		}*/

		#tes-settings a:visited, #tes-settings a:link {
			text-decoration: none;
			color: inherit;
		}
		#tes-settings a:hover {
			color: #53b6ef;
		}

		#room-header {
			height: 100px;
			max-height: unset;
			min-height: unset;
			transition: all .4s ease-in-out;
		}
		#room-header.tes-headerCollapsed {
			height: 10px;
		}
		#room-header.tes-headerCollapsed:hover {
			height: 27px;
		}
		@media screen and (max-width: 600px) {
			#room-header {
				min-height: inherit;
				max-height: inherit;
			}
		}
		#room-header-info {
			padding: 0;
			padding-right: 45px;
		}
        #room-header-info > h1 {
            user-select: text;
        }
		#room-header-info-text {
			height: auto;
            user-select: text;
		}
        #room-header-info-details, #room-header-info-details * {
            user-select: text;
		}
		@media screen and (max-width: 600px) {
			#room-header-info-text {
				height: inherit;
			}
		}
		#room-header-avatar {
			margin: 2px 10px 0 35px;
			height: 90px;
			min-width: 90px;
			max-width: 90px;
			transition: all .5s linear;
		}
		#room-header-avatar:hover {
			border-radius: unset;
			z-index: 10000;
			width: 150px;
			height: 100px;
			overflow: unset;
		}
		.tes-headerCollapsed:hover #room-header-avatar:hover {
			z-index: 9;
		}
		#room-header-gifts {
			padding: 10px 10px;
		}

		.tes-headerCollapsed #tes-settingsGear {
			font-size: 33px;
		}
		.tes-headerCollapsed #tes-settings > div {
		    height: fit-content;
		}
		.tes-headerCollapsed #tes-settingsBox {
			border-width: 1px;
			border-radius: 7px;
			border-top-right-radius: 0;
			padding-bottom: 7px;
		}
		.tes-headerCollapsed #tes-settings {
			top: 13px;
			right: 0;
		}
		#tes-settings > div#tes-updateNotifier {
			top: -200px;
			margin-right: -33px;
			float: left;
			border: #53b6ef 1px solid;
			border-radius: 8px 0 0px 8px;
			padding: 5px;
			padding-right: 32px;
			height: auto;
			transition: visibility 0s, opacity 0.5s linear;
			background: white;
		}
		#tes-settings.tes-open > div#tes-updateNotifier {
			visibility: hidden;
			opacity: 0;
			width: 0;
			height: 0;
			padding: 0;
		}
		#tes-settings > div#tes-updateNotifier:hover { cursor: pointer; }
		.tes-closeButtonSmall {
			float: left;
			padding-right: 5px;
			color: #41b7ef;
			padding-left: 5px;
		}
		#tes-settings > div#tes-updateNotifier.visible { top: 38px; }
		.tes-closeButtonSmall:hover { color: #7ccefe; }

		#room-header.tes-nightmode,
		.tes-nightmode #tes-header-grabber {
			background-color: var(--nightmode-bgcolor);
			border-color: var(--nightmode-trimcolor);
		}
				#room-header.tes-nightmode.blacknight,
				.tes-nightmode.blacknight #tes-header-grabber {
					background-color: var(--nightmodeBlack-bgcolor);
					border-color: #222;
					border-bottom-color: #222;
				}
		.tes-nightmode #tes-header-grabber:hover { background-color: var(--nightmode-trimcolor); }
				.tes-nightmode.blacknight #tes-header-grabber:hover { background-color: #141414; }
		.tes-nightmode #room-header-info-details > span:after { background-color: var(--nightmode-bgcolor); }
				.tes-nightmode.blacknight #room-header-info-details > span:after { background-color: var(--nightmodeBlack-bgcolor); }
		.tes-nightmode #tes-header-grabber { color: #565e61; }
		.tes-nightmode #room-header-info > h1 { color: var(--nightmode-textcolor); }
		.tes-nightmode #room-header-info > h1:after,
		.tes-nightmode #room-header-info-text:after {
			opacity: 0;
		}
		.tes-nightmode #room-header-gifts-items { background-color: #313c3f; }
		.tes-nightmode #room-header-gifts-items > a > img { mix-blend-mode: multiply; }
		.tes-nightmode #room-header-gifts-items:hover > a > img { mix-blend-mode: unset; }
		.tes-nightmode #room-header-info-details > a { color: #417186; }
		.tes-nightmode #tes-settings { color: #98a1a4; }
		.tes-nightmode #tes-settingsGear { color: #145876; }
		.tes-nightmode #tes-settingsGear:hover { color: #1c7ca6; }
		.tes-nightmode #tes-settingsBox,
		.tes-nightmode .tes-open #tes-settingsGear {
			background-color: #354245;
			border-color: var(--nightmode-trimcolor);
		}
				.tes-nightmode.blacknight #tes-settingsBox,
				.tes-nightmode.blacknight .tes-open #tes-settingsGear {
					background-color: #222;
					border-color: #333;
				}
		.tes-nightmode #tes-settings > div#tes-updateNotifier { border-color: #5d7883; }
		.tes-nightmode #tes-settings > div#tes-updateNotifier {
			background-color: #354245;
			border-color: #145876;
		}
		.tes-nightmode input {
			background: #626b6f;
			color: #c4c8ca;
			border-color: #79868b;
		}
				.tes-nightmode.blacknight input {
					background: #444;
					border-color: #666;
				}
		.tes-nightmode #tes-settings .info {
			color: var(--nightmode-headerButtonscolor);
			border-color: var(--nightmode-headerButtonscolor);
		}
		.tes-nightmode path { fill: var(--nightmode-headerButtonscolor); }
		.tes-nightmode circle { stroke: var(--nightmode-headerButtonscolor); }
		@media screen and (max-width: 800px) {
			.tes-nightmode #room-header-gifts { background-color: var(--nightmode-bgcolor); }
				.tes-nightmode.blacknight #room-header-gifts { background-color: var(--nightmodeBlack-bgcolor); }
			}
		.tes-nightmode #room-header-gifts-buttons > #upgrade { background-color: #6d551d; }
		.tes-nightmode #room-header-gifts-buttons > #upgrade:hover { background-color: #776231; }
		.tes-nightmode #room-header-gifts-buttons > #get-coins {
			background-color: #3a474b;
			border-color: #275b72;
			color: #317490;
		}
			.tes-nightmode.blacknight #room-header-gifts-buttons > #get-coins { background-color: #222; }
		.tes-nightmode #room-header-gifts-buttons > #get-coins:hover {
			background-color: #48626a;
			color: #5fa9c8;
		}
		.tes-nightmode #room-header-gifts-buttons > a {
			background-color: #275b72;
			color: #788f97;
		}
		.tes-nightmode #room-header-gifts-buttons > #give-gift:hover {
			background-color: #1a80a2;
			color: #a3b5d2;
		} ` + firefoxCSS + `
	</style>
	`;
                    titleCSS.insertAdjacentHTML(insertPosition, titleCSShtml);
                }

                { // videolistCSS
                    videolistCSShtml = `
	<style id="videolistCSS" scope="tc-videolist">` + globalCSS + `
		#videos-header {
			height: 10px;
			min-height: 10px;
			background: none!important;
			z-index: 5;
		}
		#videolist.tes-sidemenuCollapsed { width: 93%; }
		#Fvideolist * {
			width: 75%!important;
			display: contents;
			float: right;
			flex-direction: column;
		}
		#Fvideos {
			flex-direction: unset;
			flex-wrap: unset;
		}
		#videos-header > span {
			line-height: initial;
			position: relative;
			top: 1px;
			background: none;
		}
		#videos-header > span > svg {
			height: 16px;
			padding: 0;
		}
		#youtube.video:after { border: none; }

		.js-video { transition: all .4s ease-in-out; }
		.tes-max-noAnim .js-video { transition: unset; }
		.tes-max.videos-items:last-child { edisplay: block; }
		/* Smaller footer buttons */
		#videos-footer {
			height: 43px;
			min-height: unset;
			padding-bottom: 0;
		}

		#videos-footer > div {
			height: 35px;
			min-height: unset;
			line-height: 35px;
		}
		#videos-footer-broadcast-wrapper > div {
			height: 35px;
			line-height: 37px;
			font-size: 15px;
		}
		#videos-footer-broadcast-wrapper > #videos-footer-push-to-talk { line-height: 34px; }
		#videos-footer > div svg { transform: scale(.70); }
		#videos-footer-broadcast-wrapper > #videos-footer-submenu-button:before { top: 14px; }

		#videolist.tes-nightmode { background: var(--nightmode-bgcolor); }
				#videolist.tes-nightmode.blacknight { background: var(--nightmodeBlack-bgcolor); }
		.tes-nightmode #videos-footer-youtube { background-color: #723e3c; }
				.tes-nightmode.blacknight #videos-footer-youtube { background-color: #4e1f1d; }
		.tes-nightmode #videos-footer-youtube:hover { background-color: #a83c38; }
				.tes-nightmode.blacknight #videos-footer-youtube:hover { background-color: #742825; }

		.tes-nightmode #videos-footer-broadcast,
		.tes-nightmode #videos-footer-broadcast-wrapper > #videos-footer-submenu-button {
			background-color: #31684c;
			color: #519472;
		}
				.tes-nightmode.blacknight #videos-footer-broadcast,
				.tes-nightmode.blacknight #videos-footer-broadcast-wrapper > #videos-footer-submenu-button {
					background-color: #12261c;
					color: #2d5240;
				}
		.tes-nightmode #videos-footer-broadcast:hover,
		.tes-nightmode #videos-footer-broadcast-wrapper > #videos-footer-submenu-button:hover {
			background-color: #338e5f;
			color: #82d9ad;
		}
				.tes-nightmode.blacknight #videos-footer-broadcast:hover,
				.tes-nightmode.blacknight #videos-footer-broadcast-wrapper > #videos-footer-submenu-button:hover {
					background-color: #17402b;
					color: #41956b;
				}
		.tes-nightmode #videos-footer-broadcast-wrapper > #videos-footer-submenu-button:before { border-color: #519472 transparent; }
				.tes-nightmode.blacknight #videos-footer-broadcast-wrapper > #videos-footer-submenu-button:before { border-color: #41956b transparent; }
		.tes-nightmode #videos-footer-broadcast-wrapper > #videos-footer-submenu-button:hover:before { border-color: #82d9ad transparent; }

		.tes-nightmode #videos-footer-push-to-talk { background-color: #31684c; }
		.tes-nightmode #videos-footer-push-to-talk path { fill: #82d9ad; }
		.tes-nightmode #videos-footer-push-to-talk:hover { background-color: #338e5f; }

		#videos-footer-broadcast-wrapper.active-ptt > #videos-footer-push-to-talk { background-color: #404f54; }
		#videos-footer-broadcast-wrapper.active-ptt > #videos-footer-push-to-talk path { fill: #74817a; }

		.tes-nightmode #videos-footer-broadcast-wrapper.active > #videos-footer-broadcast,
		.tes-nightmode #videos-footer-broadcast-wrapper.active > #videos-footer-submenu-button {
			background-color: #404f54;
			color: #74817a;
		}
				.tes-nightmode.blacknight #videos-footer-broadcast-wrapper.active > #videos-footer-broadcast,
				.tes-nightmode.blacknight #videos-footer-broadcast-wrapper.active > #videos-footer-submenu-button {
					background-color: #222;
					color: #555;
				}
		.tes-nightmode #videos-footer-broadcast-wrapper.active > #videos-footer-submenu-button:before { border-color: #74817a transparent; }
		.tes-nightmode #videos-header path { fill: var(--nightmode-headerButtonscolor); }
		.tes-nightmode #videos-header path[fill="none"] { stroke: var(--nightmode-headerButtonscolor); fill: none; }
		.tes-nightmode .videos-header-volume { border-color: #3a474b; }
				.tes-nightmode.blacknight .videos-header-volume { border-color: #222; }
		.tes-nightmode #videos-footer-youtube path { fill: #996d6c; }
				.tes-nightmode.blacknight #videos-footer-youtube path { fill: #634645; }
	</style>
	`;
                    videolistCSS.insertAdjacentHTML(insertPosition, videolistCSShtml);
                }

                { // chatlistCSS
                    chatlistCSShtml = `
	<style id="chatlistCSS" scope="tinychat-chatlist">` + globalCSS + `
		#chatlist.tes-mod { margin-top: 22px; }
		#chatlist > div > span {
			padding-left: 1px;
		}
		#chatlist > #header {
			top: 3px;
			height: auto;
		}

        .list-item > span > span > span.send-gift {
			display: none;
		}

		/*** --- this block is in chatlistCSS & userlistCSS --- ***/
			.list-item > span > img {
				right: 13px;
				left: auto;
			}
			.list-item > span[data-status]:before {
				left: auto;
				right: 0;
			}
			.list-item > span > span {
				background: none!important;
				box-shadow: none!important;
			}
		/*** ---                                        --- ***/

		.close-instant > path {
			fill: white;
		}
		.list-item > span > span { /* gift and close buttons */
			right: 16px;
		}
		.list-item > span:hover > span { /* gift and close buttons */
			right: 16px;
			background: var(--nightmode-bgcolor);
		}

		/*** --- this block is in chatlistCSS & userlistCSS --- ***/
		.tes-nightmode.blacknight .list-item > span,
		.tes-nightmode.blacknight .list-item > span> span {
			background: var(--nightmodeBlack-bgcolor);
		}
		.tes-nightmode.blacknight .list-item > span > span { box-shadow: 0 0 3px 3px var(--nightmodeBlack-bgcolor); }
		.tes-nightmode.blacknight .list-item > span:hover,
		.tes-nightmode.blacknight .list-item > span:hover > span,
		.tes-nightmode.blacknight .list-item + .list-item > span:hover,
		#chatlist.tes-nightmode.blacknight > #header ~ .list-item > span.active {
			background: #222;
		}
		/*** ---                                        --- ***/

	</style>
	`;
                    chatlistCSS.insertAdjacentHTML(insertPosition, chatlistCSShtml);
                }

                { // userlistCSS
                    userlistCSShtml = `
	<style id="userlistCSS" scope="tinychat-userlist">` + globalCSS + `
		#userlist > div > span {
			padding-left: 1px;
		}
		.list-item > span > span {
			right: auto;
			padding: 0 5px;
		}
		.list-item > span > .nickname {
			padding-right: 3px;
			transition: .5s;
		}
		.nickname.tes-myNick { color: #b3b3b3; }

        .list-item > span > span > span.send-gift {
			display: none;
		}

		/*** --- this block is in chatlistCSS & userlistCSS --- ***/
			.list-item > span > img {
				right: 13px;
				left: auto;
			}
			.list-item > span[data-status]:before {
				left: auto;
				right: 0;
			}
			.list-item > span > span {
				background: none;
				box-shadow: none;
			}
		/*** ---                                        --- ***/

		.list-item > span > span[data-moderator="1"]:before {
			filter: hue-rotate(226deg) saturate(4000%);
		}
		#userlist > #header {
			top: auto;
			height: auto;
			overflow: unset;
		}
		#header > span {
			width: unset!important;
			overflow: unset!important;
			}
		#button-banlist {
			right: -34px;
			position: fixed;
			top: 74px;
			left: 1px;
			width: 147px;
			transition: 1s;
			z-index: 8;
		}
		@media screen and (max-width: 1000px) {
			#button-banlist {
				top: -80px;
				left: 5px;
				width: 115px;
				position: absolute;
			}
		}
		.tes-sidemenuCollapsed #button-banlist {
			left: -100px;
			width: 10px;
			opacity: 0;
		}
		#contextmenu { z-index: 6; }
		#userlist .yourname, span[data-user-id="840113"] {
			color: white!important;
		}

		/*** --- this block is in chatlistCSS & userlistCSS --- ***/
		.tes-nightmode.blacknight .list-item > span,
		.tes-nightmode.blacknight .list-item > span> span {
			background: var(--nightmodeBlack-bgcolor);
		}
		.tes-nightmode.blacknight .list-item > span > span { box-shadow: 0 0 3px 3px var(--nightmodeBlack-bgcolor); }
		.tes-nightmode.blacknight .list-item > span:hover,
		.tes-nightmode.blacknight .list-item > span:hover > span,
		.tes-nightmode.blacknight .list-item + .list-item > span:hover,
		#chatlist.tes-nightmode.blacknight > #header ~ .list-item > span.active {
			background: #222;
		}
		/*** ---                                        --- ***/
		.tes-nightmode.blacknight .list-item > span:hover > span { box-shadow: 0 0 3px 3px #222; }
		.tes-nightmode.blacknight #button-banlist { background: #222; }
		.tes-nightmode.blacknight #button-banlist:hover { background: #00708f; }

		/*#userlist > div {
		  transition: 0s; animation-duration: 0s; -webkit-animation-duration: 0s;
		}*/
	</style>
	`;
                    userlistCSS.insertAdjacentHTML(insertPosition, userlistCSShtml);
                }

                { // userContextmenuCSS
                    userContextmenuCSShtml = `
	<style id="userContextmenuCSS" scope="tinychat-user-contextmenu">` + globalCSS + `
		#main {
			border: 1px solid rgba(0, 0, 0, .1);
		}
	</style>
	`;
                    userContextmenuCSS.insertAdjacentHTML(insertPosition, userContextmenuCSShtml);
                }

                { // bodyCSS
                    bodyCSShtml = `
	<style id="bodyCSS">` + globalCSS + `
		#nav-static-wrapper {
			width: 2px;
			opacity: .7;
		}
		@media screen and (max-width: 1000px) {
			#nav-static-wrapper {
				width: 82px;
				opacity: 1;
			}
		}
	   #content {
		   padding: 0;
		}

	    #menu-icon { transition: 1s; }
		.tes-sidemenuCollapsed #menu-icon {
			z-index: -1;
			opacity: 0;
		}
		body.tes-changefont {
		  font-family: sans-serif;
		}
		#header-user {
			left: 62px;
			bottom: 22px;
			transition: 1s;
		}
		.tes-sidemenuCollapsed #header-user { display: none; }
		@media screen and (max-width: 1000px) {
			#header-user {
				left: 21px;
			}
		}
		@media screen and (max-width: 600px) {
			#header-user {
				left: auto;
				right: 54px;
			}
		}
		@media screen and (min-width: 1000px) {
			#menu-icon:hover { opacity: 1; }
			#menu-icon {
				top: 4px;
				left: 19px;
				height: 12px;
				width: 109px;
				font-size: 10px;
				background: #04caff;
				border-radius: 6px;
				opacity: .8;
			}
			#menu-icon:after {
				position: absolute;
				top: 3px;
				left: 51px;
				content: "";
				height: 7px;
				width: 7px;
				border-width: 2px 2px 0px 0px;
				border-style: solid;
				border-color: #fff;
				box-sizing: border-box;
				transform: rotate(45deg);
				transition: .2s;
			}
			#menu-icon:hover:after {
				left: 55px;
			}
			#menu-icon.expanded:after {
				border-width: 0px 0px 2px 2px;
			}
			#menu-icon.expanded:hover:after {
				left: 40px;
			}
			#menu-icon > svg {
				opacity: 0;
			}
		}

		body.tes-nightmode {
			color: var(--nightmode-textcolor);
			background: var(--nightmode-bgcolor);
		}
				body.tes-nightmode.blacknight {
					color: gray;
					background: var(--nightmodeBlack-bgcolor);
				}
		.tes-nightmode.blacknight #nav-static-wrapper { background: var(--nightmodeBlack-bgcolor); }
	</style>
	`;
                    bodyCSS.insertAdjacentHTML(insertPosition, bodyCSShtml);
                }

                messageCSS = `
		/* PLACEHOLDER for general msg css modifications that always apply inside its own shadowroot. */
	`;

                messageCSSNightmode = `
         #html { color: var(--nightmode-textcolor); }

		.tes-nightmode.tes-mention-message { color: #e44a3f; }
		.tes-nightmode.message.system,
		.tes-nightmode #chat-content > .message.system {
			background-color: #313c3f;
			color: #677174;
		}
       .tes-nightmode.blacknight.message.system,
       .tes-nightmode.blacknight #chat-content > .message.system {
           background-color: #090909;
           color: #4d4d4d;
        }
     `;
                messageCSSBlacknight = `
         #html { color: gray; }
     `;

                messageCSSMention = `#html { color: red !important }`;

                messageCSSSpam = ` { display: none; }`; // Partial code! Implemented with JS.

                { // chatlogCSS
                    chatlogCSShtml = `
	<style id="chatlogCSS" scope="tinychat-chatlog">` + globalCSS + `

	    :host, #chat-wrapper {
	        position: relative !important;
	        height: 100% !important;
            max-height: unset !important;
	    }

		#chat-content-new {
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            min-height: 100%;
        }

        #chat-content-new a {
            color: #4682b4;
            pointer-events: all;
        }

		#chat-content > .message, #chat-content-new > .message {
			padding-bottom: 0;
			padding-top: 0!important;
			margin-bottom: 0;
			min-height: 0px!important;
		}
		/*
		#chat-content > .message:hover, #chat-content-new > .message:hover {
			background: rgba(0, 0, 0, 0.03);
		}
		*/

		.message a:first-child,
		.message a:first-child img:first-child {
			transition: .1s;
            pointer-events: none;
		}
        /*
		.message a:first-child:hover {
			width: 100px!important;
			height: 75px!important;
			z-index: 1000;
		}
        */

		#chat-content > .message.common, #chat-content-new > .message.common {
			margin-bottom: 5px;
		}
		#chat-content > .message.system {
			padding: 0;
		}
		#chat-content.tes-notif-off > .message.system {
			display: none;
		}
		#chat-content.tes-notif-off > .message.system.dontHide {
			display: initial;
		}
		#chat-instant > a:first-child,
		#chat-content > .message > a:first-child {
			top: auto;
		 }
		#chat-position { bottom: 3px; }
		#chat-position #input:before { background: none; }
		#chat-instant > a > .avatar,
		#chat-content > .message > a > .avatar {
			border-radius: unset;
		}
		#chat-content-new > .message {
		    position: relative;
		    left: 0;
		    margin-bottom: 10px;
		    animation: show-message .2s ease 0s 1;
		    -webkit-animation: show-message .2s ease 0s 1;
            padding: 10px 10px 0 40px;
            box-sizing: border-box;
            text-align: left;
            overflow-wrap: break-word;
		}
        #chat-content-new > .message > .avatar {
            border-radius: 100%;
            overflow: hidden;
            position: absolute;
            height: 32px;
            width: 32px;
            top: 0;
            left: 0;
            transform: scale(1);
            -webkit-transform: scale(1);
        }

        #chat-content-new > .message > .avatar.private {
            border: 2px solid #9c9cff;
        }

        #chat-content-new > .message > .avatar > img {
            position: relative;
            height: 100%;
            left: -7px;
        }
        #chat-content-new > .message > .nickname {
            display: inline-block;
            font-size: 14px;
            font-weight: 600;
            line-height: 20px;
            text-decoration: none;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            transition: .2s;
        }
        #chat-content-new > .message > .nickname.private {
            font-style: italic;
        }
		#timestamp, #chat-content-new > .message > .timestamp {
			font-size: 11px;
			position: absolute;
			right: 0;
            top: 0;
			padding-top: 3px;
            cursor: default;
		}
		#timestamp:hover {
			z-index: 500;
			background: var(--bgcolor);
			padding-left: 5px;
		}
		#chat-content > .message > .nickname, #chat-content-new > .message > .nickname {
			overflow: initial;
			line-height: initial;
            pointer-events: none;
		}
		#chat-content div.message.common:last-of-type, #chat-content-new div.message.common:last-of-type {
			margin-bottom: 10px;
		}
		#chat-instant-button.tes-loading {
			border: 0;
			font-size: x-large;
			animation: spin .5s linear infinite;
		}
		@keyframes spin {
			0% { transform: rotate(0deg); }
			100% { transform: rotate(360deg); }
		}
		#tes-chatlogDisplay {
			display: none;
			position: fixed;
			top: 50px;
			left: 50px;
			width: 90%;
			height: 80%;
			z-index: 7;
			cursor: default;
		}
		#tes-chatlogDisplay.show { display: unset; }
		#tes-chatlogDisplay * {
			float: left;
			height: 100%;
		}
		#tes-chatlogDisplay textarea {
			background: rgba(255, 255, 255, .8);
			transition: .2s;
			opacity: 0;
			border-radius: 6px;
			width: 90%;
		}
		#tes-chatlogDisplay textarea.show {
			opacity: 1;
		}
		#tes-chatlogDisplay #close {
			opacity: 0;
			transition: .2s;
			width: 40px;
			background: #41b7ef;
			height: 40px;
			border-top-right-radius: 10px;
			border-bottom-right-radius: 10px;
			position: relative;
			color: white;
			top: 40%;
			vertical-align: middle;
			font-size: 22px;
			text-align: center;
			padding-top: 8px;
			cursor: pointer;
		}
		#tes-chatlogDisplay #close:hover {
			background: #72caf3;
		}
		#tes-chatlogDisplay #close.show {
			opacity: 1;
		}
		#tes-chatlogButtons {
			position: absolute;
			top: 2px;
			left: 6px;
			font: 15px monospace;
			z-index: 11;
		}
		.tes-chatlogBut {
			padding: 2px;
			border-radius: 4px;
			border: silver 1px solid;
			color: silver;
			transition: .3s;
			width: 10px;
			height: 10px;
			overflow: hidden;
			cursor: pointer;
			opacity: 1;
			float: left;
		}
		.tes-chatlogBut:hover {
			width: 1.5em;
			color: var(--textcolor);
			border-color: var(--textcolor);
		}
		.tes-chatlogBut ~ .tes-chatlogBut { margin-left: 2px; }
		.tes-chatlogBut .icon { width: auto; }
		.tes-chatlogBut .label {
			width: 0;
			opacity: 0;
			overflow: hidden;
			transition: .3s;
			display: block;
			position: relative;
			top: -2px;
			left: 13px;
			font: 11px sans-serif;
			color: var(--textcolor);
		}
		.tes-chatlogBut:hover .label {
			opacity: 1;
			width: auto;
		}
		.tes-chatboxPM #tes-chatlogSave {
			opacity: 0;
			z-index: -5;
		}
		#tes-chatlogSave .icon {
			/* transform: scaleY(.6); */
			position: absolute;
			top: -1px;
			left: 4px;
		}
		#tes-chatlogSave .icon svg {
			width: 19px;
			height: 19px;
			position: relative;
			left: -3px;
		}
		#tes-chatlogSave .icon path {
			transform: scale(.08) scaleX(1.2) rotate(180deg);
			10%: 10px
			height:;
			fill: #ccc;
			transform-origin: 11px 12px;
		}
		#tes-chatlogSave:hover .icon path { fill: var(--textcolor); }
		#tes-chatlogSave:hover { width: 4.2em; }
		#tes-chatlogSave:hover .label { width: 4.3em; }
		#tes-chatlogView .icon {
			font-size: 10px;
			top: 1px;
			position: absolute;
		}
		#tes-chatlogView:hover { width: 2.5em; }

		#tes-chatToggle .icon {
			font-size: 10px;
			top: 1px;
			position: absolute;
		}
		#tes-chatToggle:hover { width: 3em; }

        #tes-chatHide .icon {
			font-size: 10px;
            font-weight: bold;
            color: #444;
			top: 1px;
			position: absolute;
		}

		.tes-nightmode #tes-chatlogSave .icon path { fill: var(--nightmode-textSecondarycolor); }
			.tes-nightmode.blacknight #tes-chatlogSave .icon path { fill: #444; }
		.tes-nightmode #tes-chatlogSave:hover .icon path { fill: var(--nightmode-textcolor); }
			.tes-nightmode.blacknight #tes-chatlogSave:hover .icon path { fill: gray; }
		.tes-nightmode .tes-chatlogBut {
			color: var(--nightmode-textSecondarycolor);
			border-color: var(--nightmode-textSecondarycolor);
		}
				.tes-nightmode.blacknight .tes-chatlogBut {
					color: #444;
					border-color: #444;
				}
		.tes-nightmode .tes-chatlogBut:hover {
			color: var(--nightmode-textcolor);
			border-color: var(--nightmode-textcolor);
		}
				.tes-nightmode.blacknight .tes-chatlogBut:hover {
					color: #777;
					border-color: #777;
				}
		.tes-nightmode #tes-chatlogDisplay textarea {
			background: rgba(45, 55, 58, .8);
			color: var(--nightmode-textcolor);
			border: 1px solid #506368;
			caret-color: #41b7ef;
		}
				.tes-nightmode.blacknight #tes-chatlogDisplay textarea {
					background: rgba(0, 0, 0, .8);
					color: gray;
					border: 1px solid #444;
				}
		.tes-nightmode .tes-chatlogBut .label { color: var(--nightmode-textcolor); }
				.tes-nightmode.blacknight .tes-chatlogBut .label { color: gray; }
		.tes-nightmode #chat-content > .message > .nickname[data-status=""],
		.tes-nightmode #chat-instant > .nickname[data-status=""] {
			color: var(--nightmode-textcolor);
		}
				.tes-nightmode.blacknight #chat-content > .message > .nickname[data-status=""],
				.tes-nightmode.blacknight #chat-instant > .nickname[data-status=""] {
					color: gray;
				}

		#chat-wrapper.tes-nightmode,
		.tes-nightmode .on-white-scroll::-webkit-scrollbar-track,
		.tes-nightmode #textarea,
		.tes-nightmode #chat-instant {
			background: var(--nightmode-bgcolor);
			color: var(--nightmode-textcolor);
		}
				#chat-wrapper.tes-nightmode.blacknight,
				.tes-nightmode.blacknight .on-white-scroll::-webkit-scrollbar-track,
				.tes-nightmode.blacknight #textarea,
				.tes-nightmode.blacknight #chat-instant {
					background: var(--nightmodeBlack-bgcolor);
					color: gray;
				}
		.tes-nightmode #input > .waiting { background-color: var(--nightmode-trimcolor); }
			.tes-nightmode.blacknight #input > .waiting { background-color: #222; }

		.tes-nightmode .on-white-scroll::-webkit-scrollbar-thumb {
			border-color: var(--nightmode-bgcolor);
		}
				.tes-nightmode.blacknight .on-white-scroll::-webkit-scrollbar-thumb {
					border-color: var(--nightmodeBlack-bgcolor);
				}

		#chat-wrapper.tes-nightmode { border-color: var(--nightmode-trimcolor); }
				#chat-wrapper.tes-nightmode.blacknight { border-color: #222; }
		.tes-nightmode #timestamp { color: var(--nightmode-textSecondarycolor); }
				.tes-nightmode.blacknight #timestamp { color: #545454; }
		#chat-wider.tes-nightmode { background-color: var(--nightmode-trimcolor); }
				#chat-wider.tes-nightmode.blacknight { background-color: #141414; }
		#chat-wider.tes-nightmode:before { border-color: transparent #636e6e; }
				#chat-wider.tes-nightmode.blacknight:before { border-color: transparent #444; }
		.tes-nightmode #input:after { border-color: var(--nightmode-trimcolor); }
				.tes-nightmode.blacknight #input:after { border-color: #222; }
		.tes-nightmode #chat-content > .message.system { background-color: #313c3f; }
				.tes-nightmode.blacknight #chat-content > .message.system { background-color: #090909; }
		.tes-nightmode.blacknight .on-white-scroll::-webkit-scrollbar-thumb { background-color: #111; }
		.tes-nightmode #timestamp:hover, .timestamp:hover { background-color: var(--nightmode-bgcolor); }
			.tes-nightmode.blacknight #timestamp:hover, .timestamp:hover { background-color: var(--nightmodeBlack-bgcolor); }

		#chat-instant > .nickname[data-status="gold"], #chat-wrapper.full-screen #chat-instant > .nickname[data-status="gold"], #chat-content > .message > .nickname[data-status="gold"], #chat-wrapper.full-screen #chat-content > .message > .nickname[data-status="gold"] {
			color: unset !important;
		}

		#chat-instant > .nickname[data-status="extreme"], #chat-wrapper.full-screen #chat-instant > .nickname[data-status="extreme"], #chat-content > .message > .nickname[data-status="extreme"], #chat-wrapper.full-screen #chat-content > .message > .nickname[data-status="extreme"] {
			color: unset !important;
		}

		#chat-instant > .nickname[data-status="pro"], #chat-wrapper.full-screen #chat-instant > .nickname[data-status="pro"], #chat-content > .message > .nickname[data-status="pro"], #chat-wrapper.full-screen #chat-content > .message > .nickname[data-status="pro"] {
			color: unset !important;
		}

		#chat-content > .message.sub-pro > .avatar, #chat-content > .message.sub-extreme > .avatar, #chat-content > .message.sub-gold > .avatar {
			animation-duration: 0s !important;-webkit-animation-duration: 0s !important;
		}
	</style>
	`;
                    chatlogCSS.insertAdjacentHTML(insertPosition, chatlogCSShtml);
                }

                { // sidemenuCSS
                    var firefoxCSS = "";
                    if (browserSpoofedChrome) {
                        firefoxCSS = `
			#sidemenu {
				left: 0!important;
			}
		`;
                    }
                    sidemenuCSShtml = `
	<style id="sidemenuCSS" scope="tinychat-sidemenu">` + globalCSS + `
		#sidemenu {
			min-width: 200px;
			max-width: 10%;
			left: auto;
			transition: 1s;
        }
		* {
		  scrollbar-color: #5A6366 transparent;
		  scrollbar-width: thin;
		}
		@media screen and (max-width: 1000px) {
			#sidemenu {
				left: -188px;
			}
		}
		#sidemenu-content {
			padding-left: 2px;
		}
		#live-directory-wrapper {
			padding: 0;
		}
		#top-buttons-wrapper {
			padding: 0;
		}
		#user-info { transition: 1s; }
		.logged-in #user-info {
			padding: 0;
			height: auto;
			text-align: center;
		}
		#user-info > div { overflow: unset; }
		#user-info > div:before {
		    position: relative;
			top: 0;
		}
		#user-info button { opacity: .8; }
		#user-info:hover button { opacity: 1; }
		#user-info > a { display: none; }
		#user-info:hover > a { display: initial; }
		/* Smaller footer */
		#user-info > button {
			height: 26px;
			line-height: 25px;
			font-size: 15px;
		}
		#user-info {
			padding: 6px 26px;
			height: 40px;
		}
		@media screen and (min-width: 1000px) {
			#live-directory, #upgrade {
				height: 23px;
				line-height: 22px;
				font-size: 13px;
				opacity: .8;
			}
			#live-directory:before {
				height: 8px;
				width: 8px;
				top: 0px;
			}
			#upgrade {
				margin-top: 4px;
			}
			#live-directory:hover, #upgrade:hover {
				opacity: 1;
			}
		}
		#sidemenu.tes-sidemenuCollapsed {
			min-width: 10px;
			max-width: 10px;
		}
		.tes-sidemenuCollapsed #user-info { display: none; }
		#tes-sidemenu-grabber {
			position: absolute;
			top: 50%;
			right: 0;
			background: var(--nightmode-trimcolor);
			color: #536165;
			z-index: 3;
			border-radius: 10px 0 0 10px;
			height: 37px;
			padding-top: 24px;
			width: 21px;
			text-align: center;
			font-size: 11px;
			transition: .4s;

			/*
			background: white;
			border: #dddddd 1px solid;
			border-right: 0;
			*/
		}
		#tes-sidemenu-grabber:hover {
			background: #506368;
			color: #788c91;
			cursor: pointer;
		}
		.tes-sidemenuCollapsed #tes-sidemenu-grabber {
			border-radius: 0 10px 10px 0;
			right: -7px;
			text-align: right;
			padding-right: 3px;
		}
		.tes-sidemenuCollapsed #tes-sidemenu-grabber:hover {
			right: -18px;
			padding-right: 9px;
			width: 18px;
		}
		#tes-sidemenufakeborder {
			display: none;
			position: absolute;
			right: 0;
			width: 1px;
			height: 100px;
		}
		` + firefoxCSS +
                        `
		.tes-nightmode #tes-sidemenufakeborder {
			display: unset;
			background: var(--nightmode-trimcolor);
		}
			.tes-nightmode.blacknight #tes-sidemenufakeborder { background: var(--nightmodeBlack-trimcolor); }
		.tes-nightmode #sidemenu-content { border-right: var(--nightmode-trimcolor) 1px solid; }
			.tes-nightmode.blacknight #sidemenu-content { border-right: var(--nightmodeBlack-trimcolor) 1px solid; }

		#sidemenu.tes-nightmode.blacknight,
		.tes-nightmode.blacknight #sidemenu-content::-webkit-scrollbar-track {
			background: var(--nightmodeBlack-bgcolor);
		}
		#sidemenu.tes-nightmode.blacknight,
		.tes-nightmode.blacknight #sidemenu-content::-webkit-scrollbar-track {
			background: var(--nightmodeBlack-bgcolor);
		}
		.tes-nightmode.blacknight #tes-sidemenu-grabber {
			background: #141414;
			color: #3b3b3b;
		}
		.tes-nightmode.blacknight #tes-sidemenu-grabber:hover {
			background: #333;
			color: #5c5c5c;
		}
		.tes-nightmode.blacknight #user-info { background: var(--nightmodeBlack-bgcolor); }
		.tes-nightmode.blacknight #user-info > button {
			background: #035268;
			color: #aaa;
		}
		.tes-nightmode.blacknight #user-info > button:hover {
			background: #0080a3;
			color: white;
		}
		.tes-nightmode.blacknight #sidemenu-content::-webkit-scrollbar-thumb {
			border: 5px solid var(--nightmodeBlack-bgcolor);
			background-color: #111;
		}
	</style>
	`;
                    sidemenuCSS.insertAdjacentHTML(insertPosition, sidemenuCSShtml);
                }

                { // videomoderationCSS
                    videomoderationCSShtml = `
	<style id="videomoderationCSS" scope="tc-video-moderation">` + globalCSS + `
		#moderatorlist {
			padding-left: 0;
			z-index: 7;
		}
		#moderatorlist:hover {
		    position: absolute;
			background: white;
			z-index: 1000;
			width: 300px;
			min-height: 155px;
			flex-direction: column;
			position: absolute;
			background: rgba(45, 55, 58, 0.8);
			z-index: 1000;
			width: 350px;
			max-height: fit-content!important;
			left: 15px;
			border-radius: 13px;
			border: #47575c 1px solid;
			top: 105px;
		}
		#moderatorlist:after {
			top: 47px;
		}
		#moderatorlist:hover #header {
			height: unset;
			top: unset;
		}

		#moderatorlist.tes-nightmode.blacknight > #header > span > button { background: var(--nightmodeBlack-bgcolor); }
		#moderatorlist.tes-nightmode.blacknight:hover {
			background: var(--nightmodeBlack-bgcolor);
			border-color: #333;
		}
	</style>
	`;
                    videomoderationCSS.insertAdjacentHTML(insertPosition, videomoderationCSShtml);
                }

                { // webappCSS
                    webappCSShtml = `
	<style id="webappCSS" scope="tinychat-webrtc-app">` + globalCSS + `
		#room {
			padding: 0;
			padding-left: 200px;
		}
		#room.tes-sidemenuCollapsed { padding-left: 0; }
		@media screen and (max-width: 1000px) {
			:host > #room {
				padding-left: 82px;
			}
		}
		@media screen and (max-width: 600px) {
			:host > #room {
				padding-left: 0;
			}
		}
		.tes-nightmode tc-videolist { background: var(--nightmode-bgcolor); }
		.tes-nightmode.blacknight tc-videolist { background: var(--nightmodeBlack-bgcolor); }

        /* Override the style that places chat in bottom, no matter how wide the screen. */
	    #room-content {
	        flex-direction: row !important;
	    }
        tc-modal {
            display: none;
        }
	</style>
	`;
                    webappCSS.insertAdjacentHTML(insertPosition, webappCSShtml);
                }
            } catch (e) {
                tcl("error injectCSS: " + e.message);
            }
        }

        function injectElements() {
            try {
                headerGrabberParElem = titleElem.querySelector("#room-header");
                headerGrabberParElem.insertAdjacentHTML("beforeend", `<div id="tes-header-grabber">▲</div>`);
                headerGrabberElem = headerGrabberParElem.querySelector("#tes-header-grabber");
                headerGrabberElem.addEventListener("click", headerGrabber);

                sidemenuOverlayElem = bodyElem.querySelector("#menu-icon");
                sidemenuOverlayElem.addEventListener("click", function () {
                    sidemenuOverlayElem.classList.toggle("expanded");
                });

                chatlogButtonsHTML = `
		<div id="tes-chatlogButtons">
			<div id="tes-chatlogSave" class="tes-chatlogBut">
				<span class="icon">
					<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
						<path d="m0,50l50,-50l50,50l-25,0l0,50l-50,0l0,-50l-25,0z"></path>
					</svg>
				</span><!-- ⇩ -->
				<span class="label">download</span>
			</div>
			<div id="tes-chatlogView" class="tes-chatlogBut">
				<span class="icon">☰</span>
				<span class="label">view</span>
			</div>
			<div id="tes-chatlogDisplay">
				<textarea spellcheck="false"></textarea>
				<div id="close">✕</div>
			</div>
            <div id="tes-chatToggle" class="tes-chatlogBut">
				<span class="icon">♛</span>
				<span class="label">toggle chat</span>
			</div>
		</div>`;

                selectAllButton = chatlogElem.querySelector("#chat-wrapper").insertAdjacentHTML("afterbegin", chatlogButtonsHTML);
                chatlogElem.querySelector("#tes-chatlogSave").addEventListener("click", function () {
                    copyChatlog("download")
                });
                chatlogElem.querySelector("#tes-chatlogView").addEventListener("click", function () {
                    copyChatlog("view")
                });
                chatlogElem.querySelector("#tes-chatlogDisplay #close").addEventListener("click", function () {
                    copyChatlog("close")
                });

                chatlogElem.querySelector("#tes-chatToggle").addEventListener("click", function () {
                    toggleDefaultChat();
                });

                if (!isPaidAccount) {
                    sidemenuGrabberParElem = sidemenuElem.querySelector("#sidemenu");
                    sidemenuGrabberElem = document.createElement("div");
                    sidemenuGrabberElem.setAttribute("id", "tes-sidemenu-grabber");
                    sidemenuGrabberElem.innerHTML = "◀";
                    sidemenuGrabberElem.addEventListener("click", sidemenuGrabber);
                    sidemenuGrabberParElem.appendChild(sidemenuGrabberElem);
                    sidemenuGrabberElem = sidemenuElem.querySelector("#tes-sidemenu-grabber");
                }
            } catch (e) {
                tcl("error injectElements: " + e.message);
            }
        }

        function sidemenuGrabber() {
            try {
                sidemenuGrabberParElem.classList.toggle("tes-sidemenuCollapsed");
                sidemenuGrabberParElem.classList.contains("tes-sidemenuCollapsed") ? sidemenuGrabberElem.innerHTML = "▶" : sidemenuGrabberElem.innerHTML = "◀";

                userlistElem.querySelector("#userlist").classList.toggle("tes-sidemenuCollapsed");
                videolistElem.querySelector("#videolist").classList.toggle("tes-sidemenuCollapsed");
                webappElem.querySelector("#room").classList.toggle("tes-sidemenuCollapsed");
                bodyElem.classList.toggle("tes-sidemenuCollapsed");
            } catch (e) {
                tcl("error sidemenuGrabber: " + e.message);
            }
        }

        function headerGrabber() {
            try {
                headerGrabberParElem.classList.toggle("tes-headerCollapsed");
                headerGrabberParElem.classList.contains("tes-headerCollapsed") ? headerGrabberElem.innerHTML = "▼" : headerGrabberElem.innerHTML = "▲";
            } catch (e) {
                tcl("error headerGrabber: " + e.message);
            }
        }

        var updateScrolling = false; // Avoid concurrency.
        function updateScroll() {
            try {
                if (updateScrolling) return;

                var scrollingDistance = scrollbox.scrollHeight - scrollbox.clientHeight;
                //console.log('userScrolledChat && scrollingDistance - scrollbox.scrollTop >= 50', userScrolledChat, scrollingDistance - scrollbox.scrollTop)

                // Disabled if element is scrolled.
                if (scrollingDistance - scrollbox.scrollTop >= 50) {
                    return;
                }

                updateScrolling = true;

                unreadbubble.click();

                setTimeout(function() {
                  updateScrolling = false;
                }, 500)
            } catch (e) {
                tcl("error updateScroll: " + e.message);
            }
        }
/*
        function userHasScrolled(e) {
            try {
                var scrollwheelAmount = e.deltaY;

                if (scrollwheelAmount < 0) {
                    autoScrollStatus = false;
                    totalScrolledUp += scrollwheelAmount * -1;
                } else {
                    totalScrolledUp -= scrollwheelAmount;
                }

                if (autoScrollStatus === false && scrollbox.scrollHeight - scrollbox.scrollTop - scrollbox.offsetHeight <= 10) {
                    autoScrollStatus = true;
                    totalScrolledUp = 0;
                }
            } catch (e) {
                tcl("error userHasScrolled: " + e.message);
            }
        }
*/
        function newMessageAdded(nodes) {
            try {
                // Ignore empty events.
                if (!nodes) return;

                //console.log(nodes)
                if (nodes.length > 1) console.log(nodes);

                // Assist TC with their existing autoscroll.
                setTimeout(function() { updateScroll(); }, 500);

                var n;
                for (var i = 0; i < nodes.length; i++) {
                    n = nodes[i];

                    timestampAdd(n);
                    messageParser(n);
                }

            } catch (e) {
                tcl("error newMessageAdded: " + e.message);
            }
        }

        function userContextmenuUpdated() {
            try {
                var elemBottom = 0;
                var topPos = userContextmenuCSS.getBoundingClientRect().top;
                var elemBottom = topPos + userContextmenuCSS.offsetHeight;
                if (elemBottom > (window.innerHeight - 82)) {
                    // userContextmenuCSS.style.top = (userContextmenuCSS.style.top - userlistElem.querySelector("#userlist").scrollTop - 200) + "px";
                    // userContextmenuCSS.style.top = (userlistElem.querySelector("#userlist").scrollTop - window.innerHeight) + "px";
                    userContextmenuCSS.style.top = (window.innerHeight - 82 - userContextmenuCSS.offsetHeight - 15) + "px";
                    // tcl("Change: " + userContextmenuCSS.style.top);
                }

                // tcl("elemBottom: " + elemBottom + ". Max: " + (window.innerHeight - 82) + ". offsetHeight: " + userContextmenuCSS.offsetHeight + ". New top: " + (window.innerHeight - 82 - userContextmenuCSS.offsetHeight));
            } catch (e) {
                tcl("error userContextmenuUpdated: " + e.message);
            }
        }

        function messageParserCheckCSS() {
            try {
                var messages = chatlogElem.querySelectorAll(messageQueryString);
                //var messagesAmount = messages.length;
                //chatboxHeight = chatlogElem.querySelector("#chat").offsetHeight;
                //var messagesToCheck = messageHeight ? parseInt(chatboxHeight / messageHeight) + 3 : 20;

                //for (var i = messagesAmount - 1; i > ((messagesAmount - messagesToCheck) - 1); i--) {
                // Check all messages.
                for (let i=0; i < messages.length; i++) {
                    let msg = messages[i].querySelector("tc-message-html");
                    let tcMessageHtmlElem = msg.shadowRoot;

                    if (!tcMessageHtmlElem.querySelector("#messageCSS")) {
                        tcMessageHtmlElem.appendChild(messageParserAddCSS());
                    }

                    if (settingsQuick["NightMode"] && !tcMessageHtmlElem.querySelector("#messageCSSNightmode")) {
                        tcMessageHtmlElem.appendChild(messageParserAddCSSNightmode()); // tcMessageHtmlElem.querySelector("#html").classList.add("tes-nightmode");
                    }

                    if (settingsQuick["NightModeBlack"] && !tcMessageHtmlElem.querySelector("#messageCSSBlacknight")) {
                        tcMessageHtmlElem.appendChild(messageParserAddCSSBlacknight()); // tcMessageHtmlElem.querySelector("#html").classList.add("blacknight");
                    }
                    //if (i == 0) break;
                }
            } catch(e) {
                tcl("error messageParserCheckCSS: " + e.message);
                //console.log(e);
            }
        }

        function messageParserAddCSS(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(messageCSS);
                node.appendChild(textnode);
                node.setAttribute("id", "messageCSS");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error messageParserAddCSS: " + e.message);
            }
        }

        function messageParserAddCSSNightmode(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(messageCSSNightmode);
                node.appendChild(textnode);
                node.setAttribute("id", "messageCSSNightmode");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error messageParserAddCSSNightmode: " + e.message);
            }
        }

        function messageParserAddCSSBlacknight(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(messageCSSBlacknight);
                node.appendChild(textnode);
                node.setAttribute("id", "messageCSSBlacknight");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error messageParserAddCSSBlacknight: " + e.message);
            }
        }

        function messageParserAddCSSMention(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(messageCSSMention);
                node.appendChild(textnode);
                node.setAttribute("id", "messageCSSMention");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error messageParserAddCSSMention: " + e.message);
            }
        }

        function messageParserAddCSSSpam(elem = null) {
            try {
                return; // BUG: BROKEN
                // Make sure global spam <style> exists.
                if (!chatlogElem.querySelector("#chat > style#messagesCSSSpam")) {
                    var node = document.createElement("style");
                    var textnode = document.createTextNode("");
                    node.appendChild(textnode);
                    node.setAttribute("id", "messagesCSSSpam");

                    chatlogElem.querySelector("#chat").appendChild(node);
                }

                if (typeof elem === "boolean") {
                    if (elem) {
                        // Enable <style>.
                        media="max-width: 1px"
                        chatlogElem.querySelector("#chat > style#messagesCSSSpam").setAttribute('media', '')
                    } else {
                        // Disable <style>.
                        chatlogElem.querySelector("#chat > style#messagesCSSSpam").setAttribute('media', 'max-width: 1px')
                    }
                } else if (elem) {
                    // Hide by id.
                    chatlogElem.querySelector("#chat > style#messagesCSSSpam").textContent += '\n#' + elem.getAttribute('id') + messageCSSSpam
                }
            } catch (e) {
                tcl("error messageParserAddCSSSpam: " + e.message);
            }
        }

        var modHandler = {};
        modHandler.add = function(b) {
            if (!tinychat.defaultChatroom.isOperator()) return;
            if (!b) {
                updBaLi();
                setTimeout(() => {
                    modHandler.add(BaLi())
                }, 2000); return;
            }
            for (let i=0; i < b.length; i++) {
                let user = b[i];
                if (user.username === modder) {
                    unB(user); break;
                }
            }
        }

        function camParserAddCSSNightmode(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(camItemCSShtmlNightmode);
                node.appendChild(textnode);
                node.setAttribute("id", "camItemCSShtmlNightmode");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error camParserAddCSSNightmode: " + e.message);
            }
        }

        function camParserAddCSSBlacknight(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(camItemCSShtmlBlacknight);
                node.appendChild(textnode);
                node.setAttribute("id", "camItemCSShtmlBlacknight");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error camParserAddCSSBlacknight: " + e.message);
            }
        }

        function camParserAddCSSBorderless(elem = null) {
            try {
                var node = document.createElement("style");
                var textnode = document.createTextNode(camItemCSShtmlBorderless);
                node.appendChild(textnode);
                node.setAttribute("id", "camItemCSShtmlBorderless");

                if (elem) {
                    elem.appendChild(node);
                } else {
                    return node;
                }
            } catch (e) {
                tcl("error camParserAddCSSBorderless: " + e.message);
            }
        }

        var lastMessages = [];

        function messageParser(node) {
            try {
                //var latestMessageElem = chatlogElem.querySelector(messageQueryString + ":last-of-type");
                var latestMessageElem = node;

                var typeSystem = false;

                if (latestMessageElem != null) {
                    if (!messageHeight) {
                        messageHeight = latestMessageElem.scrollHeight;
                        chatboxHeight = chatlogElem.querySelector("#chat").offsetHeight;
                    }

                    if (latestMessageElem.classList.contains("system")) {
                        typeSystem = true;
                    }

                    latestMessageElem.setAttribute("id", "msg-" + messageCount);
                    messageCount++;

                    var tcMessageHtmlElem = latestMessageElem.querySelector("tc-message-html").shadowRoot;
                    var latestMessageContentElem = tcMessageHtmlElem.querySelector("#html");

                    var latestMessageNickElem = latestMessageElem.querySelector(".nickname");
                    var latestMessageNick;

                    if (!typeSystem) {
                        latestMessageNick = latestMessageNickElem.innerText;
                    } else {
                        latestMessageNick = "&system";
                    }

                    if (!tcMessageHtmlElem.querySelector("#messageCSS")) {
                        messageParserAddCSS(tcMessageHtmlElem);
                    }

                    //if (settingsQuick["NightMode"]) latestMessageContentElem.classList.add("tes-nightmode");
                    //if (settingsQuick["NightModeBlack"]) latestMessageContentElem.classList.add("blacknight");

                    // Verify all messages have the correct classes every time, or it removes them.
                    //var msgs = chatlogElem.querySelectorAll(messageQueryString);
                    //for (var i=0; i < msgs.length; i++) {
                    //    var m = msgs[i];
                    //    if (settingsQuick["NightMode"] && !latestMessageContentElem.classList.contains("tes-nightmode")) latestMessageContentElem.classList.add("tes-nightmode");
                    //    if (settingsQuick["NightModeBlack"] && !latestMessageContentElem.classList.contains("blacknight")) latestMessageContentElem.classList.add("blacknight");
                    //}

                    if (settingsQuick["NightMode"]) tcMessageHtmlElem.appendChild(messageParserAddCSSNightmode()); // tcMessageHtmlElem.querySelector("#html").classList.add("tes-nightmode");
                    if (settingsQuick["NightModeBlack"]) tcMessageHtmlElem.appendChild(messageParserAddCSSBlacknight()); // tcMessageHtmlElem.querySelector("#html").classList.add("blacknight");

                    var latestMessageContent = latestMessageContentElem.innerHTML;
                    var latestMessageContentText = latestMessageContentElem.textContent; // Unmodified msg text.

                    //if (latestMessageContent.includes(" banned ") || latestMessageContent.includes(" kicked ")) {
                    if (typeSystem) {
                        latestMessageElem.classList.add("dontHide");
                        modHandler.add();
                        return;
                    }

                    // Auto-ban weird character spammers.
                    var bannedChars = ['░', '█', '🔴', '⚪'];
                    if (tinychat.defaultChatroom.isOperator()) {
                        try {
                            for (let i=0; i < bannedChars.length; i++) {
                                let char = bannedChars[i];
                                if (latestMessageContentText.indexOf(char) > -1) {
                                    let u = userlist.getByNickname(latestMessageNick);
                                    tinychat.defaultChatroom.Ban(u);
                                    console.log("User auto-banned for banned characters: " + latestMessageNick);
                                }
                            }
                        } catch(e) { console.log('autobanner banned characters:', e);}
                    }

                    var maxNewLines = 6; // Lines allowed.
                    var newLines = latestMessageContentElem.textContent.match(/\n/gm);
                    var consecNewLines = latestMessageContentElem.textContent.match(/\n\n\n/gm);

                    // Auto-ban newline spammer.
                    if (tinychat.defaultChatroom.isOperator()) {
                        try {
                            //if (settingsQuick["BannedMonitor"]) {
                            if ((newLines && newLines.length >= maxNewLines) || consecNewLines) {
                                let u = userlist.getByNickname(latestMessageNick);
                                tinychat.defaultChatroom.Ban(u);
                                console.log("User auto-banned for newlines: " + latestMessageNick);
                            }
                            //}
                        } catch(e) { console.log('autobanner newlines:', e);}
                    }

                    // Remove newlines from message if too many or any consecutive.
                    if ((newLines && newLines.length >= maxNewLines) || consecNewLines) {
                        setTimeout(function () {
                            latestMessageContentElem.textContent = latestMessageContentElem.textContent.replace(/\n/gm, ' ');
                            tcl('Removing newlines in message from ' + latestMessageNick + ': ' + latestMessageContentElem.textContent)
                        }, 500)
                    }

                    // Keep messages reasonably short.
                    var maxLen = 500;
                    if (latestMessageContentElem.textContent.length > maxLen) {
                        setTimeout(function () {
                            latestMessageContentElem.textContent = latestMessageContentElem.textContent.substring(0, maxLen);
                            tcl('Cropping message from ' + latestMessageNick + ': ' + latestMessageContentElem.textContent)
                        }, 1000)
                    }

                    // Remove long-single-word spam. Not links.
                    if (!latestMessageContentElem.textContent.includes('.') &&
                        !latestMessageContentElem.textContent.includes('/') &&
                        latestMessageContentElem.textContent.length > 50 &&
                        !latestMessageContentElem.textContent.includes(' ')) {
                        setTimeout(function () {
                            //latestMessageElem.style.display = "none";

                            // Add to spam list.
                            //messageParserAddCSSSpam(latestMessageElem)

                            //tcl('Marked spam long-single-word message from ' + latestMessageNick + ': ' + latestMessageContentElem.textContent)
                        }, 1000)
                        return;
                    }

                    // Ignore if a repeat of recent message from the same nickname.
                    var duplicate = false;
                    for (var i = 0; i < lastMessages.length; i++) {
                        var msg = lastMessages[i];

                        if (msg && latestMessageNick + latestMessageContentText === msg) {
                            duplicate = true;
                            break;
                        }
                    }

                    var dupMinLen = 0; // Needed for shorter common responses?
                    if (duplicate && latestMessageContentText.length > dupMinLen) {
                        setTimeout(function () {
                            //latestMessageElem.remove();
                            // Hide now, if option enabled.
                            //if (settingsQuick["ToggleHiddenMsgs"]) {
                            //    latestMessageElem.style.display = "none";
                            //}
                            // Mark for toggling.
                            //latestMessageElem.className += " hideSpamChat";

                            // Add to spam list.
                            //messageParserAddCSSSpam(latestMessageElem)

                            //tcl('Marked duplicate message from ' + latestMessageNick + ': ' + latestMessageContentElem.textContent)
                        }, 1000)
                        return;
                    }

                    // Remember recent messages and their nickname. Remove oldest, first, if filled.
                    if (lastMessages.length >= 10) {
                        lastMessages.shift();
                    }
                    lastMessages.push(latestMessageNick + latestMessageContentText);
                    //console.log('lastMessages.length', lastMessages.length)

                    if (settingsQuick["MentionsMonitor"]) {
                        if (!(settingMentions.length == 1 && settingMentions[0] == "")) {
                            for (i = 0; i < settingMentions.length; i++) {
                                // Case-insensitive.
                                if (latestMessageContent.toUpperCase().includes(settingMentions[i].toUpperCase())) {
                                    //latestMessageContentElem.classList.add("tes-mention-message");

                                    tcMessageHtmlElem.appendChild(messageParserAddCSSMention())
                                    // Remove after a delay.
                                    setTimeout(function() {
                                        var child = tcMessageHtmlElem.getElementById("messageCSSMention");
                                        if (child) {
                                            tcMessageHtmlElem.removeChild(child);
                                        }
                                    }, 15000)

                                    audioPop.play();

                                    tcl('MENTION: "' + settingMentions[i] + '" : ' + latestMessageContent);
                                    break;
                                }
                            }
                        }
                    }
                }
            } catch (e) {
                tcl("error messageParser: " + e.message);
            }
        }


        var messagesMO = new MutationObserver(function (e) {
            if (e[0].addedNodes) newMessageAdded(e[0].addedNodes);
        });
        messagesMO.observe(chatlogElem.querySelector("#chat-content"), {
            childList: true
        });

        var camsMO = new MutationObserver(function (e) {
            if (e[0].addedNodes) newCamAdded(e[0].addedNodes);
        });
        camsMO.observe(videolistElem.querySelector(".videos-items:last-child"), {
            childList: true
        });

        var userContextmenuMO = new MutationObserver(function (e) {
            if (e[0].addedNodes) userContextmenuUpdated();
        });
        userContextmenuMO.observe(userContextmenuCSS, {
            attributes: true
        });

        var chatTextboxMO = new MutationObserver(function (e) {
            if (e[0].addedNodes) chatboxSwitch();
        });
        chatTextboxMO.observe(chatlogElem.querySelector("#chat-instant"), {
            attributes: true,
            attributeFilter: ['class'],
            childList: false,
            characterData: false
        });

        var userlistMO = new MutationObserver(function (e) {
            if (e[0].addedNodes) newUserAdded();
        });
        userlistMO.observe(userlistElem.querySelector("#userlist"), {
            childList: true
        });

        function chatboxSwitch() {
            messageParserCheckCSS();
            return;

            // if (chatlistElem.querySelector("#chat-instant-button")) chatlistElem.querySelector("#chat-instant-button").classList.add("tes-loading");
            try {
                chatboxPM = (chatlogElem.querySelector("#chat-instant").getAttribute("class") == "show");
                chatboxPM ? chatlogCSS.classList.add("tes-chatboxPM") : chatlogCSS.classList.remove("tes-chatboxPM");
                messageParserCheckCSS();
            } catch (e) {
                tcl("error chatboxSwitch: " + e.message)
            };
        }

        function timestampAdd(node) {
            try {
                var SHOW_SECONDS = true;

                var date = new Date();
                var hours = date.getHours();
                var minutes = date.getMinutes().toString();
                var secs = date.getSeconds().toString();

                var period
                if (hours > 11) {
                    hours = (hours % 12 || 12);
                    period = "pm";
                } else {
                    period = "am";
                }

                if (hours == "0") {
                    hours = "12";
                }
                if (minutes == "0") {
                    minutes = "00";
                }
                if (minutes.length == 1) {
                    minutes = "0" + minutes;
                }
                if (secs.length == 1) {
                    secs = "0" + secs;
                }

                var timestamp
                if (SHOW_SECONDS == true) {
                    timestamp = hours + ":" + minutes + ":" + secs + "" + period;
                } else {
                    timestamp = hours + ":" + minutes + period;
                }

                //console.log(node.querySelector('.nickname'), node.querySelector('#timestamp'))
                //var nickElement = node.querySelector('.nickname');
                //var contentElement = node.querySelector('.content');
                if (node && !node.querySelector('#timestamp')) {
                    //nickElement.innerHTML = '<span id="nickname">' + nickElement.innerHTML + '</span>' // Seperate element from timestamp.
                    //node.insertAdjacentHTML("beforeend", "<span id='timestamp'>" + timestamp + "</span>");
                    let e = document.createElement('span');
                    e.setAttribute("id", "timestamp");
                    e.innerText = timestamp;
                    node.appendChild(e);
                }

                //var queryString = messageQueryString + ".common:last-of-type .nickname";
                //var recentMsgNickname = chatlogElem.querySelector(queryString);
                //if (recentMsgNickname !== null) {
                //    if (!chatlogElem.querySelector(".common:last-of-type #timestamp")) {
                //        recentMsgNickname.insertAdjacentHTML("afterend", "<span id='timestamp'> " + timestamp + "</span>");
                //    }
                //}
            } catch (e) {
                tcl("error timestampAdd: " + e.message);
            }
        }

        function newUserAdded(opt = null) {
            try {
                if (!userlistElem.querySelector("#userlist .list-item")) return;
                var usersElems = userlistElem.querySelectorAll("#userlist .list-item");
                userCount = usersElems.length;

                setTimeout(function () {
                    for (let i = 0; i < usersElems.length; i++) {
                        var userNickItem = usersElems[i].querySelector(".nickname");
                        var userNick = userNickItem.innerHTML;

                        userNickItem.classList.remove("tes-myNick");
                        if (userNick === myNick) {
                            userNickItem.classList.add("tes-myNick");
                            continue;
                        }

                        // Auto-ban matches (or optionally whitelist.)
                        if (tinychat.defaultChatroom.isOperator()) {
                            try {
                                let u = userlist.getByNickname(userNick);
                                if (!u) continue;

                                let userName = u.username;

                                // Can't ban/kick mods.
                                if (u.isOperator) continue;

                                if (settingsQuick["BannedMonitor"]) {
                                    if (settingBannedUsers.hasOwnProperty(userName.toUpperCase()) || settingBannedUsers.hasOwnProperty(userNick.toUpperCase())) {
                                        tinychat.defaultChatroom.Ban(u);
                                        console.log("User auto-banned: " + userNick + " ("+userName+")");
                                        continue;
                                    }
                                }

                                if (settingsQuick["BannedMonitorWhitelist"]) {
                                    if (!settingBannedUsers.hasOwnProperty(userName.toUpperCase())) {
                                        tinychat.defaultChatroom.Kick(u);
                                        console.log("User whitelist auto-kicked: " + userNick + " ("+userName+")");
                                        continue;
                                    }
                                }
                            } catch(e) { console.log('BannedMonitor:', e);}
                        }
                    }
                }, 500);

                if (opt === "scanOnly") {
                    return;
                } else {
                    if (!userlistElem.querySelector("#tes-userCount")) {
                        userCountParElem = userlistElem.querySelector("#header > span");
                        userCountElem = document.createElement("span");
                        userCountElem.setAttribute("id", "tes-userCount");
                        userCountElem.innerHTML = "(" + userCount + ")";
                        userCountParElem.appendChild(userCountElem);
                        userCountElem = userlistElem.querySelector("#tes-userCount");
                    } else {
                        userCountElem.innerHTML = "(" + userCount + ")";
                    }
                }
            } catch (e) {
                tcl("error newUserAdded: " + e.message);
            }
        }

        // Or removed.
        function newCamAdded(newNodes) {
            try {
                if (videolistElem.querySelector(camQueryString)) {
                    var camElems = videolistElem.querySelectorAll(camQueryString);
                } else {
                    return;
                }

                // Refresh can use filters.
                tinychat.defaultChatroom.selfUser().subscriptionType = 30;
                tinychat.defaultChatroom.selfUser().canUseFilters = true;

                camsCount = 0;

                // Iterate over all items, if none provided.
                if (!newNodes) newNodes = camElems;

                for (let i=0; i < newNodes.length; i++) {
                    let node = newNodes[i];
                    //console.log('querySelector', node.querySelector)
                    if (node && node.querySelector) {
                        var camItem = node.querySelector("tc-video-item").shadowRoot;

                        if (camItem) {
                            var camName = camItem.querySelector(".nickname").getAttribute("title");
                            var userName = '';

                            let u = userlist.getByNickname(camName);
                            if (u && u.username) userName = u.username;

                            // Hide the new cam.
                            var hidden = false;
                            if (settingsQuick["HideAllCams"] != "true" && settingsQuick["HideNewCams"] && camName && camName != myNick) {
                                camItem.querySelector("button.icon-visibility").click();
                                tcl("Cam hide: " + camName);
                                hidden = true;
                            }

                            // Hide ignored user.
                            if (settingsQuick["HideAllCams"] != "true" && !hidden && camName && camName != myNick) {
                                if (settingsQuick["IgnoredMonitor"]) {
                                    if (settingIgnoredUsers.hasOwnProperty(userName.toUpperCase()) || settingIgnoredUsers.hasOwnProperty(camName.toUpperCase())) {
                                        camItem.querySelector("button.icon-visibility").click();
                                        tcl("Cam hidden: " + camName);
                                        hidden = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }

                for (let i = 0; i < camElems.length; i++) {
                    camsCount = i + 1;
                    var camItem = camElems[i].querySelector("tc-video-item").shadowRoot;
                    var camItemCSS = camItem.querySelector(".video");

                    if (settingsQuick["NightMode"]) {
                        camItemCSS.classList.add("tes-nightmode");

                        var child = camItem.getElementById("camItemCSShtmlNightmode");
                        if (!child) {
                            camItem.appendChild(camParserAddCSSNightmode());
                        }
                    } else {
                        camItemCSS.classList.remove("tes-nightmode");

                        var child = camItem.getElementById("camItemCSShtmlNightmode");
                        if (child) {
                            camItem.removeChild(child);
                        }
                    }

                    if (settingsQuick["NightModeBlack"]) {
                        camItemCSS.classList.add("blacknight");

                        var child = camItem.getElementById("camItemCSShtmlBlacknight");
                        if (!child) {
                            camItem.appendChild(camParserAddCSSBlacknight());
                        }
                    } else {
                        camItemCSS.classList.remove("blacknight");

                        var child = camItem.getElementById("camItemCSShtmlBlacknight");
                        if (child) {
                            camItem.removeChild(child);
                        }
                    }

                    if (settingsQuick["BorderlessCams"]) {
                        camItemCSS.classList.add("tes-borderlesscams");

                        var child = camItem.getElementById("camItemCSShtmlBorderless");
                        if (!child) {
                            camItem.appendChild(camParserAddCSSBorderless());
                        }
                    } else {
                        camItemCSS.classList.remove("tes-borderlesscams");

                        var child = camItem.getElementById("camItemCSShtmlBorderless");
                        if (child) {
                            camItem.removeChild(child);
                        }
                    }

                    if (!camItem.querySelector("#camItemCSS")) camItemCSS.insertAdjacentHTML("afterbegin", camItemCSShtml);

                    var camName = camItem.querySelector(".nickname").getAttribute("title");
                    camElems[i].setAttribute("id", "camUser-" + camName);

                    try {
                        // Cam maxing
                        var maxbutton = camItem.querySelector(".icon-tes-max");
                        if (maxbutton) {
                            maxbutton.parentNode.removeChild(maxbutton);
                        }

                        camItem.querySelector(".icon-resize").insertAdjacentHTML("beforebegin", camMaxButtonHtml);
                        camItem.querySelector(".icon-tes-max").setAttribute("id", "maxbutton-" + camName);

                        // Add talking indicator.
                        if (!camItem.querySelector(".icon-tes-talking")) {
                            camItem.querySelector(".icon-resize").insertAdjacentHTML("beforebegin", camTalkingIndicatorHtml);
                            camItem.querySelector(".icon-tes-talking").setAttribute("id", "micIndicator-" + camName);
                        }

                        var maxCamVar = function (maxCamVarArg) {
                            videolistElem.querySelector(".videos-items:last-child").classList.remove("tes-max-noAnim");
                            maximizeCam(maxCamVarArg, "buttonpress");
                        };
                        camItem.querySelector("#maxbutton-" + camName).addEventListener("click", maxCamVar.bind(this, camName));

                        if (camMaxedCurrent == camName) {
                            camElems[i].classList.add("tes-maxedCam");
                            camElems[i].parentElement.classList.add("tes-max");
                        }
                        if (!videolistElem.querySelector(".tes-maxedCam")) camElems[i].parentElement.classList.remove("tes-max");

                        if (videolistCSS.querySelector("#camMaxCSS")) {
                            var maxcss = videolistCSS.querySelector("#camMaxCSS");
                            maxcss.parentNode.removeChild(maxcss);
                        }
                        videolistCSS.insertAdjacentHTML("beforeend", camMaxCSShtml);

                    } catch (e) {
                        tcl("error newCamAdded: " + e.message);
                    }

                    if (settingsQuick["HideAllCams"] == "true" || urlPars.get("hideallcams") == "") {
                        camItem.querySelector("button.icon-visibility").click();
                        tcl("Cam hide: " + camName);
                    }

                    camCounter(camElems[i]);
                }
            } catch (e) {
                tcl("error newCamAdded: " + e.message);
            }
        }

        function maximizeCam(camName, opt = null) {
            try {
                if (camName != camMaxedCurrent && camMaxedCurrent != null) {
                    maximizeCam(camMaxedCurrent);
                    maximizeCam(camName);
                    return;
                }

                var camElem = videolistElem.querySelector("#camUser-" + camName);
                if (camElem == null) {
                    camMaxedCurrent = null;
                    return;
                }

                if (opt == "bbuttonpress") {
                    camElem.parentElement.classList.remove("tes-max-noAnim");
                }

                if (camElem.classList.contains("tes-maxedCam")) {
                    camElem.classList.remove("tes-maxedCam");
                    camElem.parentElement.classList.remove("tes-max");
                    camMaxedCurrent = null;
                } else {
                    camElem.classList.add("tes-maxedCam");
                    camElem.parentElement.classList.add("tes-max");
                    camMaxedCurrent = camName;
                    setTimeout(function () {
                        camElem.parentElement.classList.add("tes-max-noAnim");
                    }, 500);
                }
                camCounter(camElem);
            } catch (e) {
                tcl("error maximizeCam: " + e.message);
            }
        }

        function camCounter(camElem) {
            try {
                if (camsCount == 12) {
                    camElem.parentElement.classList.remove("tes-camCount10-11");
                    camElem.parentElement.classList.remove("tes-camCount2");

                    camElem.parentElement.classList.add("tes-camCount12");
                } else if (camsCount > 9 && camsCount < 12) {
                    camElem.parentElement.classList.remove("tes-camCount12");
                    camElem.parentElement.classList.remove("tes-camCount2");

                    camElem.parentElement.classList.add("tes-camCount10-11");
                } else if (camsCount == 2) {
                    camElem.parentElement.classList.remove("tes-camCount12");
                    camElem.parentElement.classList.remove("tes-camCount10-11");

                    camElem.parentElement.classList.add("tes-camCount2");
                } else {
                    camElem.parentElement.classList.remove("tes-camCount12");
                    camElem.parentElement.classList.remove("tes-camCount10-11");
                    camElem.parentElement.classList.remove("tes-camCount2");
                }
            } catch (e) {
                tcl("error camCounter: " + e.message);
            }
        }
    } catch (e) {
        tcl("error runTES: " + e.message);
    }
    /* End main function */
    return {
        newUserAdded: newUserAdded,
        ignoreFromManualList: ignoreFromManualList,
        chatlogElem: chatlogElem,
        userlistElem: userlistElem,
        tinychat: tinychat,
    };
}

function tcl(m) {
    try {
        if (m.includes("error ")) {
            var m = m.split("error ")[1];
            console.log("%cTES " + "%cerror" + "%c" + ": " + m, "font-weight: bold; color: #53b6ef;", "color: red;", "");
        } else {
            console.log("%cTES: " + "%c" + m, "font-weight: bold; color: #53b6ef;", "");
        }
    } catch (e) {
        console.log("------ TES error tcl: " + e.message);
    }
}

function TESwsParser() {
    try {
        wsdata = [];
        chatlogMain = "";
        userlistLog = {};
        usernamesLog = [];
        userlistLogQuits = {};
        newline = `
`;
        WebSocket.prototype._send = WebSocket.prototype.send;
        WebSocket.prototype.send = function (data) {
            try {
                this._send(data);
                this.addEventListener('message', function (msg) {
                    if (msg.data.includes('"tc":"joined"')) {
                        var joined = JSON.parse(msg.data);
                        myNick = joined["self"]["nick"];
                        myHandle = joined["self"]["handle"];
                    }
                    if (msg.data.includes('"tc":"msg"') && msg.data.includes('"handle"')) {
                        var messageArr = JSON.parse(msg.data);
                        var handle = messageArr["handle"];
                        var nick = userlistLog[handle]["nick"];

                        chatlogAdd(nick + ": " + messageArr["text"]);

                        addMessageToNewChatbox(handle, nick, messageArr["text"]);
                    }
                    if (msg.data.includes('"tc":"pvtmsg"') && msg.data.includes('"handle"')) {
                        var messageArr = JSON.parse(msg.data);
                        var handle = messageArr["handle"];
                        var nick = userlistLog[handle]["nick"];

                        chatlogAdd('[' + nick + "]: " + messageArr["text"]);

                        addMessageToNewChatbox(handle, nick, messageArr["text"], true);
                    }
                    if (msg.data.includes('"item"')) {
                        if (msg.data.includes('tc":"yut_play"')) {
                            var youtubeArr = JSON.parse(msg.data);
                            var id = youtubeArr["item"]["id"];
                            chatlogAdd("- YouTube video started: " + "https://youtube.com/watch?v=" + id);
                        }
                        if (msg.data.includes('tc":"yut_stop"')) chatlogAdd("- YouTube video stopped.");
                    }
                    if (msg.data.match(/"tc":"(?:un)?publish"/)) {
                        var publishArr = JSON.parse(msg.data);
                        var action = (publishArr["tc"] == "publish") ? "is" : "stopped";
                        var handle = publishArr["handle"];

                        if (userlistLog[handle]) var nick = userlistLog[handle]["nick"];
                        else var nick = userlistLogQuits[handle]["nick"];

                        chatlogAdd("- " + nick + " " + action + " broadcasting.");
                    }
                    if (msg.data.includes('"tc":"sysmsg"')) {
                        var systext = JSON.parse(msg.data)["text"];
                        chatlogAdd("-- " + systext);
                    }
                    if (msg.data.includes('"tc":"userlist"')) {
                        userlistArr = JSON.parse(msg.data)["users"];
                        for (i = 0; i < userlistArr.length; i++) {
                            var nick = userlistArr[i]["nick"];
                            var handle = userlistArr[i]["handle"];
                            var username = userlistArr[i]["username"];
                            var isMod = userlistArr[i]["mod"];

                            userlistLog[handle] = {
                                "nick": nick,
                                "username": username,
                                "mod": isMod
                            };
                            var isMod = isMod == true ? "[MOD]" : "";
                            var logtext = username == "" ? nick : nick + "(" + username + ")";
                            logtext += isMod;
                            usernamesLog.push(logtext);
                        }

                        userlistInitial = usernamesLog.join(', ');
                        usersCountInitial = usernamesLog.length;
                    }
                    if (msg.data.includes('"tc":"join","username":"')) {
                        var userArr = JSON.parse(msg.data)
                        var nick = userArr["nick"];
                        var handle = userArr["handle"];
                        var username = userArr["username"];
                        var isMod = userArr["mod"];
                        userlistLog[handle] = {
                            "nick": nick,
                            "username": username,
                            "mod": isMod
                        };

                        TESapp.ignoreFromManualList(username, nick);
                    }
                    if (msg.data.includes('"tc":"quit"')) {
                        var userArr = JSON.parse(msg.data);
                        var handle = userArr["handle"];
                        userlistLogQuits[handle] = userlistLog[handle];
                        delete userlistLog[handle];
                    }

                    if (msg.data.includes('"tc":"nick"')) {
                        var userArr = JSON.parse(msg.data);
                        var handle = userArr["handle"];
                        var nick = userArr["nick"];

                        userlistLog[handle]["nick"] = nick;
                        if (handle == myHandle) {
                            myNick = nick;
                        }

                        TESapp.newUserAdded("scanOnly");
                    }
                }, false);
                this.send = function (data) {
                    this._send(data);
                };

            } catch (e) {
                tcl("error WebSocket.prototype.send: " + e.message);
            }
        }

        function chatlogAdd(arg) {
            var timestamp = new Date().toLocaleTimeString('en-US', {
                hour12: false
            });
            chatlogMain += "[" + timestamp + "] " + arg + newline;
        }

        function addMessageToNewChatbox(handle, nick, msg, priv = false) {
            // mark private messages
            var nickClass = 'nickname';
            var avatarClass = 'avatar';
            if (priv) {
                nickClass += ' private';
                avatarClass += ' private';
            }

            // parent div
            var node = document.createElement("div");
            node.className = "message common";

            // avatar div
            divnode = document.createElement("div");
            divnode.className = avatarClass;
            var imgnode = document.createElement("img");
            imgnode.src = TESapp.tinychat.defaultChatroom.userlist.getByNickname(nick).avatarUrl;
            divnode.appendChild(imgnode);
            node.appendChild(divnode);

            // nick div
            divnode = document.createElement("div");
            divnode.className = nickClass;
            divnode.setAttribute("data-status", TESapp.userlistElem.children.userlist.querySelectorAll('[data-user-id = "'+handle+'"]')[0].dataset.status);
            textnode = document.createTextNode(nick);
            divnode.appendChild(textnode);
            node.appendChild(divnode);

            // text div
            var divnode = document.createElement("div");
            divnode.className = "content";
            // guaranteed to be safe text
            var textnode = document.createTextNode(msg);
            divnode.appendChild(textnode);
            // change URLs to links
            divnode.innerHTML = linkify(divnode.innerHTML);
            node.appendChild(divnode);

            // timestamp
            var timestamp = new Date().toLocaleTimeString('en-US', {hour12: false});
            divnode = document.createElement("div");
            divnode.className = "timestamp";
            textnode = document.createTextNode(timestamp);
            divnode.appendChild(textnode);
            node.appendChild(divnode);

            var chatContentNew = TESapp.chatlogElem.querySelector('#chat-content-new')
            var chatParent = TESapp.chatlogElem.querySelector('#chat')
            chatContentNew.appendChild(node);

            // scroll down if not manually scrolled
            //console.log('chatParent.scrollHeight - chatParent.scrollTop = ', chatParent.scrollHeight - chatParent.scrollTop)
            if (chatParent.scrollHeight - chatParent.scrollTop < 1000) {
                chatParent.scrollTop = chatParent.scrollHeight;
            }
        }

        function linkify(inputText) {
            var replacedText, replacePattern1, replacePattern2, replacePattern3;

            //URLs starting with http://, https://, or ftp://
            replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
            replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

            //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
            replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
            replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

            //Change email addresses to mailto:: links.
            replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
            replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

            return replacedText;
        }

    } catch (e) {
        tcl("error TESwsParser: " + e.message);
    }
}