// ==UserScript== // @name Custom Status Bar // @description Lets you customize the Geoguessr status bar via a GUI // @version 1.3.0 // @license MIT // @author zorby#1431 // @namespace https://greasyfork.org/en/users/986787-zorby // @match https://www.geoguessr.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com // @downloadURL https://update.greasyfork.icu/scripts/465248/Custom%20Status%20Bar.user.js // @updateURL https://update.greasyfork.icu/scripts/465248/Custom%20Status%20Bar.meta.js // ==/UserScript== function pathMatches(path) { return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`)) } function getIndex(element) { if (!element) return -1 let i = 0 while (element = element.previousElementSibling) { i++ } return i } const OBSERVER_CONFIG = { characterDataOldValue: false, subtree: true, childList: true, characterData: false } const SCRIPT_PREFIX = "csb__" const CONFIG_KEY = SCRIPT_PREFIX + "config" const STYLE_ID = SCRIPT_PREFIX + "style" const PERCENTAGE_INPUT_CLASS = SCRIPT_PREFIX + "percentage-input" const COLOR_INPUT_CLASS = SCRIPT_PREFIX + "color-input" const TEXT_INPUT_CLASS = SCRIPT_PREFIX + "text-input" const DELETE_BUTTON_CLASS = SCRIPT_PREFIX + "delete-button" const STANDARD_BUTTON_CLASS = SCRIPT_PREFIX + "standard-button" const DOWN_BUTTON_CLASS = SCRIPT_PREFIX + "down-button" const UP_BUTTON_CLASS = SCRIPT_PREFIX + "up-button" const CUSTOMIZE_STATUS_BAR_BUTTON_ID = SCRIPT_PREFIX + "customize-status-bar-button" const ADD_GRADIENT_NODE_BUTTON_ID = SCRIPT_PREFIX + "add-gradient-node-button" const CUSTOMIZE_STATUS_BAR_SCREEN_ID = SCRIPT_PREFIX + "customize-status-bar-screen" const GRADIENT_NODE_LIST_ID = SCRIPT_PREFIX + "gradient-node-list" const TEXT_COLOR_NODE_LIST_ID = SCRIPT_PREFIX + "text-color-node-list" const RESUME_BUTTON_ID = SCRIPT_PREFIX + "resume-button" const BACKGROUND_COLOR_ID = SCRIPT_PREFIX + "background-color" const TEMP_COLOR_VAR = "--" + SCRIPT_PREFIX + "temp-color" const RESET_DEFAULTS_BUTTON_ID = SCRIPT_PREFIX + "reset-defaults-button" const defaultNode = () => ({ color: "#000000", percentage: 100 }) const DEFAULT_BACKGROUND_COLOR = "var(--ds-color-purple-80)" const DEFAULT_GRADIENT_NODES = [ { color: "#a19bd999", percentage: 0 }, { color: "#00000000", percentage: 50 }, { color: "#00000000", percentage: 50 } ] const DEFAULT_TEXT_COLORS = [ "var(--ds-color-purple-20)", "var(--ds-color-white)" ] const configString = localStorage.getItem(CONFIG_KEY) let gradientNodes = DEFAULT_GRADIENT_NODES let textColors = DEFAULT_TEXT_COLORS let backgroundColor = DEFAULT_BACKGROUND_COLOR if (configString) { const config = JSON.parse(configString) gradientNodes = config.gradient textColors = config.textColors backgroundColor = config.backgroundColor } const CUSTOMIZE_STATUS_BAR_BUTTON = `
` const COLOR_WIDGET = ` ` const GRADIENT_NODE = `
%
${COLOR_WIDGET}
` const colorNode = (label, id) => `
${label}
${COLOR_WIDGET}
` // Transforms an arbitrary css color value (like "rgb(0, 0, 0)" or "var(--some-color)") into a hex color // (pretty hacky) const evaluateColor = (color) => { const statusBar = document.querySelector(".game_status___YFni") statusBar.style.setProperty(TEMP_COLOR_VAR, color) const hexColor = window.getComputedStyle(statusBar).getPropertyValue(TEMP_COLOR_VAR) // Verify that it's indeed a hex color if (!hexColor.startsWith("#")) { console.log("could not evaluate color!") return "#000000" } // Strip the alpha channel if (hexColor.length > 7) { return hexColor.substr(0, 7) } // Expand the color, if it's in compressed form if (hexColor.length == 4) { return "#" + hexColor.substr(1).split("").map(char => char + char).join("") } return hexColor } const appendTextColorNode = (parent, label, index) => { parent.insertAdjacentHTML("beforeend", colorNode(label, "")) const textColorNode = parent.lastElementChild const colorInput = textColorNode.querySelector(`.${COLOR_INPUT_CLASS}`) const colorTextInput = textColorNode.querySelector(`.${TEXT_INPUT_CLASS}`) console.log(evaluateColor(textColors[index])) colorInput.value = evaluateColor(textColors[index]) colorTextInput.value = textColors[index] colorInput.oninput = () => { textColors[index] = colorInput.value colorTextInput.value = textColors[index] updateStatusBarStyles() } colorTextInput.oninput = () => { textColors[index] = colorTextInput.value colorInput.value = evaluateColor(textColors[index]) updateStatusBarStyles() } } const generateGradientString = () => { return `linear-gradient(180deg, ${ gradientNodes.map((node) => `${node.color} ${node.percentage}%`).join(",") })` } const updateStatusBarStyles = () => { const style = document.getElementById(STYLE_ID) style.innerHTML = ` .slanted-wrapper_variantPurple__AujW3 { --variant-background-color: ${generateGradientString()}, ${backgroundColor}; } .slanted-wrapper_variantPurple__AujW3 .status_label__mZ7Ok { color: ${textColors[0]} } .slanted-wrapper_variantPurple__AujW3 .status_value__w_Nh0 { color: ${textColors[1]} } ` localStorage.setItem(CONFIG_KEY, JSON.stringify({ "gradient": gradientNodes, "textColors": textColors, "backgroundColor": backgroundColor })) } const appendGradientNode = (parent) => { parent.insertAdjacentHTML("beforeend", GRADIENT_NODE) const gradientNode = parent.lastElementChild const percentageInput = gradientNode.querySelector(`.${PERCENTAGE_INPUT_CLASS}`) const colorInput = gradientNode.querySelector(`.${COLOR_INPUT_CLASS}`) const colorTextInput = gradientNode.querySelector(`.${TEXT_INPUT_CLASS}`) const deleteButton = gradientNode.querySelector(`.${DELETE_BUTTON_CLASS}`) const upButton = gradientNode.querySelector(`.${UP_BUTTON_CLASS}`) const downButton = gradientNode.querySelector(`.${DOWN_BUTTON_CLASS}`) const updateInputs = () => { percentageInput.value = gradientNodes[getIndex(gradientNode)].percentage colorInput.value = evaluateColor(gradientNodes[getIndex(gradientNode)].color) colorTextInput.value = gradientNodes[getIndex(gradientNode)].color } gradientNode.updateInputs = updateInputs updateInputs() percentageInput.oninput = () => { gradientNodes[getIndex(gradientNode)].percentage = percentageInput.value updateStatusBarStyles() } colorInput.oninput = () => { gradientNodes[getIndex(gradientNode)].color = colorInput.value colorTextInput.value = gradientNodes[getIndex(gradientNode)].color updateStatusBarStyles() } colorTextInput.oninput = () => { gradientNodes[getIndex(gradientNode)].color = colorTextInput.value colorInput.value = evaluateColor(gradientNodes[getIndex(gradientNode)].color) updateStatusBarStyles() } deleteButton.onclick = () => { gradientNodes.splice(getIndex(gradientNode), 1) gradientNode.remove() updateStatusBarStyles() } upButton.onclick = () => { let temp = gradientNodes[getIndex(gradientNode)].color gradientNodes[getIndex(gradientNode)].color = gradientNodes[getIndex(gradientNode) - 1].color gradientNodes[getIndex(gradientNode) - 1].color = temp parent.children[getIndex(gradientNode) - 1].updateInputs() updateInputs() updateStatusBarStyles() } downButton.onclick = () => { let temp = gradientNodes[getIndex(gradientNode)].color gradientNodes[getIndex(gradientNode)].color = gradientNodes[getIndex(gradientNode) + 1].color gradientNodes[getIndex(gradientNode) + 1].color = temp parent.children[getIndex(gradientNode) + 1].updateInputs() updateInputs() updateStatusBarStyles() } } const CUSTOMIZE_STATUS_BAR_SCREEN = `

Customize Status Bar

Background

${colorNode("Color", BACKGROUND_COLOR_ID)}

Gradient

Text colors

` const onCustomizeStatusBarButtonClick = () => { // Close the settings overlay document.querySelector(".game-menu_inGameMenuOverlay__XWQpg .buttons_buttons__3yvvA .button_variantPrimary__u3WzI").click() const gameLayout = document.querySelector(".in-game_layout__kqBbg") gameLayout.insertAdjacentHTML("beforeend", CUSTOMIZE_STATUS_BAR_SCREEN) const backgroundColorDiv = document.getElementById(BACKGROUND_COLOR_ID) const colorInput = backgroundColorDiv.querySelector(`.${COLOR_INPUT_CLASS}`) const colorTextInput = backgroundColorDiv.querySelector(`.${TEXT_INPUT_CLASS}`) colorInput.value = evaluateColor(backgroundColor) colorTextInput.value = backgroundColor colorInput.oninput = () => { backgroundColor = colorInput.value colorTextInput.value = backgroundColor updateStatusBarStyles() } colorTextInput.oninput = () => { backgroundColor = colorTextInput.value colorInput.value = evaluateColor(backgroundColor) updateStatusBarStyles() } const addGradientNodeButton = document.getElementById(ADD_GRADIENT_NODE_BUTTON_ID) addGradientNodeButton.onclick = () => { gradientNodes.push(defaultNode()) appendGradientNode(gradientNodeList) } const resetButton = document.getElementById(RESET_DEFAULTS_BUTTON_ID) resetButton.onclick = () => { gradientNodes = DEFAULT_GRADIENT_NODES textColors = DEFAULT_TEXT_COLORS backgroundColor = DEFAULT_BACKGROUND_COLOR updateStatusBarStyles() } const resumeButton = document.getElementById(RESUME_BUTTON_ID) resumeButton.onclick = () => { const statusBar = document.querySelector(".game_status___YFni") statusBar.style.zIndex = null document.querySelector(".game_canvas__twTKG").appendChild(statusBar) document.getElementById(CUSTOMIZE_STATUS_BAR_SCREEN_ID).remove() } // Move the status bar up so it's visible through the backdrop blur const statusBar = document.querySelector(".game_status___YFni") statusBar.style.zIndex = "30" document.querySelector(".in-game_layout__kqBbg").appendChild(statusBar) const gradientNodeList = document.getElementById(GRADIENT_NODE_LIST_ID) for (const i in gradientNodes) { appendGradientNode(gradientNodeList) } const textColorNodeList = document.getElementById(TEXT_COLOR_NODE_LIST_ID) appendTextColorNode(textColorNodeList, "Labels", 0) appendTextColorNode(textColorNodeList, "Values", 1) } const injectCustomizeStatusBarButton = (settingsScreen) => { settingsScreen.insertAdjacentHTML("afterend", CUSTOMIZE_STATUS_BAR_BUTTON) document.getElementById(CUSTOMIZE_STATUS_BAR_BUTTON_ID).onclick = onCustomizeStatusBarButtonClick } const onMutations = () => { if (!pathMatches("game/.+")) return if (!document.getElementById(STYLE_ID)) { const style = document.createElement("style") style.id = STYLE_ID document.body.appendChild(style) updateStatusBarStyles() } const settingsScreen = document.querySelector(".in-game_layout__kqBbg > .game-menu_inGameMenuOverlay__XWQpg .game-menu_settingsContainer__NeJu2 > .game-menu_divider__IhA4t") if (settingsScreen && !document.querySelector(`#${CUSTOMIZE_STATUS_BAR_BUTTON_ID}`)) { injectCustomizeStatusBarButton(settingsScreen) } } const observer = new MutationObserver(onMutations) observer.observe(document.body, OBSERVER_CONFIG)