- // ==UserScript==
- // @name Geoguessr Blink Mode
- // @description Shows the round briefly, then screen goes black and you have unlimited time to make your guess.
- // @version 1.3.4
- // @author macca7224
- // @license MIT
- // @match https://www.geoguessr.com/*
- // @require https://unpkg.com/@popperjs/core@2.11.5/dist/umd/popper.min.js
- // @grant none
- // @require https://gf.qytechs.cn/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151654
- // @namespace https://gf.qytechs.cn/en/scripts/438579-geoguessr-blink-mode
- // @icon https://www.svgrepo.com/show/40039/eye.svg
- // ==/UserScript==
-
- const guiEnabled = true
- // ^^^^ Set to false (all lowercase) if you want to hide the GUI and manually enable the script/set the time, otherwise true
-
- let timeLimit = 1.5
- // ^^^ Modify this number above to change the time
-
- let roundDelay = 0
- // ^ Modify this number above to change the length of time the round is delayed for
-
-
-
- // --------- DON'T MODIFY ANYTHING BELOW THIS LINE -------- //
-
-
- const styleElement = document.createElement("style");
- document.head.appendChild(styleElement);
- styleElement.innerHTML = `
- .toggle_toggle__ {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- background: var(--ds-color-white-10);
- border: 0;
- border-radius: 2rem;
- cursor: pointer;
- display: inline-block;
- height: 1.25rem;
- position: relative;
- transition: background-color .2s ease;
- width: 2.5rem
- }
-
- .toggle_toggle__:checked {
- background: var(--ds-color-purple-50)
- }
-
- .toggle_toggle__:after {
- background: var(--ds-color-white);
- border-radius: 100%;
- content: "";
- height: 1rem;
- left: 0;
- margin: .125rem;
- opacity: .1;
- position: absolute;
- top: 0;
- transition: transform .1s ease,opacity .1s ease;
- width: 1rem
- }
-
- .toggle_toggle__:checked:after {
- opacity: 1;
- transform: translateX(125%)
- }
-
- .toggle_toggle__:before {
- background: var(--ds-color-purple-50);
- content: "";
- height: 100%;
- left: 0;
- opacity: 0;
- position: absolute;
- top: 0;
- transition: opacity .3s ease;
- width: 100%
- }`;
-
- const classicGameGuiClasses = ["map-stats_mapStat__", "map-selector_selector__"];
- const classicGameGuiHTML = () => `
- <div style="width: 30rem; max-width: 80vw; text-align: center; margin: 0 auto;">
- <h2 style="margin-bottom: 20px; font-style: italic;">Blink Mode settings</h2>
- <div>
- <div style="display: flex; justify-content: space-around;">
- <div style="display: flex; align-items: center;">
- <span style="margin: 0; padding-right: 6px;">Enabled</span>
- <input type="checkbox" id="enableScript" onclick="toggleBlinkMode(this)" class="toggle_toggle__">
- </div>
-
- <div style="display: flex; align-items: center;">
- <span style="margin: 0; padding-right: 6px;">Time (Seconds)</span>
- <input type="text" id="blinkTime" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
- </div>
- <div style="margin-top: 10px">
- <span style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
- <input type="text" id="delayTime" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
- </div>
- </div>
- `
-
- const friendLobbyGuiClasses = ["section_sectionHeader__", "bars_root__", "toggle-option_wrapper__"];
- const friendLobbyGuiHTML = () => `
- <div class="${cn("settings-modal_settingsSection__")}">
- <div class="${cn("section_sectionHeader__")} ${cn("section_sizeMedium__")} ${cn("section_variantLight__")}">
- <div class="${cn("bars_root__")}">
- <span class="${cn("bars_content__")}"><h2>Blink Mode Settings</h2></span>
- <div class="${cn("bars_after__")}"></div>
- </div>
- </div>
- <div class="${cn("settings-modal_section__")}" style="margin-top: 8px">
- <div class="${cn("toggle-option_wrapper__")}">
- <div class="${cn("toggle-option_label__")}">Enabled</div>
- <input type="checkbox" id="enableScript" onclick="toggleBlinkMode(this)" class="toggle_toggle__">
- </div>
-
- <div class="${cn("numeric-option_wrapper__")}">
- <div class="${cn("numeric-option_label__")}">Time (Seconds)</div>
- <input type="text" id="blinkTime" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
-
- <div class="${cn("numeric-option_wrapper__")}">
- <div class="${cn("numeric-option_label__")}">Round Delay (Seconds)</div>
- <input type="text" id="delayTime" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
- </div>
- </div>
- `
-
- const guiHeaderClasses = ["menu-item_container__", "quick-search_wrapper__"];
- const guiHTMLHeader = () => `
- <div id="blinkHeaderToggle" class="${cn("menu-item_container__")}">
- <div class="${cn("quick-search_wrapper__")}">
- <div class="${cn("slanted-wrapper_root__")} ${cn("slanted-wrapper_variantGrayTransparent__")}">
- <div class="${cn("quick-search_searchInputWrapper__")}">
- <div id="popup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; width: 200px; border-radius: 10px;">
- <div style="display: flex; justify-content: space-between; align-items: center;">
- <span>Enabled</span>
- <input type="checkbox" id="enableScriptHeader" class="toggle_toggle__" onclick="toggleBlinkMode(this)">
- </div>
-
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
- <span>Time (Seconds)</span>
- <input type="text" id="blinkTimeHeader" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
-
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
- <span style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
- <input type="text" id="delayTimeHeader" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
- </div>
- <button style="width: 59.19px" id="headerGuiToggle" class="${cn("quick-search_searchInputButton__")}"><picture style="justify-content: center" class="${cn("quick-search_iconSection__")}"><img src="https://www.svgrepo.com/show/40039/eye.svg" style="width: 15px; filter: brightness(0) invert(1); opacity: 100%;"></picture></button>
- </div>
- </div>
- </div>
- </div>
- `
-
- const guiPartyHeaderClasses = ["header_item__", "slanted-wrapper_root__", "game-options_optionLabel__"];
- const guiPartyHeader = () => `
- <div id="blinkHeaderToggle" class="${cn("header_item__")}" style="margin-right: 1rem;">
- <div id="popup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; width: 200px; border-radius: 10px; z-index: 999;">
- <div style="display: flex; justify-content: space-between; align-items: center;">
- <span class="${cn("game-options_optionLabel__")}">Enabled</span>
- <input type="checkbox" id="enableScriptHeader" class="toggle_toggle__" onclick="toggleBlinkMode(this)">
- </div>
-
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
- <span class="${cn("game-options_optionLabel__")}">Time (Seconds)</span>
- <input type="text" id="blinkTimeHeader" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
-
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
- <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
- <input type="text" id="delayTimeHeader" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
- </div>
- </div>
- <div class="${cn("quick-search_wrapper__")}">
- <div class="${cn("slanted-wrapper_root__")} ${cn("slanted-wrapper_variantGrayTransparent__")}">
- <div class="${cn("slanted-wrapper_start__")} ${cn("slanted-wrapper_right__")}"></div>
- <div>
- <button id="headerGuiToggle" style="width: 59.19px; background-color: inherit;border: initial;cursor: pointer;min-height: 2rem;min-width: 2rem;padding: var(--padding-y) var(--padding-x);"><picture style="justify-content: center" class="${cn("quick-search_iconSection__")}"><img src="https://www.svgrepo.com/show/40039/eye.svg" style="width: 15px; filter: brightness(0) invert(1); opacity: 60%;"></picture></button>
- </div>
- <div class="${cn("slanted-wrapper_end__")} ${cn("slanted-wrapper_right__")}"></div>
- </div>
- </div>
- </div>
- `
-
- if (localStorage.getItem('blinkEnabled') == null) {
- localStorage.setItem('blinkEnabled', 'disabled');
- }
-
- if (!guiEnabled) {
- localStorage.setItem('blinkEnabled', 'enabled');
- }
-
- if (localStorage.getItem('blinkTime') == null || isNaN(localStorage.getItem('blinkTime'))) {
- localStorage.setItem('blinkTime', timeLimit);
- }
- if (localStorage.getItem('delayTime') == null || isNaN(localStorage.getItem('delayTime'))) {
- localStorage.setItem('delayTime', roundDelay);
- }
-
- if (guiEnabled) {
- timeLimit = parseFloat(localStorage.getItem('blinkTime'));
- roundDelay = parseFloat(localStorage.getItem('delayTime'));
- }
-
- window.toggleBlinkMode = (e) => {
- localStorage.setItem('blinkEnabled', e.checked ? 'enabled' : 'disabled');
- if (!e.checked) {
- try { showPanoramaCached(); } catch {}
- }
-
- if (document.querySelector('#enableScript')) {
- document.querySelector('#enableScript').checked = e.checked;
- }
- if (document.querySelector('#enableScriptHeader')) {
- document.querySelector('#enableScriptHeader').checked = e.checked;
- }
- }
-
- window.changeBlinkTime = (e) => {
- if (!isNaN(e.value)) {
- localStorage.setItem('blinkTime', parseFloat(e.value));
- timeLimit = parseFloat(e.value);
-
- if (document.querySelector('#blinkTime')) {
- document.querySelector('#blinkTime').value = e.value;
- }
- if (document.querySelector('#blinkTimeHeader')) {
- document.querySelector('#blinkTimeHeader').value = e.value;
- }
- }
- }
-
- window.changeDelayTime = (e) => {
- if (!isNaN(e.value)) {
- localStorage.setItem('delayTime', parseFloat(e.value));
- roundDelay = parseFloat(e.value);
-
- if (document.querySelector('#delayTime')) {
- document.querySelector('#delayTime').value = e.value;
- }
- if (document.querySelector('#delayTimeHeader')) {
- document.querySelector('#delayTimeHeader').value = e.value;
- }
- }
- }
-
- const insertHeaderGui = (header, gui) => {
- header.insertAdjacentHTML('afterbegin', gui);
- const showButton = document.querySelector('#headerGuiToggle');
- const popup = document.querySelector('#popup');
- popup.style.display = 'none';
-
- document.addEventListener('click', (e) => {
- const target = e.target;
- if (target == popup || popup.contains(target)) return;
- if (target.matches('#headerGuiToggle, #headerGuiToggle *')) {
- e.preventDefault();
-
- popup.style.display = 'block';
- Popper.createPopper(showButton, popup, {
- placement: 'bottom',
- modifiers: [
- {
- name: 'offset',
- options: {
- offset: [0, 10],
- },
- },
- ],
- });
- } else {
- popup.style.display = 'none';
- }
-
- if (document.querySelector('#enableScriptHeader')) {
- if (localStorage.getItem('blinkEnabled') === 'enabled') {
- document.querySelector('#enableScriptHeader').checked = true;
- }
- document.querySelector('#blinkTimeHeader').value = timeLimit;
- document.querySelector('#delayTimeHeader').value = roundDelay;
- }
- });
- }
-
- const checkInsertGui = () => {
- // Play page for classic games
- if (document.querySelector('[class*=map-block_mapStatsContainer__]') && document.querySelector('#enableScript') === null && checkAllStylesFound(classicGameGuiClasses)) {
- document.querySelector('[class*=map-block_mapStatsContainer__]').insertAdjacentHTML('afterend', classicGameGuiHTML());
- if (localStorage.getItem('blinkEnabled') === 'enabled') {
- document.querySelector('#enableScript').checked = true;
- }
- document.querySelector('#blinkTime').value = timeLimit;
- document.querySelector('#delayTime').value = roundDelay;
- }
-
- // Lobby for friends party games
- if (document.querySelector('[class*=party-modal_heading__]') && document.querySelector('#enableScript') === null && checkAllStylesFound(friendLobbyGuiClasses)) {
- const columns = document.querySelectorAll('[class*=settings-modal_column__]');
- columns[columns.length - 1].insertAdjacentHTML('beforeend', friendLobbyGuiHTML());
- if (localStorage.getItem('blinkEnabled') === 'enabled') {
- document.querySelector('#enableScript').checked = true;
- }
- document.querySelector('#blinkTime').value = timeLimit;
- document.querySelector('#delayTime').value = roundDelay;
- }
-
- // Header
- if (document.querySelector('[class*=header-tablet-desktop_desktopSectionRight__]') && document.querySelector('#blinkHeaderToggle') === null && checkAllStylesFound(guiHeaderClasses)) {
- insertHeaderGui(document.querySelector('[class*=header-tablet-desktop_desktopSectionRight__]'), guiHTMLHeader())
- } else if (document.querySelector('[class*=party-header_root__]') && document.querySelector('#blinkHeaderToggle') === null && checkAllStylesFound(guiPartyHeaderClasses)) {
- insertHeaderGui(document.querySelector('[class*=party-header_right__]'), guiPartyHeader())
- } else if (document.querySelector('[class*=header-desktop_desktopSectionRight__]') && document.querySelector('#blinkHeaderToggle') === null && checkAllStylesFound(guiHeaderClasses)) {
- insertHeaderGui(document.querySelector('[class*=header-desktop_desktopSectionRight__]'), guiHTMLHeader())
- }
- }
-
- let mapRoot = null;
-
- function getMapRoot() {
- return document.querySelector("[data-qa=panorama]");
- }
-
- function hidePanorama() {
- mapRoot = getMapRoot() || mapRoot;
- hidePanoramaCached();
- }
-
- function hidePanoramaCached() {
- mapRoot.style.filter = 'brightness(0%)';
- }
-
- function showPanorama() {
- mapRoot = getMapRoot() || mapRoot;
- showPanoramaCached();
- }
-
- function showPanoramaCached() {
- mapRoot.style.filter = 'brightness(100%)';
- }
-
- function isLoading() {
- return document.querySelector('[class*=fullscreen-spinner_root__]') || !document.querySelector('.widget-scene-canvas');
- }
-
- let wasBackdropThereOrLoading = false;
- function isBackdropThereOrLoading() {
- return isLoading() // loading
- || document.querySelector('[class*=result-layout_root__]') // classic
- || document.querySelector('[class*=overlay_backdrop__]') // duels / team duels
- || document.querySelector('[class*=round-starting_wrapper____]') // live challenges
- || document.querySelector('[class*=popup_backdrop__]') // BR
- || document.querySelector('[class*=game-starting_container__]') || document.querySelector('[class*=round-score_container__]') // bullseye
- || document.querySelector('[class*=overlay-modal_backlight__]'); // city streaks
- }
-
- let showTimeoutID = null
- let hideTimeoutID = null
- function triggerBlink() {
- hidePanorama();
- clearTimeout(showTimeoutID);
- showTimeoutID = setTimeout(showPanorama, roundDelay * 1000);
- clearTimeout(hideTimeoutID);
- hideTimeoutID = setTimeout(hidePanorama, (timeLimit + roundDelay) * 1000);
- }
-
- let played = false;
-
- const checkStatus = () => {
- if (!(location.pathname.includes('/duels'))) {
- return;
- }
- let timer = document.querySelector("[class^='clock-timer_timerContainer__']");
- let stress = document.querySelector("[class^='stress-indicator_container__']");
- if ((timer !== null) && !played && (stress == null)) {
- showPanorama();
- played = true;
- } else if (timer == null) {
- played = false;
- }
- }
-
- let observer = new MutationObserver((mutations) => {
- if (guiEnabled) {
- scanStyles().then(_ => { checkInsertGui(); });
- }
-
- if (localStorage.getItem('blinkEnabled') === 'enabled') {
- if (isBackdropThereOrLoading()) {
- wasBackdropThereOrLoading = true;
- if (!isLoading()) hidePanorama();
- } else if (wasBackdropThereOrLoading) {
- wasBackdropThereOrLoading = false;
- triggerBlink();
- }
- checkStatus();
- }
-
- });
-
- observer.observe(document.body, {
- characterDataOldValue: false,
- subtree: true,
- childList: true,
- characterData: false
- });