Custom Status Bar

Lets you customize the status bar

Versión del día 22/05/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Custom Status Bar
// @description  Lets you customize the status bar
// @version      1.1.0
// @license      MIT
// @author       zorby#1431
// @namespace    https://greatest.deepsurf.us/en/users/986787-zorby
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// ==/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 defaultNode = () => ({
    color: "#000000",
    percentage: 100
})

const DEFAULT_GRADIENT_NODES = [
    {
        color: "#000000",
        percentage: 0
    },
    {
        color: "#000000",
        percentage: 100
    }
]

const DEFAULT_TEXT_COLORS = [
    "#b0b0b0",
    "#ffffff"
]

const configString = localStorage.getItem(CONFIG_KEY)
let gradientNodes = DEFAULT_GRADIENT_NODES
let textColors = DEFAULT_TEXT_COLORS

if (configString) {
    const config = JSON.parse(configString)

    gradientNodes = config.gradient
    textColors = config.textColors
}

const CUSTOMIZE_STATUS_BAR_BUTTON = `
  <button id="${CUSTOMIZE_STATUS_BAR_BUTTON_ID}" class="button_button__CnARx button_variantSecondary__lSxsR">
    Customize status bar
  </button>
  <div class="game-menu_divider__f2BbL"></div>
`

const GRADIENT_NODE = `
  <div style="
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto;
  ">
    <div class="grid-element">
      <input type="number" class="${PERCENTAGE_INPUT_CLASS}" min="0" max="100" step="any" required></input>
      <div style="font-weight: 700;">%</div>
    </div>
    <div class="grid-element" style="flex-direction: row-reverse">
      <button class="${DELETE_BUTTON_CLASS}">X</button>
      <button class="${STANDARD_BUTTON_CLASS} ${DOWN_BUTTON_CLASS}">v</button>
      <button class="${STANDARD_BUTTON_CLASS} ${UP_BUTTON_CLASS}" style="margin-left: 1rem;">^</button>
      <input type="color" class="${COLOR_INPUT_CLASS}" ></input>
      <input type="text" class="${TEXT_INPUT_CLASS}" style="width: 4.5rem;" pattern="[0-9a-fA-F]{6}" required></input>
      <div style="font-weight: 700;">#</div>
    </div>
  </div>
`

const appendTextColorNode = (parent, label, index) => {
    const html = `
      <div style="
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: auto;
      ">
        <div class="grid-element">
          <div style="font-weight: 700;">${label}</div>
        </div>
        <div class="grid-element" style="flex-direction: row-reverse">
          <input type="color" class="${COLOR_INPUT_CLASS}" ></input>
          <input type="text" class="${TEXT_INPUT_CLASS}" style="width: 4.5rem;" pattern="[0-9a-fA-F]{6}" required></input>
          <div style="font-weight: 700;">#</div>
        </div>
      </div>
    `

    parent.insertAdjacentHTML("beforeend", html)
    const textColorNode = parent.lastElementChild

    const colorInput = textColorNode.querySelector(`.${COLOR_INPUT_CLASS}`)
    const colorTextInput = textColorNode.querySelector(`.${TEXT_INPUT_CLASS}`)

    colorInput.value = textColors[index]
    colorTextInput.value = textColors[index].substring(1)

    colorInput.oninput = () => {
        textColors[index] = colorInput.value
        colorTextInput.value = textColors[index].substring(1)
        updateStatusBarStyles()
    }

    colorTextInput.oninput = () => {
        textColors[index] = "#" + colorTextInput.value
        colorInput.value = 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__95_Ub {
        --variant-background-color: ${generateGradientString()};
      }

      .slanted-wrapper_variantPurple__95_Ub .status_label__SNHKT {
        color: ${textColors[0]}
      }

      .slanted-wrapper_variantPurple__95_Ub .status_value__xZMNY {
        color: ${textColors[1]}
      }
    `

    localStorage.setItem(CONFIG_KEY, JSON.stringify({
        "gradient": gradientNodes,
        "textColors": textColors
    }))
}

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 = gradientNodes[getIndex(gradientNode)].color
        colorTextInput.value = gradientNodes[getIndex(gradientNode)].color.substring(1)
    }

    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.substring(1)
        updateStatusBarStyles()
    }

    colorTextInput.oninput = () => {
        gradientNodes[getIndex(gradientNode)].color = "#" + colorTextInput.value
        colorInput.value = 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 = `
  <div id="${CUSTOMIZE_STATUS_BAR_SCREEN_ID}" class="game-menu_gameMenu__8ON8f">
    <style>
      .${PERCENTAGE_INPUT_CLASS}, .${COLOR_INPUT_CLASS},
      .${TEXT_INPUT_CLASS}, .${DELETE_BUTTON_CLASS}, .${STANDARD_BUTTON_CLASS} {
        background: rgba(255,255,255,0.1);
        color: white;
        border: none;
        border-radius: 5px;
        font-family: var(--default-font);
        font-size: var(--font-size-14);
        padding: 0.5rem;
      }

      .${PERCENTAGE_INPUT_CLASS}, .${COLOR_INPUT_CLASS} {
        width: 3rem;
      }

      .${PERCENTAGE_INPUT_CLASS}, .${TEXT_INPUT_CLASS} {
        text-align: center;
        -moz-appearance: textfield;
      }

      .${PERCENTAGE_INPUT_CLASS}::-webkit-outer-spin-button,
      .${PERCENTAGE_INPUT_CLASS}::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
      }

      .${COLOR_INPUT_CLASS} {
        height: 100%;
        padding: 0.25rem;
      }

      .${COLOR_INPUT_CLASS}::-webkit-color-swatch-wrapper {
        padding: 0;
      }

      .${COLOR_INPUT_CLASS}::-webkit-color-swatch {
        border: none;
        border-radius: 5px;
      }

      .${TEXT_INPUT_CLASS}:invalid, .${PERCENTAGE_INPUT_CLASS}:invalid {
        background: rgba(209, 27, 38, 0.1);
        color: var(--color-red-60);
      }

      .${DELETE_BUTTON_CLASS}, .${STANDARD_BUTTON_CLASS} {
        width: 2rem;
        user-select: none;
      }

      .${DELETE_BUTTON_CLASS} {
        background: rgba(209, 27, 38, 0.1);
      }

      .${DELETE_BUTTON_CLASS}:hover, .${STANDARD_BUTTON_CLASS}:hover, .${COLOR_INPUT_CLASS}:hover {
        cursor: pointer;
      }

      .${DELETE_BUTTON_CLASS}:hover {
        background: var(--color-red-60);
      }

      .${STANDARD_BUTTON_CLASS}:hover {
        background: var(--color-grey-70);
      }

      #${CUSTOMIZE_STATUS_BAR_SCREEN_ID} .grid-element {
        display: flex;
        align-items: center;
        gap: 0.5rem;
      }
    </style>
    <div class="game-menu_innerContainer__jEQ9E">
      <p class="game-menu_header__KeQ7F">Customize Status Bar</p>
      <div class="game-menu_volumeContainer__dRQtK" style="display: flex; flex-direction: column; gap: 0.4rem;">
        <p class="game-menu_subHeader___oVKH">Gradient</p>
        <div id="${GRADIENT_NODE_LIST_ID}" style="display: flex; flex-direction: column; gap: 0.4rem; max-height: 10rem; overflow-y: auto;"></div>
        <button id="${ADD_GRADIENT_NODE_BUTTON_ID}" class="button_button__CnARx button_variantSecondary__lSxsR">Add node</button>
      </div>
      <div class="game-menu_volumeContainer__dRQtK" style="display: flex; flex-direction: column; gap: 0.4rem;">
        <p class="game-menu_subHeader___oVKH">Text colors</p>
        <div id="${TEXT_COLOR_NODE_LIST_ID}" style="display: flex; flex-direction: column; gap: 0.4rem;"></div>
      </div>
      <div class="game-menu_divider__f2BbL"></div>
      <button id="${RESUME_BUTTON_ID}" class="button_button__CnARx button_variantPrimary__xc8Hp">Resume Game</button>
    </div>
  </div>
`


const onCustomizeStatusBarButtonClick = () => {
    document.querySelector(".game-menu_gameMenu__8ON8f .button_variantPrimary__xc8Hp").click()

    const gameLayout = document.querySelector(".game-layout")
    gameLayout.insertAdjacentHTML("beforeend", CUSTOMIZE_STATUS_BAR_SCREEN)

    const addGradientNodeButton = document.getElementById(ADD_GRADIENT_NODE_BUTTON_ID)
    addGradientNodeButton.onclick = () => {
        gradientNodes.push(defaultNode())
        appendGradientNode(gradientNodeList)
    }

    const resumeButton = document.getElementById(RESUME_BUTTON_ID)
    resumeButton.onclick = () => {
        document.querySelector(".game-layout__status").style.zIndex = null
        document.getElementById(CUSTOMIZE_STATUS_BAR_SCREEN_ID).remove()
    }

    document.querySelector(".game-layout__status").style.zIndex = "30"

    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__7zzGJ > .game-menu_gameMenu__8ON8f .game-menu_divider__f2BbL")

    if (settingsScreen && !document.querySelector(`#${CUSTOMIZE_STATUS_BAR_BUTTON_ID}`)) {
        injectCustomizeStatusBarButton(settingsScreen)
    }
}

const observer = new MutationObserver(onMutations)

observer.observe(document.body, OBSERVER_CONFIG)