Skip to content

Instantly share code, notes, and snippets.

@alexchexes
Last active June 9, 2025 00:34
Show Gist options
  • Save alexchexes/498c069fa994b7d76d8bcc1f7c144629 to your computer and use it in GitHub Desktop.
Save alexchexes/498c069fa994b7d76d8bcc1f7c144629 to your computer and use it in GitHub Desktop.
Add colors to keymap zones on monkeytype.com
// ==UserScript==
// @name Monkeytype Keymap Colors
// @namespace http://tampermonkey.net/
// @version 2024-09-29
// @description Adds colors to keymap zones on monkeytype.com
// @author alexchexes
// @updateURL https://gist.github.com/alexchexes/498c069fa994b7d76d8bcc1f7c144629/raw/monkeytype_keymap_colors.user.js
// @downloadURL https://gist.github.com/alexchexes/498c069fa994b7d76d8bcc1f7c144629/raw/monkeytype_keymap_colors.user.js
// @match https://monkeytype.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=monkeytype.com
// @grant none
// ==/UserScript==
/*----------------------------------------*
I wonder why the most popular touch-typing tool doesn't have any kind of keymap color guides, which are a must-have for beginners or near-beginners.
I've created a simple userscript for myself that generates CSS to color and highlight the keymap in one of three styles of your choice.
I’d love to see this implemented as a standard feature.
A couple of notes:
1. The color choices are deliberate. Although other people might be used to differently colored keys in other tools (to address this, it would be useful to add the ability to customize colors in the settings), the current selection works well as mnemonic aids:
- Pink (lavender) for the pinky finger. This works for English speakers, but for others, pink may also stereotypically represent a "girly" color and might be associated with cute, small things. Stupid as it sounds, this mnemonic could work well.
- Gold (orange/yellow) for the ring finger, since rings are often gold. Works at least for Western culture.
- Red for the middle finger because you probably shouldn’t show your middle finger to people.
- The left and right index fingers are the brightest (lime and cyan, or green and blue), as these are the most convenient fingers to use. For many, green and blue are the most appealing colors, and they follow the order of RGB.
2. My solution is based on this Stylus style: https://userstyles.world/style/17547/monkeytype-keymap-color
If my version is used, half the credit should go to the author of that style, as they saved me the time I would have spent matching class names with fingers.
*----------------------------------------*/
/*---------------------------------------*
* CHANGE THE MODE HERE *
*----------------------------------------*/
const MODE = 'background';
// const MODE = 'outline';
// const MODE = 'combined';
(function() {
window.addEventListener('load', function() {
const colors = {
pinky: '#b460ff',
ring: '#ffc107',
middle: '#ff387c',
Lindex: '#0f0',
Rindex: '#00abff',
special: '#ffffffc2', // space
};
const selectors = {
pinky: [
/* Left pinky */
`[data-key='\`~']`,
`[data-key='1!']`,
`[data-key='qQ']`,
`[data-key='aA']`,
`[data-key='zZ']`,
/* Right pinky */
`[data-key='0)']`,
`[data-key='-_']`,
`[data-key='=+']`,
`[data-key='pP']`,
`[data-key='[{']`,
`[data-key=']}']`,
`[data-key="'\\""]`,
`[data-key="/?"]`,
`[data-key=";:"]`,
/* Left pinky RUSSIAN */
`[data-key='ёЁ']`,
`[data-key='1!']`,
`[data-key='йЙ']`,
`[data-key='фФ']`,
`[data-key='яЯ']`,
/* Right pinky RUSSIAN */
`[data-key='0)']`,
`[data-key='-_']`,
`[data-key='=+']`,
`[data-key="зЗ"]`,
`[data-key="хХ"]`,
`[data-key="ъЪ"]`,
`[data-key="жЖ"]`,
`[data-key="эЭ"]`,
`[data-key=".,"]`,
],
ring: [
/* Left ring finger */
`[data-key='2@']`,
`[data-key='wW']`,
`[data-key='sS']`,
`[data-key='xX']`,
/* Right ring finger */
`[data-key='9(']`,
`[data-key='oO']`,
`[data-key='lL']`,
`[data-key='.>']`,
/* Left ring finger RUSSIAN */
`[data-key="2\\""]`,
`[data-key="цЦ"]`,
`[data-key="ыЫ"]`,
`[data-key="чЧ"]`,
/* Right ring finger RUSSIAN */
`[data-key='9(']`,
`[data-key="щЩ"]`,
`[data-key="дД"]`,
`[data-key="юЮ"]`,
],
middle: [
/* Left middle finger */
`[data-key='3#']`,
`[data-key='eE']`,
`[data-key='dD']`,
`[data-key='cC']`,
/* Right midle finger */
`[data-key='8*']`,
`[data-key='iI']`,
`[data-key='kK']`,
`[data-key=',<']`,
/* Left middle finger RUSSIAN */
`[data-key="3№"]`,
`[data-key="уУ"]`,
`[data-key="вВ"]`,
`[data-key="сС"]`,
/* Right midle finger RUSSIAN */
`[data-key='8*']`,
`[data-key="шШ"]`,
`[data-key="лЛ"]`,
`[data-key="бБ"]`,
],
Lindex: [
/* Left index */
`[data-key='4$']`,
`[data-key='5%']`,
`[data-key='rR']`,
`[data-key='tT']`,
`[data-key='fF']`,
`[data-key='gG']`,
`[data-key='vV']`,
`[data-key='bB']`,
/* Left index RUSSIAN */
`[data-key="4;"]`,
`[data-key="5%"]`,
`[data-key='кК']`,
`[data-key='еЕ']`,
`[data-key="аА"]`,
`[data-key="пП"]`,
`[data-key="мМ"]`,
`[data-key="иИ"]`,
],
Rindex: [
/* Right index */
`[data-key='7&']`,
`[data-key='6^']`,
`[data-key='uU']`,
`[data-key='yY']`,
`[data-key='jJ']`,
`[data-key='hH']`,
`[data-key='mM']`,
`[data-key='nN']`,
/* Right index RUSSIAN */
`[data-key="6:"]`,
`[data-key="7?"]`,
`[data-key="нН"]`,
`[data-key="гГ"]`,
`[data-key="рР"]`,
`[data-key="оО"]`,
`[data-key="тТ"]`,
`[data-key="ьЬ"]`,
],
};
let css = ``;
for (const finger in selectors) {
if (Object.prototype.hasOwnProperty.call(selectors, finger)) {
const fingerSelectors = selectors[finger];
css += fingerSelectors.join(',\n');
css += ` {
color: ${colors[finger]} !important;
border-color: ${colors[finger]} !important;
`;
if (MODE === 'combined') {
css += `border: 1px solid !important`;
}
css += `}\n`;
if (MODE !== 'outline') {
css += fingerSelectors.join('.activeKey,\n');
css += `.activeKey {
background-color: ${colors[finger]} !important;
}\n`;
}
}
}
if (MODE === 'outline') {
css += `
.activeKey {
background-color: #00000030 !important;
border: 2px solid !important;
}
.keymapKey.keySpace {
border-color: ${colors.special} !important;
}
`;
} else {
// MODE === 'background'
css += `
.activeKey {
color: #000000c4 !important;
}
.keymapKey.keySpace.activeKey {
background-color: ${colors.special} !important;
}
`;
}
// reduce blur when unfocused
css += `
#words.blurred {
opacity: .6;
filter: blur(1px);
-webkit-filter: blur(1px);
}
`;
// normalize other colors so that letters visible on any theme
css += `
#keymap .keymapKey {
background-color: #00000030;
}
`;
const styleSheet = document.createElement('style');
styleSheet.classList.add('_us_styles');
styleSheet.innerHTML = css;
document.head.appendChild(styleSheet);
});
})();
@VanillaEssence
Copy link

VanillaEssence commented Jun 9, 2025

Thank you for making this. Not sure if I just did something wrong but this script did not work for me. I asked Claude to try to fix and it gave me this output that worked. Sharing here in case someone else has the same issue.

Sorry I cant quite figure out how to stop the formatting from breaking but copying all this should work:

// ==UserScript==
// @name Monkeytype Keymap Colors (Revised)
// @namespace http://tampermonkey.net/
// @Version 2025-06-09
// @description Adds colors to keymap zones on monkeytype.com with improved reliability
// @author alexchexes (revised)
// @match https://monkeytype.com/*
// @match https://www.monkeytype.com/*
// @ICON https://www.google.com/s2/favicons?sz=64&domain=monkeytype.com
// @grant none
// @run-at document-start
// ==/UserScript==

/----------------------------------------
Revised version with better reliability:

  • Uses MutationObserver for dynamic content
  • Multiple fallback selectors
  • Better CSS specificity
  • Improved timing handling
  • Debug logging (can be disabled)
    ----------------------------------------/

/---------------------------------------

  •      CONFIGURATION OPTIONS         *
    

----------------------------------------/
const CONFIG = {
MODE: 'background', // 'background', 'outline', or 'combined'
DEBUG: true, // Set to false to disable console logging
RETRY_DELAY: 1000, // ms to wait before retrying
MAX_RETRIES: 10
};

(function() {
'use strict';

let retryCount = 0;
let observer;
let styleElement;

// Debug logging function
function log(...args) {
    if (CONFIG.DEBUG) {
        console.log('[Monkeytype Keymap Colors]', ...args);
    }
}

// Color scheme for different fingers
const colors = {
    pinky: '#b460ff',
    ring: '#ffc107',
    middle: '#ff387c',
    Lindex: '#0f0',
    Rindex: '#00abff',
    special: '#ffffffc2', // space and special keys
};

// Enhanced selectors with multiple fallbacks
const selectors = {
    pinky: [
        // Left pinky - English
        `[data-key='\`~']`, `[data-key='1!']`, `[data-key='qQ']`, `[data-key='aA']`, `[data-key='zZ']`,
        `[data-key='Backquote']`, `[data-key='Digit1']`, `[data-key='KeyQ']`, `[data-key='KeyA']`, `[data-key='KeyZ']`,
        // Right pinky - English
        `[data-key='0)']`, `[data-key='-_']`, `[data-key='=+']`, `[data-key='pP']`, `[data-key='[{']`, 
        `[data-key=']}']`, `[data-key="'\\""]`, `[data-key="/?"]`, `[data-key=";:"]`,
        `[data-key='Digit0']`, `[data-key='Minus']`, `[data-key='Equal']`, `[data-key='KeyP']`, 
        `[data-key='BracketLeft']`, `[data-key='BracketRight']`, `[data-key='Quote']`, `[data-key='Slash']`, `[data-key='Semicolon']`,
        // Russian layout
        `[data-key='ёЁ']`, `[data-key='йЙ']`, `[data-key='фФ']`, `[data-key='яЯ']`,
        `[data-key="зЗ"]`, `[data-key="хХ"]`, `[data-key="ъЪ"]`, `[data-key="жЖ"]`, `[data-key="эЭ"]`, `[data-key=".,"]`,
    ],
    ring: [
        // Left ring - English
        `[data-key='2@']`, `[data-key='wW']`, `[data-key='sS']`, `[data-key='xX']`,
        `[data-key='Digit2']`, `[data-key='KeyW']`, `[data-key='KeyS']`, `[data-key='KeyX']`,
        // Right ring - English
        `[data-key='9(']`, `[data-key='oO']`, `[data-key='lL']`, `[data-key='.>']`,
        `[data-key='Digit9']`, `[data-key='KeyO']`, `[data-key='KeyL']`, `[data-key='Period']`,
        // Russian layout
        `[data-key="2\\""]`, `[data-key="цЦ"]`, `[data-key="ыЫ"]`, `[data-key="чЧ"]`,
        `[data-key="щЩ"]`, `[data-key="дД"]`, `[data-key="юЮ"]`,
    ],
    middle: [
        // Left middle - English
        `[data-key='3#']`, `[data-key='eE']`, `[data-key='dD']`, `[data-key='cC']`,
        `[data-key='Digit3']`, `[data-key='KeyE']`, `[data-key='KeyD']`, `[data-key='KeyC']`,
        // Right middle - English
        `[data-key='8*']`, `[data-key='iI']`, `[data-key='kK']`, `[data-key=',<']`,
        `[data-key='Digit8']`, `[data-key='KeyI']`, `[data-key='KeyK']`, `[data-key='Comma']`,
        // Russian layout
        `[data-key="3№"]`, `[data-key="уУ"]`, `[data-key="вВ"]`, `[data-key="сС"]`,
        `[data-key="шШ"]`, `[data-key="лЛ"]`, `[data-key="бБ"]`,
    ],
    Lindex: [
        // Left index - English
        `[data-key='4$']`, `[data-key='5%']`, `[data-key='rR']`, `[data-key='tT']`, 
        `[data-key='fF']`, `[data-key='gG']`, `[data-key='vV']`, `[data-key='bB']`,
        `[data-key='Digit4']`, `[data-key='Digit5']`, `[data-key='KeyR']`, `[data-key='KeyT']`,
        `[data-key='KeyF']`, `[data-key='KeyG']`, `[data-key='KeyV']`, `[data-key='KeyB']`,
        // Russian layout
        `[data-key="4;"]`, `[data-key="5%"]`, `[data-key='кК']`, `[data-key='еЕ']`,
        `[data-key="аА"]`, `[data-key="пП"]`, `[data-key="мМ"]`, `[data-key="иИ"]`,
    ],
    Rindex: [
        // Right index - English
        `[data-key='6^']`, `[data-key='7&']`, `[data-key='yY']`, `[data-key='uU']`, 
        `[data-key='hH']`, `[data-key='jJ']`, `[data-key='nN']`, `[data-key='mM']`,
        `[data-key='Digit6']`, `[data-key='Digit7']`, `[data-key='KeyY']`, `[data-key='KeyU']`,
        `[data-key='KeyH']`, `[data-key='KeyJ']`, `[data-key='KeyN']`, `[data-key='KeyM']`,
        // Russian layout
        `[data-key="6:"]`, `[data-key="7?"]`, `[data-key="нН"]`, `[data-key="гГ"]`,
        `[data-key="рР"]`, `[data-key="оО"]`, `[data-key="тТ"]`, `[data-key="ьЬ"]`,
    ],
};

function generateCSS() {
    let css = `
        /* Monkeytype Keymap Colors - Generated CSS */
        /* Base keymap styling */
        #keymap .keymapKey,
        .keymap .keymapKey,
        [class*="keymap"] [class*="key"],
        .keyboardKey {
            background-color: #00000030 !important;
            transition: all 0.1s ease !important;
        }
    `;

    // Generate finger-specific colors
    for (const finger in selectors) {
        if (Object.prototype.hasOwnProperty.call(selectors, finger)) {
            const fingerSelectors = selectors[finger];
            const selectorString = fingerSelectors.join(', ');
            
            css += `
                /* ${finger.toUpperCase()} finger colors */
                ${selectorString} {
                    color: ${colors[finger]} !important;
                    border-color: ${colors[finger]} !important;
            `;

            if (CONFIG.MODE === 'combined') {
                css += `border: 1px solid !important;`;
            }

            css += `}\n`;

            // Active key styling
            if (CONFIG.MODE !== 'outline') {
                const activeSelectors = fingerSelectors.map(sel => sel + '.activeKey').join(', ');
                css += `
                    ${activeSelectors} {
                        background-color: ${colors[finger]} !important;
                    }
                `;
            }
        }
    }

    // Mode-specific styling
    if (CONFIG.MODE === 'outline') {
        css += `
            .activeKey,
            [class*="active"] {
                background-color: #00000030 !important;
                border: 2px solid !important;
            }
            
            .keymapKey.keySpace,
            [data-key=" "],
            [data-key="Space"] {
                border-color: ${colors.special} !important;
            }
        `;
    } else {
        css += `
            .activeKey,
            [class*="active"] {
                color: #000000c4 !important;
            }
            
            .keymapKey.keySpace.activeKey,
            [data-key=" "].activeKey,
            [data-key="Space"].activeKey {
                background-color: ${colors.special} !important;
            }
        `;
    }

    // Additional improvements
    css += `
        /* Reduce blur when unfocused */
        #words.blurred {
            opacity: .6 !important;
            filter: blur(1px) !important;
            -webkit-filter: blur(1px) !important;
        }
        
        /* Ensure visibility on all themes */
        #keymap .keymapKey:hover,
        .keymap .keymapKey:hover {
            transform: scale(1.05) !important;
        }
    `;

    return css;
}

function injectCSS() {
    // Remove existing styles
    if (styleElement) {
        styleElement.remove();
    }

    styleElement = document.createElement('style');
    styleElement.classList.add('_monkeytype_keymap_colors');
    styleElement.innerHTML = generateCSS();
    
    const head = document.head || document.getElementsByTagName('head')[0];
    head.appendChild(styleElement);
    
    log('CSS injected successfully');
    return true;
}

function checkForKeymap() {
    // Multiple selectors to find keymap
    const keymapSelectors = [
        '#keymap',
        '.keymap',
        '[class*="keymap"]',
        '[data-key]'
    ];

    let keymapFound = false;
    let keyCount = 0;

    for (const selector of keymapSelectors) {
        const elements = document.querySelectorAll(selector);
        if (elements.length > 0) {
            keymapFound = true;
            keyCount += elements.length;
        }
    }

    // Also check for specific key elements
    const keys = document.querySelectorAll('[data-key], .keymapKey, [class*="key"]');
    keyCount += keys.length;

    log(`Keymap check: found=${keymapFound}, keyCount=${keyCount}`);
    
    return keymapFound && keyCount > 10; // Reasonable threshold
}

function initialize() {
    log('Initializing...');
    
    if (checkForKeymap()) {
        log('Keymap found, injecting CSS');
        injectCSS();
        
        // Stop observing
        if (observer) {
            observer.disconnect();
            observer = null;
        }
        
        return true;
    }
    
    return false;
}

function setupObserver() {
    if (observer) {
        observer.disconnect();
    }

    observer = new MutationObserver((mutations) => {
        let shouldCheck = false;
        
        for (const mutation of mutations) {
            // Check if keymap-related nodes were added
            if (mutation.type === 'childList') {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.id === 'keymap' || 
                            node.className?.includes('keymap') ||
                            node.querySelector && node.querySelector('[data-key]')) {
                            shouldCheck = true;
                            break;
                        }
                    }
                }
            }
        }
        
        if (shouldCheck && initialize()) {
            log('Successfully initialized via observer');
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: false
    });
    
    log('MutationObserver setup complete');
}

function retryInitialization() {
    if (retryCount >= CONFIG.MAX_RETRIES) {
        log('Max retries reached, giving up. Make sure keymap is enabled in Monkeytype settings!');
        return;
    }

    retryCount++;
    log(`Retry attempt ${retryCount}/${CONFIG.MAX_RETRIES}`);
    
    if (!initialize()) {
        setTimeout(retryInitialization, CONFIG.RETRY_DELAY);
    }
}

// Main execution
function main() {
    log('Script starting...');
    
    // Try immediate initialization
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            if (!initialize()) {
                setupObserver();
                retryInitialization();
            }
        });
    } else {
        if (!initialize()) {
            setupObserver();
            retryInitialization();
        }
    }

    // Also try after page load as fallback
    window.addEventListener('load', () => {
        setTimeout(() => {
            if (!styleElement || !document.querySelector('._monkeytype_keymap_colors')) {
                log('Fallback initialization on window load');
                initialize();
            }
        }, 1000);
    });
}

// Start the script
main();

})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment