Skip to content

Instantly share code, notes, and snippets.

@ouatu-ro
Last active April 26, 2026 15:20
Show Gist options
  • Select an option

  • Save ouatu-ro/5ca4abca26bd65630de3d4768fe5f69a to your computer and use it in GitHub Desktop.

Select an option

Save ouatu-ro/5ca4abca26bd65630de3d4768fe5f69a to your computer and use it in GitHub Desktop.
Typing Site Redirector (TypeLit Focus Mode)
// ==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