Themes - Bonk.io

Recolors elements in Bonk.io to customizable colors, and allows toggling your theme with a hotkey

目前为 2021-11-23 提交的版本。查看 最新版本

// ==UserScript==
// @name         Themes - Bonk.io
// @description  Recolors elements in Bonk.io to customizable colors, and allows toggling your theme with a hotkey
// @author       Excigma
// @namespace    https://greasyfork.org/users/416480
// @license      GPL-3.0
// @version      0.1.7
// @match        https://bonk.io/*
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(() => {
	const configuration_metadata_map = {
		hotkey: {
			default_dark: "d",
			default_light: "d",
			description: "Hotkey used to toggle the theme on and off\n(Ctrl + Alt + <key>)",
			datatype: "char"
		},
		colored_text: {
			default_dark: "#40c99e",
			default_light: "#032a71",
			description: "Text color of custom games with friends online in the room, and 'Added by' in picks",
			datatype: "color"
		},
		primary_text: {
			default_dark: "#f8fafd",
			default_light: "#000000",
			description: "Primary text color",
			datatype: "color"
		},
		secondary_text: {
			default_dark: "#bebebe",
			default_light: "#505050",
			description: "Secondary text color\nNote: Some of the texts are lightened by 'brightness_lighter' above\nthis will have no affect on such texts (Eg: Chat usernames)",
			datatype: "color"
		},
		window_color: {
			default_dark: "#2b6351",
			default_light: "#009688",
			description: "Color of window titles\n(Eg: Custom games, Leave Game, Chat, Level Select, ...)",
			datatype: "color"
		},
		window_text: {
			default_dark: "#f8fafd",
			default_light: "#ffffff",
			description: "Color of text in window titles\n(Eg: Custom games, Leave Game, Chat, Level Select, ...)",
			datatype: "color"
		},
		page_background: {
			default_dark: "#111111",
			default_light: "#1a2733",
			description: "Used for the main page's background",
			datatype: "color"
		},
		primary_background: {
			default_dark: "#222222",
			default_light: "#e2e2e2",
			description: "Used as the main background color of windows\n(Eg: Chat background, Leave lobby background, Login page)",
			datatype: "color"
		},
		secondary_background: {
			default_dark: "#333333",
			default_light: "#f3f3f3",
			description: "Secondary background color for things inside windows\n(Eg: Auto login, Joining room status text, Map Editor inputs, ...)",
			datatype: "color"
		},
		behind_lobby_background: {
			default_dark: "#131313",
			default_light: "#1a2733",
			description: "Color for the Lobby's background\n(Eg: Behind the Lobby and Map Editor)",
			datatype: "color"
		},
		red_text: {
			default_dark: "#e14747",
			default_light: "#cc3333",
			description: "Used for player nerf indicator in the lobby",
			datatype: "color"
		},
		blue_text: {
			default_dark: "#179be8",
			default_light: "#0955c7",
			description: "Used for [Load] when a map is suggested, and Copy link is clicked",
			datatype: "color"
		},
		green_text: {
			default_dark: "#17e88b",
			default_light: "#155824",
			description: "Used for player buff and in chat friend requests",
			datatype: "color"
		},
		purple_text: {
			default_dark: "#8f68e8",
			default_light: "#6033cc",
			description: "Used for output of /curate",
			datatype: "color"
		},
		magenta_text: {
			default_dark: "#d23cfb",
			default_light: "#800d6e",
			description: "Used for when [Load] is clicked, or when you are now the host of the game",
			datatype: "color"
		},
		button_color: {
			default_dark: "#4f382f",
			default_light: "#795548",
			description: "Button/dropdown color",
			datatype: "color"
		},
		hover_button_color: {
			default_dark: "#3a2a24",
			default_light: "#7f5d51",
			description: "Button/dropdown hover color",
			datatype: "color"
		},
		active_button_color: {
			default_dark: "#362620",
			default_light: "#4b252b",
			description: "Button/dropdown click color",
			datatype: "color"
		},
		disabled_button_color: {
			default_dark: "#444444",
			default_light: "#777777",
			description: "Button/dropdown disabled color",
			datatype: "color"
		},
		button_text: {
			default_dark: "#f8fafd",
			default_light: "#ffffff",
			description: "Button/dropdown text color",
			datatype: "color"
		},
		xp_bar_fill: {
			default_dark: "#473aaf",
			default_light: "#473aaf",
			description: "XP bar fill color at the top of your screen whilst in-game",
			datatype: "color"
		},
		top_bar_opacity: {
			default_dark: "0.8",
			default_light: "0.8",
			description: "Opacity of the 'top bar' that contains your:\nSkin, Username, Level, Volume Settings, ...",
			datatype: "percentage"
		},
		top_bar_color: {
			default_dark: "#333333",
			default_light: "#273749",
			description: "Color of the 'top bar'\nMake sure this value is dark, the icon colors are white and can't really be easily changed",
			datatype: "color"
		},
		top_bar_hover_color: {
			default_dark: "#222222",
			default_light: "#2d435a",
			description: "Color of buttons in the 'top bar' then hovered",
			datatype: "color"
		},
		top_bar_text: {
			default_dark: "#bebebe",
			default_light: "#bebebe",
			description: "Color of text in the 'top bar'",
			datatype: "color"
		},
		football_background: {
			default_dark: "#161616",
			default_light: "#5a7f64",
			description: "Color for the Football gamemode's background\n(Note: Requires restarting the football to apply)\nWarning: Unstable, experimental",
			datatype: "color"
		},
		border_color: {
			default_dark: "#333333",
			default_light: "#a5acb0",
			description: "Color used for subtile borders\n(Eg. Settings border, line separating chat and messages in lobby)",
			datatype: "color"
		},
		mini_menu_color: {
			default_dark: "#191919",
			default_light: "#1e2833",
			description: "Used for Tooltips and 'Filter by' in Custom Games",
			datatype: "color"
		},
		mini_menu_text: {
			default_dark: "#bebebe",
			default_light: "#ffffff",
			description: "Used for Tooltips and 'Filter by' in Custom Games",
			datatype: "color"
		},
		scrollbar_background: {
			default_dark: "#191919",
			default_light: "#dddddd",
			description: "Used for the scrollbar's background (Chromium only)",
			datatype: "color"
		},
		scrollbar_thumb: {
			default_dark: "#555555",
			default_light: "#aaaaaa",
			description: "Used for the scrollbar's thumb - which is the thing you drag (Chromium only)",
			datatype: "color"
		},
		list_headers: {
			default_dark: "#2b3839",
			default_light: "#a8bcc0",
			description: "Used for the colors of lists\n(Eg: Friends list and the sections in the Map Editor, ...)",
			datatype: "color"
		},
		table_color: {
			default_dark: "#444444",
			default_light: "#c1cdd2",
			description: "Stripe color for use in tables\n(Eg: Custom games, Friends list, Map Editor, Skin Editor, ...)",
			datatype: "color"
		},
		table_alt_color: {
			default_dark: "#333333",
			default_light: "#d2dbde",
			description: "Alternative stripe color used in tables",
			datatype: "color"
		},
		table_hover_color: {
			default_dark: "#222222",
			default_light: "#aac5d7",
			description: "Color used when a row in a table is hovered",
			datatype: "color"
		},
		table_active_color: {
			default_dark: "#111111",
			default_light: "#9cc8d6",
			description: "Color used when a row in a table is selected",
			datatype: "color"
		},
		brightness_lighter: {
			default_dark: "2.5",
			default_light: "1",
			description: "Used to lighten text that have bad contrast with dark colors\nNote: Change to 1 if making a light theme\n(0.5 = 50%, 1.5 = 150% brightness)",
			datatype: "brightness"
		},
		brightness_darker: {
			default_dark: "0.5",
			default_light: "1",
			description: "Used to darken images that that are too bright\nNote: Change to 1 if making a light theme\n(0.5 = 50%, 1.5 = 150% brightness)",
			datatype: "brightness"
		},
		description_invert: {
			default_dark: "1",
			default_light: "0",
			description: "How much to invert description at the bottom of the page by\n(Value from 0 to 1, Don't use 0.5)",
			datatype: "percentage"
		},
	};

	const datatype_metadata_map = {
		"color": {
			value: "value",
			attributes: {
				type: "color"
			}
		},
		"percentage": {
			value: "value",
			attributes: {
				type: "number",
				max: 1,
				min: 0,
				step: 0.01
			}
		},
		"brightness": {
			value: "value",
			attributes: {
				type: "number",
				max: 10,
				min: 0,
				step: 0.05
			}
		},
		"char": {
			value: "value",
			attributes: { 
				type: "text" ,
				maxLength: 1 
			}
		},
	};

	const default_configuration = {};
	for (const [key, value] of Object.entries(configuration_metadata_map)) 
		default_configuration[key] = value.default_dark;
		
	// If running inside maingameframe, inject our injectors
	if (unsafeWindow.parent !== unsafeWindow) {
		unsafeWindow.bonk_theme = {
			on: true,
			get_football_background: () => parseInt(getComputedStyle(document.documentElement)
				.getPropertyValue("--bonk_theme_football_background").trim().slice(1), 16),
			get_hotkey: () => getComputedStyle(document.documentElement)
				.getPropertyValue("--bonk_theme_hotkey").trim() || default_configuration.hotkey
		};

		// Injector for football mode, if it doesn't work, ...well football will be bright
		if (!unsafeWindow.bonkCodeInjectors) unsafeWindow.bonkCodeInjectors = [];
		unsafeWindow.bonkCodeInjectors.push(bonkCode => {
			try {
				// Default football background color, not used elsewhere (yet?)
				const FOOTBALL_BACKGROUND_COLOR = "0x5a7f64";
				let newBonkCode = bonkCode;
				newBonkCode = newBonkCode
					.replace(
						FOOTBALL_BACKGROUND_COLOR,
						`(window.bonk_theme.on ? window.bonk_theme.get_football_background() : ${FOOTBALL_BACKGROUND_COLOR})`
					);
				if (bonkCode === newBonkCode) throw "[Themes] Injection failed!";

				console.log("Themes injector run");
				return newBonkCode;
			} catch (er) {
				// Silently ignore errors, only football's background will be affected
				console.log("[Themes] Failed to inject");
				console.error(er);
				return bonkCode;
			}
		});
	}

	document.addEventListener("DOMContentLoaded", () => {
		document.body.classList.add("themed-colors");
		// Get the currently applied theme
		const get_stored_theme = () => {
		// Get the stored theme, if it doesn't exist, it will be null
			let stored_theme = localStorage.getItem("bonk_theme");
			try {
				stored_theme = JSON.parse(stored_theme);
			} catch (er) {
			// If the stored theme is somehow corrupted - use blank theme
				console.log("[Themes] Failed to load theme from localstorage");
				console.log(`[Themes] ${stored_theme}`);
				console.error(er);
				alert("Failed to load theme from localstorage");
				stored_theme = {};
			}
			// Merge the stored theme with the default (if null, it will overwrite)
			return {...default_configuration, ...stored_theme};
		};

		// Get the stored theme when the page loads
		const configuration = get_stored_theme();
	
		// Check if the script is running inside maingameframe or not
		if (unsafeWindow.parent === unsafeWindow) {
			// Outer bonk.io website
			// Hotkey to turn the script on and off
			const maingameframe = document.getElementById("maingameframe");
			document.addEventListener("keydown", evt => {
				if (evt.key?.toLowerCase() === maingameframe.contentWindow.bonk_theme.get_hotkey() && evt.ctrlKey && evt.altKey) {			
					document.body.classList.toggle("themed-colors");
					maingameframe.contentDocument.body.classList.toggle("themed-colors");
					maingameframe.contentWindow.bonk_theme.on = !maingameframe.contentWindow.bonk_theme.on;
				}
			});

			// Flag to check whether the theme container already exists
			let theme_container_created = false;
			// Create the menu only when someone clicks on the thing in the menu
			// Show the theme container when you click the menu command thing
			// eslint-disable-next-line no-undef
			GM_registerMenuCommand("Edit theme", () => {
				// Creating a new theme_container, don't make new ones after this
				if (theme_container_created) return document.getElementById("theme_container").style.display = "block";
				theme_container_created = true;

				// Create the theme menu. This is really ugly but there's like no other way idk.
				const theme_container = document.createElement("div");
				const theme_container_title = document.createElement("p");
				const configuration_container = document.createElement("div");
				const configuration_container_label = document.createElement("label");
				const configuration_list = document.createElement("div");
				const configuration_json = document.createElement("textarea");
				const configuration_json_label = document.createElement("label");
				const theme_cancel = document.createElement("div");
				const theme_save = document.createElement("div");
				const theme_reset_dark = document.createElement("div");
				const theme_reset_light = document.createElement("div");
		
				// Add the IDs so we can style them with CSS
				theme_container_title.id = "theme_container_title";
				theme_container.id = "theme_container";
				configuration_container_label.id = "configuration_container_label";
				configuration_list.id = "configuration_list";
				configuration_json.id = "configuration_json";
				theme_cancel.id = "theme_cancel";
				theme_save.id = "theme_save";
				theme_reset_dark.class = "theme_reset";
				theme_reset_light.class = "theme_reset";

				// Add the classes
				theme_container_title.classList.add("classicTopBar");
				theme_cancel.classList.add("brownButton", "brownButton_classic", "buttonShadow");
				theme_save.classList.add("brownButton", "brownButton_classic", "buttonShadow");
				theme_reset_dark.classList.add("brownButton", "brownButton_classic", "buttonShadow");
				theme_reset_light.classList.add("brownButton", "brownButton_classic", "buttonShadow");

				// Add text to buttons and titles
				theme_container_title.textContent = "Theme Editor";
				theme_cancel.textContent = "CANCEL";
				theme_save.textContent = "SAVE";
				theme_reset_dark.textContent = "RESET THEME TO DARK PRESET";
				theme_reset_light.textContent = "RESET THEME TO LIGHT PRESET";
				configuration_container_label.textContent = "Hover over settings for more info";
				configuration_json_label.textContent = "Share, Backup or Import theme data - Copy or paste themes from/into the textbox below";
				configuration_json_label.for = "configuration_json";
				configuration_json.value = JSON.stringify(configuration);
				
				theme_container.appendChild(theme_container_title);

				configuration_list.appendChild(configuration_container_label);
				configuration_list.appendChild(document.createElement("br"));

				// Populate the themes list with the theme that is currently loaded
				for (const [key, value] of Object.entries(configuration)) {
				// Check if the setting exists
					if (!configuration_metadata_map[key]) continue;
					const datatype_metadata = datatype_metadata_map[configuration_metadata_map[key].datatype];
					const configuration_container = document.createElement("div");
					const configuration_name = document.createElement("p");
					const configuration_value = document.createElement("input");
			
					configuration_container.classList.add("configuration_container");
					configuration_name.classList.add("configuration_name");
					configuration_value.classList.add("configuration_value");

					configuration_container.dataset["configuration_name"] = key;
					configuration_container.title = configuration_metadata_map[key].description ?? "No description";
					configuration_name.textContent = key.replace(/_/g, " ");

					for (const [key, value] of Object.entries(datatype_metadata.attributes))
						configuration_value[key] = value;
					configuration_value[datatype_metadata.value] = value;
					// Using "oninput" instead of addEventListener so we can call it somewhere else
					configuration_value.oninput = () => {
						if (!datatype_metadata.ignore_change) {
						// Update the CSS variable on both bonk.io and maingameframe
							document.documentElement.style
								.setProperty(`--bonk_theme_${key}`, configuration_value.value);
							maingameframe.contentDocument.documentElement.style
								.setProperty(`--bonk_theme_${key}`, configuration_value.value);
							// Store it in the current configuration (so it can be saved later)
							configuration[key] = configuration_value[datatype_metadata.value];
						}

						// Update the data at the bottom
						configuration_json.value = JSON.stringify(configuration);
					};

					// Add the current config to the list of settings
					configuration_container.appendChild(configuration_name);
					configuration_container.appendChild(configuration_value);
					configuration_list.appendChild(configuration_container);
				}

				// Add the Save and Cancel buttons
				configuration_container.appendChild(theme_cancel);
				configuration_container.appendChild(theme_save);

				configuration_list.appendChild(document.createElement("br")); // this
				configuration_list.appendChild(configuration_container);
				configuration_list.appendChild(document.createElement("br")); // is
				configuration_list.appendChild(configuration_json_label);
				configuration_list.appendChild(document.createElement("br")); // so
				configuration_list.appendChild(configuration_json);
				configuration_list.appendChild(document.createElement("br")); // bad
				configuration_list.appendChild(theme_reset_dark);
				configuration_list.appendChild(theme_reset_light);
				theme_container.appendChild(configuration_list);

				// When save is clicked, try save theme to localstorage
				theme_save.addEventListener("click", () => {
					try {
						localStorage.setItem("bonk_theme", JSON.stringify(configuration));
						theme_container.style.display = "none";
					} catch (er) {
						console.log("[Themes] Failed to save theme to localstorage");
						console.log(`[Themes] ${JSON.stringify(configuration)}`);
						console.error(er);
						alert("Failed to save theme to localstorage");
					}
				});

				// Reset the state of configuration from localstorage
				theme_cancel.addEventListener("click", () => {
					configuration_json.value = JSON.stringify(get_stored_theme());
					configuration_json.oninput();
					theme_container.style.display = "none";
				});

				// Get the currently saved theme, and overwrite everything with it
				theme_reset_dark.addEventListener("click", () => {
					configuration_json.value = JSON.stringify(default_configuration);
					configuration_json.oninput();
				});

				theme_reset_light.addEventListener("click", () => {
					// Light theme defaults are not calculated by default, so we need to calculate it here now
					const default_light_configuration = {};
					for (const [key, value] of Object.entries(configuration_metadata_map)) 
						default_light_configuration[key] = value.default_light;

					configuration_json.value = JSON.stringify(default_light_configuration);
					configuration_json.oninput();
				});

				configuration_json.oninput = () => {
					try {
						const new_theme = JSON.parse(configuration_json.value);
						// Update the configurations
						const configuration_list_children = Array.from(configuration_list.children);
						for (const configuration_container of configuration_list_children) {
							const key = configuration_container.dataset["configuration_name"];
							const value = new_theme[key];
							if (!value) continue;

							const configuration_value = configuration_container.querySelector("input");
							const datatype_meta = datatype_metadata_map[configuration_metadata_map[key].datatype];
						
							configuration_value[datatype_meta.value] = value;
							configuration_value.oninput();
						}

						// If success, remove the border
						configuration_json.style.setProperty("border", "none");
					} catch (er) {
						console.log("[Themes] Failed to load theme from <textarea>");
						console.log(`[Themes] ${configuration_json.value}`);
						console.error(er);
						// If there is an error, set a red border
						configuration_json.style.setProperty("border", "3px solid var(--bonk_theme_red)", "important");
					}
				};

				// Add the theme dialog to body
				document.body.appendChild(theme_container);
			});
		} else {
			// This code is for inside maingameframe
			// Hotkey to turn the script on and off
			document.addEventListener("keydown", evt => {
				if (evt.key?.toLowerCase() === unsafeWindow.bonk_theme.get_hotkey() && evt.ctrlKey && evt.altKey) {
					unsafeWindow.bonk_theme.on = !unsafeWindow.bonk_theme.on;
					document.body.classList.toggle("themed-colors");
					unsafeWindow.parent.document.body.classList.toggle("themed-colors");
				}
			});
		}

		// List of default CSS variables
		let root_vars = [];
		for (const [key, value] of Object.entries(configuration))
			root_vars.push(`--bonk_theme_${key}: ${value};`);

		// eslint-disable-next-line no-undef
		GM_addStyle(`
	/* CSS Variables, so we can edit their values on the fly */
	:root {
		${root_vars.join("\n		")}
	}

	/* Page background color */
	.themed-colors #pagecontainer,
	body.themed-colors { 
		background-color: var(--bonk_theme_page_background) !important;
		--kkleeMultiSelectColour: ${configuration.colored_text};
		--kkleeErrorColour: ${configuration.red_text};
		--kkleeCheckboxTfsTrue: ${configuration.green_text};
		--kkleeCheckboxTfsFalse: ${configuration.red_text};
	}

	/* Dark scroll bar tracks on Firefox and Chromium-based browsers */
	.themed-colors {
		scrollbar-color: var(--bonk_theme_scrollbar_thumb) var(--bonk_theme_scrollbar_background) !important;
	}

	.themed-colors ::-webkit-scrollbar-track {
		background-color: var(--bonk_theme_scrollbar_background) !important;
	}

	.themed-colors ::-webkit-scrollbar-thumb { 
		background-color: var(--bonk_theme_scrollbar_thumb) !important;
	}

	/* Color tooltips */
	.themed-colors #friendsToolTip,
	.themed-colors #pretty_top_replay_report_tooltip,
	.themed-colors #pretty_top_replay_fav_tooltip,
	.themed-colors #newbonklobby_tooltip,
	.themed-colors #mapeditor_rightbox_shapeaddmenucontainer,
	.themed-colors #mapeditor_leftbox_createmenucontainerleft,
	.themed-colors #mapeditor_leftbox_createmenucontainerright,
	.themed-colors #mapeditor_leftbox_copywindow,
	.themed-colors #mapeditor_rightbox_newjointmenu,
	.themed-colors .newbonklobby_playerentry_menu,
	.themed-colors .newbonklobby_playerentry_menu_submenu {
		background-color: var(--bonk_theme_mini_menu_color) !important;
		color: var(--bonk_theme_mini_menu_text) !important;
	}

	/* Color the lobby's background */
	.themed-colors #bonkiocontainer {
		background-color: var(--bonk_theme_behind_lobby_background) !important;
	}
	
	/* Dialogs - I can just do windowShadow, but it might break things if new things are added */
	/* It's better to have a new thing visibly light rather than broken */
	.themed-colors .bt-primary-background,
	.themed-colors #autoLoginContainer,
	.themed-colors #guestOrAccountContainer_accountBox,
	.themed-colors #guestOrAccountContainer_guestBox,
	.themed-colors #guestContainer, 
	.themed-colors .accountContainer,
	.themed-colors #registerwindow_remember_label,
	.themed-colors #loginwindow_remember_label,
	.themed-colors #loginwindow,
	.themed-colors #registerwindow,
	.themed-colors #settingsContainer,
	.themed-colors .settingsHeading,
	.themed-colors .redefineControls_selectionCell:hover,
	.themed-colors #newswindow,
	.themed-colors #skinmanager,
	.themed-colors .skineditor_shapewindow,
	.themed-colors .skineditor_shapewindow_imagecontainer,
	.themed-colors #skineditor_propertiesbox,
	.themed-colors #skineditor_propertiesbox_table,
	.themed-colors #skineditor_previewbox,
	.themed-colors #skineditor_layerbox,
	.themed-colors #skineditor_layerbox_baselabel,
	.themed-colors #quickPlayWindow,
	.themed-colors #roomlistcreatewindow,
	.themed-colors .roomlistcreatewindowlabel,
	.themed-colors #roomlistjoinpasswordwindow,
	.themed-colors #sm_connectingWindow,
	.themed-colors #newbonklobby_specbox,
	.themed-colors #newbonklobby_playerbox,
	.themed-colors #newbonklobby_chatbox,
	.themed-colors #newbonklobby_settingsbox,
	.themed-colors #newbonklobby_votewindow,
	.themed-colors #newbonklobby_votewindow_maptitle,
	.themed-colors #newbonklobby_votewindow_mapauthor,
	.themed-colors #leaveconfirmwindow,
	.themed-colors #leaveconfirmwindow_text2,
	.themed-colors #hostleaveconfirmwindow,
	.themed-colors #hostleaveconfirmwindow_text2,
	.themed-colors #maploadwindow,
	.themed-colors #maploadwindowgreybar,
	.themed-colors #maploadwindowstatustext,
	.themed-colors #mapeditor_leftbox,
	.themed-colors #mapeditor_midbox,
	.themed-colors #mapeditor_rightbox,
	.themed-colors #mapeditor_save_window,
	.themed-colors #kkleeRoot,
	.themed-colors #skineditor_colorpicker,
	.themed-colors #mapeditor_colorpicker,
	.themed-colors #friendsSendWindow,
	.themed-colors #roomlistfilterwindow,
	.themed-colors #ingamecountdown,
	.themed-colors #mapeditor_save_overwrite_window,
	#theme_container {
		background-color: var(--bonk_theme_primary_background) !important;
		color: var(--bonk_theme_primary_text) !important;
	}
	
	/* Lighter backgrounds for some menus and stuff */
	.themed-colors .bt-secondary-background,
	.themed-colors #bonkioheader,
	.themed-colors .windowTopBar_classic,
	.themed-colors #autoLogin_text,
	.themed-colors .guestOrAccountContainerLabelBox,
	.themed-colors #guest_nametext,
	.themed-colors #loginwindow_username,
	.themed-colors #loginwindow_password,
	.themed-colors #registerwindow_username,
	.themed-colors #registerwindow_password,
	.themed-colors #guest_skinbox,
	.themed-colors #skineditor_propertiesbox_blocker,
	.themed-colors #newswindow_white,
	.themed-colors .quickPlayWindowModeDiv,
	.themed-colors .quickPlayWindowText1,
	.themed-colors .quickPlayWindowText2,
	.themed-colors .quickPlayWindowText3,
	.themed-colors #roomlist,
	.themed-colors #roomlisttableheadercontainer,
	.themed-colors .roomlistcreatewindowinput,
	.themed-colors .whiteInputField,
	.themed-colors #sm_connectingWindow_text,
	.themed-colors #friendsContainer,
	.themed-colors .friends_table,
	.themed-colors .skinmanager_icon,
	.themed-colors #maploadwindowsearchinput,
	.themed-colors .maploadwindowmapdiv,
	.themed-colors #mapeditor_midbox_explain,
	.themed-colors #mapeditor_rightbox_namefield,
	.configuration_value,
	#configuration_json {
		border-color: transparent !important;
		background-color: var(--bonk_theme_secondary_background) !important;
		color: var(--bonk_theme_primary_text) !important;
	}
	
	.themed-colors .mapeditor_field,
	.themed-colors .skineditor_field,
	.themed-colors .mapeditor_rightbox_table_shape_headerfield {
		border-color: transparent;
		background-color: var(--bonk_theme_secondary_background);
		color: var(--bonk_theme_primary_text);
	}

	.themed-colors .newbonklobby_playerentry_name,
	.themed-colors #newbonklobby_modetext,
	.themed-colors #newbonklobby_roundslabel,
	.themed-colors .maploadwindowtext,
	.themed-colors #roomliststatustext, 
	.themed-colors #roomlisttable,
	.themed-colors .mapeditor_rightbox_table_leftcell,
	.themed-colors .mapeditor_rightbox_table_rightcell,
	.themed-colors #ingamecountdown_text,
	.themed-colors #bonkiobugemail {
		color: var(--bonk_theme_primary_text) !important;
	}

	.themed-colors .newbonklobby_mapsuggest_high,
	.themed-colors .maploadwindowtext_picks,
	.themed-colors .newbonklobby_chat_msg_name,
	.themed-colors .newbonklobby_playerentry_balancetext {
		color: var(--bonk_theme_primary_text);
	}

	.themed-colors .newbonklobby_playerentry_level,
	.themed-colors .newbonklobby_playerentry_pingtext, 
	.themed-colors #newbonklobby_chat_lowerinstruction,
	.themed-colors #newbonklobby_chat_lowerline, 
	.themed-colors .newbonklobby_chat_msg_txt,
	.themed-colors #newbonklobby_chat_input,
	.themed-colors #newbonklobby_maptext,
	.themed-colors #newbonklobby_roundsinput,
	.themed-colors #newbonklobby_mapauthortext,
	.themed-colors #newbonklobby_votewindow_maptitle_by,
	.themed-colors #hostleaveconfirmwindow_text1,
	.themed-colors #leaveconfirmwindow_text1,
	.themed-colors .maploadwindowtextmode_picks,
	.themed-colors .maploadwindowtextmode,
	.themed-colors .maploadwindowtextauthor,
	.themed-colors .maploadwindowtextauthor_picks,
	.themed-colors .roomlisttablejoined {
		color: var(--bonk_theme_secondary_text) !important;
	}

	/* Lobby chat */
	.themed-colors .newbonklobby_mapsuggest_low {
		color: var(--bonk_theme_secondary_text);
	}

	/* Copy link */
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(9, 85, 199);"],
	.themed-colors .newbonklobby_chat_link {
		color: var(--bonk_theme_blue_text) !important;
	}

	/* /help, player join */
	.themed-colors .newbonklobby_playerentry_balance_nerf,
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(204, 68, 68);"],
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(204, 51, 51);"],
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(255, 0, 0);"] {
		color: var(--bonk_theme_red_text) !important;
	}

	/* Friend requests */
	.themed-colors .newbonklobby_playerentry_balance_buff,
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(0, 103, 93);"] {
		color: var(--bonk_theme_green_text) !important;
	}

	/* /curate */
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(96, 51, 204);"] {
		color: var(--bonk_theme_purple_text) !important;
	}

	/* "You are now host" */
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(122, 25, 154);"],
	.themed-colors #newbonklobby_chat_content span[style="color: rgb(128, 13, 110);"] {
		color: var(--bonk_theme_magenta_text) !important;
	}

	.themed-colors .newbonklobby_chat_status:not([style]) {
		filter: brightness(var(--bonk_theme_brightness_lighter)) !important;
	}

	.themed-colors #descriptioncontainer,
	.themed-colors #gamethumbbox,
	.themed-colors #bgreplay {
		filter: brightness(var(--bonk_theme_brightness_darker)) !important;
	}

	/* Style sliders and buttons */
	.themed-colors .compactSlider {
		background-color: transparent !important;
		color: var(--bonk_theme_button_text) !important;
	}

	.themed-colors .brownButton_classic,
	.themed-colors .dropdown_classic,
	.themed-colors .skineditor_field_button,
	.themed-colors .mapeditor_field_button,
	#theme_save, #theme_cancel, .theme_reset {
		border-color: transparent !important;
		background-color: var(--bonk_theme_button_color) !important;
		color: var(--bonk_theme_button_text) !important;
	}

	.themed-colors .brownButtonDisabled,
	.themed-colors .brownButton_disabled_classic,
	.themed-colors .dropdown-option-disabled {
		background-color: var(--bonk_theme_disabled_button_color) !important;
		color: var(--bonk_theme_button_text) !important;
	}

	.themed-colors .brownButton_classic:hover,
	.themed-colors .dropdown_classic:hover,
	#theme_save:hover, #theme_cancel:hover, .theme_reset:hover {
		background-color: var(--bonk_theme_hover_button_color) !important;
		color: var(--bonk_theme_button_text) !important;
	}

	.themed-colors .brownButton_classic:active,
	.themed-colors .dropdown_classic:active,
	.themed-colors .newbonklobby_teamlockbutton_warn,
	#theme_save:active, #theme_cancel:active, .theme_reset:active {
		background-color: var(--bonk_theme_active_button_color) !important;
		color: var(--bonk_theme_button_text) !important;
	}

	/* Tables in the game - Skin editor, Map Editor and Custom game list */
	.themed-colors #roomlisttable tr:nth-child(odd),
	.themed-colors .friends_table>tbody:nth-child(odd),
	.themed-colors #skineditor_layerbox_layertable tr:nth-child(odd),
	.themed-colors #mapeditor_leftbox_platformtable tr:nth-child(odd),
	.themed-colors #mapeditor_leftbox_spawntable tr:nth-child(odd),
	.themed-colors #redefineControls_table tr:nth-child(odd),
	.themed-colors .roomlistfilterwindow_a,
	.themed-colors #redefineControls_table th {
		background-color: var(--bonk_theme_table_color) !important;
		color: var(--bonk_theme_primary_text) !important;
	}

	.themed-colors #roomlisttable tr:nth-child(even),
	.themed-colors .friends_table tr:nth-child(even),
	.themed-colors #skineditor_layerbox_layertable tr:nth-child(even),
	.themed-colors #mapeditor_leftbox_platformtable tr:nth-child(even),
	.themed-colors #mapeditor_leftbox_spawntable tr:nth-child(even),
	.themed-colors #redefineControls_table tr:nth-child(even),
	.themed-colors #roomlisttableheadercontainer,
	.themed-colors .roomlistfilterwindow_b {
		background-color: var(--bonk_theme_table_alt_color) !important;
		color: var(--bonk_theme_primary_text) !important;
	}

	.themed-colors #roomlisttable tr.FRIENDSPRESENT,
	.themed-colors .maploadwindowtextaddedby_picks {
		color: var(--bonk_theme_colored_text) !important;
	}

	.themed-colors tr.HOVERUNSELECTED:hover td {
		background-color: var(--bonk_theme_table_hover_color) !important;
	}

	.themed-colors tr.HOVERSELECTED td,
	.themed-colors tr.SELECTED td {
		background-color: var(--bonk_theme_table_active_color) !important;
	}

	/* "popup" color, custom room list top bar */
	.themed-colors .bt-titlebar,
	.themed-colors .windowTopBar_classic, 
	.themed-colors .classicTopBar,
	.themed-colors .newbonklobby_boxtop, 
	.themed-colors .newbonklobby_boxtop_classic,
	.themed-colors .friends_topbar,
	.themed-colors #newbonklobby_votewindow_top,
	.themed-colors #hostleaveconfirmwindow_top,
	.themed-colors #leaveconfirmwindow_top,
	.themed-colors #ingamecountdown_top,
	.themed-colors #roomlisthidepasswordedcheckboxlabel,
	#theme_container_title {
		background-color: var(--bonk_theme_window_color) !important;
		color: var(--bonk_theme_window_text) !important;
	}

	/* Style the top bar */
	.themed-colors #pretty_top_bar {
		background-color: var(--bonk_theme_top_bar_color) !important;
		opacity: var(--bonk_theme_top_bar_opacity) !important;
		color: var(--bonk_theme_top_bar_text) !important;
	}

	.themed-colors .pretty_top_button,
	.themed-colors #pretty_top_name,
	.themed-colors #pretty_top_name,
	.themed-colors #pretty_top_level,
	.themed-colors #pretty_top_playercount {
		background-color: var(--bonk_theme_top_bar_color) !important;
		color: var(--bonk_theme_top_bar_text) !important;
	}

	.themed-colors .pretty_top_button:hover {
		background-color: var(--bonk_theme_top_bar_hover_color) !important;
	}

	/* Invert the Bonk.io description at the bottom of the page */
	.themed-colors #descriptioninner,
	.themed-colors #descriptioninner .descriptionthumbr,
	.themed-colors #descriptioninner .descriptionthumbl {
		filter: invert(var(--bonk_theme_description_invert)) !important;
	}

	/* Change color of borders in settings */
	.themed-colors #redefineControls_table td,
	.themed-colors #redefineControls_table th,
	.themed-colors #redefineControls_table {
		border: 1px solid var(--bonk_theme_border_color) !important;
	}

	/* Players in lobby */
	.themed-colors .newbonklobby_playerentry {
		border-left: 4px solid var(--bonk_theme_primary_background) !important;
		border-top: 4px solid var(--bonk_theme_primary_background) !important;
		border-right: 4px solid var(--bonk_theme_primary_background) !important;
		background-color: var(--bonk_theme_primary_background) !important;
		color: var(--bonk_theme_primary_text) !important;
	}

	.themed-colors .newbonklobby_playerentry:hover {
		border-left: 4px solid var(--bonk_theme_secondary_background) !important;
		border-top: 4px solid var(--bonk_theme_secondary_background) !important;
		border-right: 4px solid var(--bonk_theme_secondary_background) !important;
		background-color: var(--bonk_theme_secondary_background) !important;
	}

	.themed-colors #newbonklobby_chat_lowerline {
		border-top: 1px solid var(--bonk_theme_border_color) !important;
	}

	.themed-colors #newbonklobby_playerbox_middleline {
		border-left: 1px solid var(--bonk_theme_border_color) !important;
	}

	/* Headings in the Map Editor and stuff */
	.themed-colors .friends_titles,
	.themed-colors .mapeditor_table_heading_div {
		background-color: var(--bonk_theme_list_headers) !important;
	}

	/* Change the XP bar to use the colors we want */
	.themed-colors #xpbarfill {
		background-color: var(--bonk_theme_xp_bar_fill) !important;
	}

	/* Below is CSS for our theme editor thing */
	#theme_container {
		border-radius: 3px 0px 3px 3px;
		z-index: 999;
		font-family: "futurept_b1";
		padding: 0px;
		right: 0px;
		top: 0px;
		position: fixed;
		overflow-y: scroll;
		max-height: 100vh;
		width: 20em;
	}

	#configuration_list {
		padding: 1.5em;
		display: flex;
		flex-direction: column;
	}

	#theme_container_title {
		text-align: center;
		margin: 0px;
		font-size: 20px;
		line-height: 32px;
		font-family: "futurept_b1"
	}

	.configuration_container {
		white-space: pre-line;
		display: flex;
		justify-content: space-between;
		align-items: center;
		height: 2.25em;
	}

	.configuration_name {
		text-transform: capitalize;
		float: left;
		width: 70%;
	}

	.configuration_value {
		float: right;
		width: 25%;
	}

	#theme_cancel {
		float: left;
	}

	#theme_save {
		float: right;
	}

	#theme_save,
	#theme_cancel {
		width: 45%;
		height: 30px;
		line-height: 30px;
	}

	#configuration_json {
		align-self: center;
		resize: none;
		width: 100%;
		height: 15em;
	}
	`);
	});
})();