Last active
April 26, 2026 15:20
-
-
Save ouatu-ro/5ca4abca26bd65630de3d4768fe5f69a to your computer and use it in GitHub Desktop.
Typing Site Redirector (TypeLit Focus Mode)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Typing Site Redirector (TypeLit Focus Mode) | |
| // @namespace gobdovan | |
| // @version 2.1 | |
| // @description Saves your last non-root TypeLit page and redirects you there if you open time-wasting typing sites | |
| // @match *://*.typeracer.com/* | |
| // @match *://typeracer.com/* | |
| // @match *://*.monkeytype.com/* | |
| // @match *://monkeytype.com/* | |
| // @match *://*.10fastfingers.com/* | |
| // @match *://10fastfingers.com/* | |
| // @match *://*.typingtest.com/* | |
| // @match *://typingtest.com/* | |
| // @match *://*.how-to-type.com/* | |
| // @match *://how-to-type.com/* | |
| // @match *://*.keybr.com/* | |
| // @match *://keybr.com/* | |
| // @match *://typelit.io/* | |
| // @match *://www.typelit.io/* | |
| // @run-at document-start | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const DEFAULT_TYPELIT_URL = 'https://www.typelit.io/'; | |
| const TYPELIT_ROOT_PATHS = new Set(['/']); | |
| const isTypeLit = /(^|\.)typelit\.io$/.test(location.hostname); | |
| const isTypeLitRoot = isTypeLit && TYPELIT_ROOT_PATHS.has(location.pathname); | |
| const REDIRECT_MODE = true; // if false -> block page | |
| function getNormalizedUrl() { | |
| return `${location.origin}${location.pathname}${location.search}${location.hash}`; | |
| } | |
| function isRootUrl(url) { | |
| try { | |
| const parsed = new URL(url); | |
| return /(^|\.)typelit\.io$/.test(parsed.hostname) && parsed.pathname === '/'; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| async function getLastReadingUrl() { | |
| const lastUrl = await GM_getValue('last_typelit_url', ''); | |
| if (!lastUrl || isRootUrl(lastUrl)) { | |
| return DEFAULT_TYPELIT_URL; | |
| } | |
| return lastUrl; | |
| } | |
| async function saveTypeLitPage() { | |
| if (isTypeLitRoot) { | |
| console.debug('[FocusMode] Skipping TypeLit root page:', getNormalizedUrl()); | |
| return; | |
| } | |
| const currentUrl = getNormalizedUrl(); | |
| await GM_setValue('last_typelit_url', currentUrl); | |
| console.debug('[FocusMode] Saved last TypeLit URL:', currentUrl); | |
| } | |
| function injectResumeButton() { | |
| if (!isTypeLitRoot) { | |
| return; | |
| } | |
| const buttonId = 'typelit-focus-mode-resume-button'; | |
| if (document.getElementById(buttonId)) { | |
| return; | |
| } | |
| const button = document.createElement('button'); | |
| button.id = buttonId; | |
| button.type = 'button'; | |
| button.textContent = 'Resume reading'; | |
| Object.assign(button.style, { | |
| position: 'fixed', | |
| right: '20px', | |
| bottom: '20px', | |
| zIndex: '2147483647', | |
| border: '0', | |
| borderRadius: '999px', | |
| padding: '16px 24px', | |
| minWidth: '180px', | |
| background: '#111', | |
| color: '#fff', | |
| fontFamily: 'system-ui, sans-serif', | |
| fontSize: '16px', | |
| fontWeight: '600', | |
| boxShadow: '0 10px 30px rgba(0, 0, 0, 0.30)', | |
| cursor: 'pointer', | |
| letterSpacing: '0.01em', | |
| transformOrigin: 'center', | |
| }); | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| @keyframes typelitFocusPulse { | |
| 0% { transform: scale(1); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.30); opacity: 1; } | |
| 35% { transform: scale(1.03); box-shadow: 0 10px 34px rgba(255, 255, 255, 0.16), 0 10px 34px rgba(0, 0, 0, 0.24); opacity: 0.9; } | |
| 70% { transform: scale(1); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.30); opacity: 1; } | |
| 100% { transform: scale(1.03); box-shadow: 0 10px 34px rgba(255, 255, 255, 0.16), 0 10px 34px rgba(0, 0, 0, 0.24); opacity: 0.92; } | |
| } | |
| #${buttonId}.typelit-focus-pulse { | |
| animation: typelitFocusPulse 760ms ease-in-out 2; | |
| } | |
| `; | |
| button.addEventListener('click', async () => { | |
| const targetUrl = await getLastReadingUrl(); | |
| location.href = targetUrl; | |
| }); | |
| const mountButton = () => { | |
| if (document.body) { | |
| if (!document.getElementById('typelit-focus-mode-styles')) { | |
| style.id = 'typelit-focus-mode-styles'; | |
| document.head.appendChild(style); | |
| } | |
| document.body.appendChild(button); | |
| requestAnimationFrame(() => { | |
| button.classList.add('typelit-focus-pulse'); | |
| button.addEventListener('animationend', () => { | |
| button.classList.remove('typelit-focus-pulse'); | |
| }, { once: true }); | |
| }); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (!mountButton()) { | |
| const observer = new MutationObserver(() => { | |
| if (mountButton()) { | |
| observer.disconnect(); | |
| } | |
| }); | |
| observer.observe(document.documentElement, { childList: true, subtree: true }); | |
| } | |
| } | |
| async function redirectOrBlock() { | |
| const lastUrl = await getLastReadingUrl(); | |
| if (REDIRECT_MODE) { | |
| console.debug('[FocusMode] Redirecting to', lastUrl); | |
| location.replace(lastUrl); | |
| } else { | |
| window.stop(); | |
| document.documentElement.innerHTML = ` | |
| <head><title>Blocked!</title></head> | |
| <body style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;background:#111;color:#eee;text-align:center;"> | |
| <div> | |
| <h1>🚫 Site Blocked</h1> | |
| <p>You decided to stop wasting time on typing sites.</p> | |
| <p>Close this tab and do something useful 👊</p> | |
| </div> | |
| </body> | |
| `; | |
| } | |
| } | |
| function watchTypeLitNavigation() { | |
| let lastUrl = getNormalizedUrl(); | |
| saveTypeLitPage(); | |
| injectResumeButton(); | |
| const observer = new MutationObserver(() => { | |
| const nextUrl = getNormalizedUrl(); | |
| if (nextUrl !== lastUrl) { | |
| lastUrl = nextUrl; | |
| saveTypeLitPage(); | |
| } | |
| }); | |
| observer.observe(document.documentElement, { childList: true, subtree: true }); | |
| } | |
| if (isTypeLit) { | |
| document.addEventListener('DOMContentLoaded', watchTypeLitNavigation); | |
| } else { | |
| redirectOrBlock(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment