Useless Things Series: Mouse and Keyboard Event Counters

Advanced tracking with WPM, scroll distance, idle time, double-clicks, combos, copy/paste/cut, backspace/delete, arrow keys, enter, undo/redo, export, import, keyboard shortcuts, tab sync, and minimalist floating button UI.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Useless Things Series: Mouse and Keyboard Event Counters
// @version      4.1
// @description  Advanced tracking with WPM, scroll distance, idle time, double-clicks, combos, copy/paste/cut, backspace/delete, arrow keys, enter, undo/redo, export, import, keyboard shortcuts, tab sync, and minimalist floating button UI.
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @namespace https://greasyfork.org/users/1126616
// ==/UserScript==

(function() {
    'use strict';
    
    const sessionData = {
        mouseInches: 0,
        scrollCount: 0,
        scrollPixels: 0,
        letterCount: 0,
        leftClickCount: 0,
        rightClickCount: 0,
        middleClickCount: 0,
        doubleClickCount: 0,
        keyPressCount: {},
        combosPressed: {},
        sessionStart: Date.now(),
        mouseSpeed: 0,
        maxMouseSpeed: 0,
        mouseAcceleration: 0,
        maxAcceleration: 0,
        idleTime: 0,
        lastActivityTime: Date.now(),
        totalIdleTime: 0,
        typingStartTime: null,
        wordsTyped: 0,
        autoHideDelay: 3000,
        copyCount: 0,
        pasteCount: 0,
        cutCount: 0,
        backspaceCount: 0,
        deleteCount: 0,
        arrowUpCount: 0,
        arrowDownCount: 0,
        arrowLeftCount: 0,
        arrowRightCount: 0,
        enterCount: 0,
        undoCount: 0,
        redoCount: 0
    };

    const loadSavedData = () => {
        try {
            const saved = localStorage.getItem('eventCounters');
            if (saved) {
                const data = JSON.parse(saved);
                Object.assign(sessionData, data);
            }
        } catch (e) {
            console.warn('Could not load saved data:', e);
        }
    };

    const saveData = () => {
        try {
            localStorage.setItem('eventCounters', JSON.stringify(sessionData));
            broadcastToTabs('sync', sessionData);
        } catch (e) {
            console.warn('Could not save data:', e);
        }
    };

    const broadcastToTabs = (type, data) => {
        try {
            localStorage.setItem('eventCounters_broadcast', JSON.stringify({
                type,
                data,
                timestamp: Date.now()
            }));
        } catch (e) {
            console.warn('Could not broadcast:', e);
        }
    };

    window.addEventListener('storage', (e) => {
        if (e.key === 'eventCounters_broadcast') {
            try {
                const broadcast = JSON.parse(e.newValue);
                if (broadcast.type === 'sync') {
                    Object.assign(sessionData, broadcast.data);
                    updateCounters();
                }
            } catch (err) {
                console.warn('Error handling broadcast:', err);
            }
        }
    });

    const exportData = (format) => {
        const data = {
            ...sessionData,
            exportDate: new Date().toISOString(),
            version: '3.0'
        };
        
        let content, filename, mimeType;
        
        if (format === 'json') {
            content = JSON.stringify(data, null, 2);
            filename = `event-counters-${Date.now()}.json`;
            mimeType = 'application/json';
        } else if (format === 'csv') {
            const rows = [
                ['Metric', 'Value'],
                ['Mouse Distance (inches)', sessionData.mouseInches.toFixed(2)],
                ['Mouse Speed (in/s)', sessionData.mouseSpeed.toFixed(2)],
                ['Max Mouse Speed (in/s)', sessionData.maxMouseSpeed.toFixed(2)],
                ['Mouse Acceleration', sessionData.mouseAcceleration.toFixed(2)],
                ['Max Acceleration', sessionData.maxAcceleration.toFixed(2)],
                ['Scroll Events', sessionData.scrollCount],
                ['Scroll Distance (px)', sessionData.scrollPixels],
                ['Keys Pressed', sessionData.letterCount],
                ['Left Clicks', sessionData.leftClickCount],
                ['Right Clicks', sessionData.rightClickCount],
                ['Middle Clicks', sessionData.middleClickCount],
                ['Double Clicks', sessionData.doubleClickCount],
                ['Words Typed', sessionData.wordsTyped],
                ['WPM', calculateWPM().toFixed(1)],
                ['Copy Actions', sessionData.copyCount],
                ['Paste Actions', sessionData.pasteCount],
                ['Cut Actions', sessionData.cutCount],
                ['Backspaces', sessionData.backspaceCount],
                ['Deletes', sessionData.deleteCount],
                ['Arrow Up', sessionData.arrowUpCount],
                ['Arrow Down', sessionData.arrowDownCount],
                ['Arrow Left', sessionData.arrowLeftCount],
                ['Arrow Right', sessionData.arrowRightCount],
                ['Enter Key', sessionData.enterCount],
                ['Undo (Ctrl+Z)', sessionData.undoCount],
                ['Redo (Ctrl+Y)', sessionData.redoCount],
                ['Total Idle Time (s)', Math.floor(sessionData.totalIdleTime / 1000)],
                ['Session Duration', formatTime(Date.now() - sessionData.sessionStart)]
            ];
            content = rows.map(row => row.join(',')).join('\n');
            filename = `event-counters-${Date.now()}.csv`;
            mimeType = 'text/csv';
        }
        
        const blob = new Blob([content], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
    };

    const importData = () => {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json,.csv';
        input.onchange = (e) => {
            const file = e.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = (event) => {
                    try {
                        const imported = JSON.parse(event.target.result);
                        if (confirm(`Import data from "${file.name}"?\n\nCurrent data will be replaced.`)) {
                            Object.assign(sessionData, imported);
                            saveData();
                            updateCounters();
                            showNotification('Data imported successfully!', 'success');
                        }
                    } catch (err) {
                        showNotification('Error importing data: ' + err.message, 'error');
                    }
                };
                reader.readAsText(file);
            }
        };
        input.click();
    };

    const showNotification = (message, type = 'info') => {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 8px;
            color: white;
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: bold;
            z-index: 10000000;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            animation: slideIn 0.3s ease-out;
            backdrop-filter: blur(10px);
        `;
        
        if (type === 'success') {
            notification.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
        } else if (type === 'error') {
            notification.style.background = 'linear-gradient(135deg, #f44336, #da190b)';
        } else {
            notification.style.background = 'linear-gradient(135deg, #2196F3, #0b7dda)';
        }
        
        document.body.appendChild(notification);
        
        setTimeout(() => {
            notification.style.animation = 'slideOut 0.3s ease-in';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    };
    
    const createStyle = () => {
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideIn {
                from {
                    transform: translateX(400px);
                    opacity: 0;
                }
                to {
                    transform: translateX(0);
                    opacity: 1;
                }
            }
            @keyframes slideOut {
                from {
                    transform: translateX(0);
                    opacity: 1;
                }
                to {
                    transform: translateX(400px);
                    opacity: 0;
                }
            }
        `;
        document.head.appendChild(style);
    };
    createStyle();

    const calculateWPM = () => {
        const sessionDuration = (Date.now() - sessionData.sessionStart) / 1000 / 60;
        if (sessionDuration === 0) return 0;
        return (sessionData.letterCount / 5) / sessionDuration;
    };

    setInterval(() => {
        const now = Date.now();
        const timeSinceActivity = now - sessionData.lastActivityTime;
        if (timeSinceActivity > 5000) {
            sessionData.totalIdleTime += 1000;
        }
    }, 1000);

    setInterval(saveData, 5000);

    loadSavedData();

    // Create floating button
    const floatingButton = document.createElement('div');
    floatingButton.id = 'event-counters-fab';
    floatingButton.textContent = 'EC';
    floatingButton.title = 'Event Counters (Click to toggle)';
    Object.assign(floatingButton.style, {
        position: 'fixed',
        right: '20px',
        bottom: '20px',
        width: '56px',
        height: '56px',
        borderRadius: '50%',
        backgroundColor: '#4CAF50',
        color: '#fff',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '16px',
        fontWeight: 'bold',
        cursor: 'pointer',
        boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(255, 255, 255, 0.1)',
        zIndex: '999998',
        transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
        userSelect: 'none',
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
    });

    floatingButton.addEventListener('mouseenter', () => {
        floatingButton.style.transform = 'scale(1.1) rotate(5deg)';
        floatingButton.style.boxShadow = '0 6px 20px rgba(76, 175, 80, 0.5), 0 0 0 2px rgba(255, 255, 255, 0.2)';
    });

    floatingButton.addEventListener('mouseleave', () => {
        floatingButton.style.transform = 'scale(1) rotate(0deg)';
        floatingButton.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(255, 255, 255, 0.1)';
    });

    floatingButton.addEventListener('click', () => {
        if (countersDiv.style.display === 'none') {
            countersDiv.style.display = 'block';
            requestAnimationFrame(() => {
                countersDiv.style.opacity = '1';
                countersDiv.style.transform = 'scale(1)';
            });
        } else {
            countersDiv.style.opacity = '0';
            countersDiv.style.transform = 'scale(0.95)';
            setTimeout(() => countersDiv.style.display = 'none', 300);
        }
    });

    // Create panel
    const countersDiv = document.createElement('div');
    countersDiv.id = 'event-counters-panel';
    Object.assign(countersDiv.style, {
        position: 'fixed',
        right: '20px',
        bottom: '86px',
        padding: '18px',
        border: '2px solid #4CAF50',
        borderRadius: '12px',
        transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
        backgroundColor: 'rgba(0, 0, 0, 0.95)',
        color: '#fff',
        userSelect: 'none',
        display: 'none',
        opacity: '0',
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
        fontSize: '14px',
        boxShadow: '0 8px 32px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1) inset',
        backdropFilter: 'blur(16px)',
        zIndex: '999999',
        width: '320px',
        maxHeight: '70vh',
        overflowY: 'auto',
        willChange: 'transform',
        transform: 'scale(0.95)'
    });

    const initUI = () => {
        if (document.body) {
            document.body.appendChild(floatingButton);
            document.body.appendChild(countersDiv);
        } else {
            setTimeout(initUI, 100);
        }
    };
    initUI();

    const header = document.createElement('div');
    header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 2px solid rgba(76, 175, 80, 0.5);';
    
    const title = document.createElement('div');
    title.textContent = 'Event Counters';
    title.style.cssText = 'font-weight: bold; font-size: 18px; background: linear-gradient(135deg, #4CAF50, #8BC34A); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;';
    
    const closeBtn = document.createElement('button');
    closeBtn.textContent = '×';
    closeBtn.title = 'Close Panel';
    closeBtn.style.cssText = `
        background: transparent;
        border: none;
        color: #999;
        font-size: 28px;
        line-height: 1;
        cursor: pointer;
        padding: 0;
        width: 28px;
        height: 28px;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.2s ease;
        border-radius: 4px;
    `;
    closeBtn.addEventListener('mouseenter', () => {
        closeBtn.style.color = '#fff';
        closeBtn.style.background = 'rgba(255, 255, 255, 0.1)';
    });
    closeBtn.addEventListener('mouseleave', () => {
        closeBtn.style.color = '#999';
        closeBtn.style.background = 'transparent';
    });
    closeBtn.addEventListener('click', () => {
        countersDiv.style.opacity = '0';
        countersDiv.style.transform = 'scale(0.95)';
        setTimeout(() => countersDiv.style.display = 'none', 300);
    });
    
    const headerLeft = document.createElement('div');
    headerLeft.appendChild(title);
    
    header.appendChild(headerLeft);
    header.appendChild(closeBtn);
    countersDiv.appendChild(header);
    
    const buttonGroup = document.createElement('div');
    buttonGroup.style.cssText = 'display: flex; gap: 8px; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid rgba(76, 175, 80, 0.3);';
    
    const createButton = (text, title, color, hoverColor) => {
        const btn = document.createElement('button');
        btn.textContent = text;
        btn.title = title;
        btn.style.cssText = `
            background: ${color};
            border: none;
            color: white;
            padding: 8px 14px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 13px;
            font-weight: bold;
            transition: all 0.2s ease;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        `;
        btn.addEventListener('mouseenter', () => {
            btn.style.background = hoverColor;
            btn.style.transform = 'translateY(-2px)';
            btn.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.background = color;
            btn.style.transform = 'translateY(0)';
            btn.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
        });
        return btn;
    };
    
    const exportBtn = createButton('Export', 'Export Data (JSON/CSV)', '#2196F3', '#1976D2');
    exportBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        const format = confirm('Choose export format:\n\nOK = JSON (Full Data)\nCancel = CSV (Summary)') ? 'json' : 'csv';
        exportData(format);
        showNotification('Data exported as ' + format.toUpperCase(), 'success');
    });
    
    const importBtn = createButton('Import', 'Import Data (JSON)', '#FF9800', '#F57C00');
    importBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        importData();
    });
    
    const resetBtn = createButton('Reset', 'Reset All Counters', '#f44336', '#d32f2f');
    resetBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        if (confirm('Reset all counters?\n\nThis will erase all tracked data and cannot be undone!')) {
            Object.keys(sessionData).forEach(key => {
                if (key === 'keyPressCount' || key === 'combosPressed') {
                    sessionData[key] = {};
                } else if (key === 'sessionStart' || key === 'lastActivityTime') {
                    sessionData[key] = Date.now();
                } else if (key === 'autoHideDelay') {
                    sessionData[key] = 3000;
                } else {
                    sessionData[key] = 0;
                }
            });
            saveData();
            updateCounters();
            showNotification('All counters reset!', 'success');
        }
    });
    
    buttonGroup.appendChild(exportBtn);
    buttonGroup.appendChild(importBtn);
    buttonGroup.appendChild(resetBtn);
    countersDiv.appendChild(buttonGroup);

    const mouseCounter = createCounter('Mouse Distance');
    const mouseSpeedCounter = createCounter('Mouse Speed');
    const mouseAccelCounter = createCounter('Mouse Acceleration');
    const scrollCounter = createCounter('Scrolls');
    const scrollDistCounter = createCounter('Scroll Distance');
    const letterCounter = createCounter('Keys Pressed');
    const wpmCounter = createCounter('Typing Speed (WPM)');
    const leftClickCounter = createCounter('Left Clicks');
    const rightClickCounter = createCounter('Right Clicks');
    const middleClickCounter = createCounter('Middle Clicks');
    const doubleClickCounter = createCounter('Double Clicks');
    const idleTimeCounter = createCounter('Idle Time');
    const sessionTimeCounter = createCounter('Session Time');
    const topKeysCounter = createCounter('Top 5 Keys');
    const topCombosCounter = createCounter('Top 5 Combos');
    const copyPasteCutCounter = createCounter('Copy/Paste/Cut');
    const backspaceDeleteCounter = createCounter('Backspace/Delete');
    const arrowKeysCounter = createCounter('Arrow Keys');
    const enterCounter = createCounter('Enter Key');
    const undoRedoCounter = createCounter('Undo/Redo');

    [mouseCounter, mouseSpeedCounter, mouseAccelCounter, scrollCounter, scrollDistCounter,
     letterCounter, wpmCounter, leftClickCounter, rightClickCounter, middleClickCounter,
     doubleClickCounter, idleTimeCounter, sessionTimeCounter, topKeysCounter, topCombosCounter,
     copyPasteCutCounter, backspaceDeleteCounter, arrowKeysCounter, enterCounter, undoRedoCounter].forEach(counter => {
        countersDiv.appendChild(counter);
    });

    let timeout;
    let lastScrollTime = Date.now();
    let lastMouseTime = Date.now();
    let lastMouseSpeed = 0;
    let lastClickTime = 0;
    let keysPressed = new Set();

    document.addEventListener('keydown', (e) => {
        // Keyboard shortcuts
        if (e.ctrlKey && e.shiftKey && e.key.toUpperCase() === 'E') {
            e.preventDefault();
            floatingButton.click();
            return;
        }
        
        if (e.ctrlKey && e.shiftKey && e.key.toUpperCase() === 'D') {
            e.preventDefault();
            const newDelay = prompt(`Current auto-hide delay: ${sessionData.autoHideDelay}ms\n\nEnter new delay (milliseconds):`, sessionData.autoHideDelay);
            if (newDelay && !isNaN(newDelay)) {
                sessionData.autoHideDelay = parseInt(newDelay);
                saveData();
                alert(`Auto-hide delay set to ${sessionData.autoHideDelay}ms`);
            }
            return;
        }

        sessionData.lastActivityTime = Date.now();
        
        if (e.ctrlKey || e.metaKey) {
            if (e.key.toLowerCase() === 'c') {
                sessionData.copyCount++;
            } else if (e.key.toLowerCase() === 'v') {
                sessionData.pasteCount++;
            } else if (e.key.toLowerCase() === 'x') {
                sessionData.cutCount++;
            } else if (e.key.toLowerCase() === 'z') {
                if (e.shiftKey) {
                    sessionData.redoCount++;
                } else {
                    sessionData.undoCount++;
                }
            } else if (e.key.toLowerCase() === 'y') {
                sessionData.redoCount++;
            }
        }
        
        if (e.key === 'Backspace') {
            sessionData.backspaceCount++;
        } else if (e.key === 'Delete') {
            sessionData.deleteCount++;
        }
        
        if (e.key === 'ArrowUp') {
            sessionData.arrowUpCount++;
        } else if (e.key === 'ArrowDown') {
            sessionData.arrowDownCount++;
        } else if (e.key === 'ArrowLeft') {
            sessionData.arrowLeftCount++;
        } else if (e.key === 'ArrowRight') {
            sessionData.arrowRightCount++;
        }
        
        if (e.key === 'Enter') {
            sessionData.enterCount++;
        }

        sessionData.letterCount++;
        
        if (e.key === ' ') {
            sessionData.wordsTyped++;
        }
        
        const key = e.key.length === 1 ? e.key.toUpperCase() : e.key;
        sessionData.keyPressCount[key] = (sessionData.keyPressCount[key] || 0) + 1;
        
        keysPressed.add(e.key);
        const combo = Array.from(keysPressed).sort().join('+');
        if (keysPressed.size > 1) {
            sessionData.combosPressed[combo] = (sessionData.combosPressed[combo] || 0) + 1;
        }

        updateCounters();
    });

    document.addEventListener('mousemove', (e) => {
        sessionData.lastActivityTime = Date.now();
        
        clearTimeout(timeout);

        const currentTime = Date.now();
        const deltaTime = currentTime - lastMouseTime;
        
        const deltaX = e.movementX || 0;
        const deltaY = e.movementY || 0;
        const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        
        sessionData.mouseInches += distance / 96;
        
        if (deltaTime > 0) {
            const speedInchesPerSec = (distance / 96) / (deltaTime / 1000);
            const acceleration = Math.abs(speedInchesPerSec - lastMouseSpeed) / (deltaTime / 1000);
            
            sessionData.mouseSpeed = speedInchesPerSec;
            sessionData.mouseAcceleration = acceleration;
            
            if (speedInchesPerSec > sessionData.maxMouseSpeed) {
                sessionData.maxMouseSpeed = speedInchesPerSec;
            }
            if (acceleration > sessionData.maxAcceleration) {
                sessionData.maxAcceleration = acceleration;
            }
            
            lastMouseSpeed = speedInchesPerSec;
        }
        
        lastMouseTime = currentTime;
        
        updateCounters();
    });

    document.addEventListener('mouseup', () => {
        // Placeholder for future use
    });

    document.addEventListener('wheel', (event) => {
        sessionData.lastActivityTime = Date.now();
        const currentTime = Date.now();
        if (currentTime - lastScrollTime > 100) {
            sessionData.scrollCount++;
            lastScrollTime = currentTime;
        }
        
        sessionData.scrollPixels += Math.abs(event.deltaY);

        updateCounters();
    }, { passive: true });

    document.addEventListener('keyup', (event) => {
        keysPressed.delete(event.key);
    });

    document.addEventListener('mousedown', (event) => {
        sessionData.lastActivityTime = Date.now();
        const currentTime = Date.now();
        
        if (currentTime - lastClickTime < 300 && event.button === 0) {
            sessionData.doubleClickCount++;
        }
        
        if (event.button === 0) {
            sessionData.leftClickCount++;
        } else if (event.button === 1) {
            sessionData.middleClickCount++;
        } else if (event.button === 2) {
            sessionData.rightClickCount++;
        }
        
        lastClickTime = currentTime;

        updateCounters();
    });

    const formatTime = (ms) => {
        const seconds = Math.floor(ms / 1000);
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        
        if (hours > 0) {
            return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
        } else if (minutes > 0) {
            return `${minutes}m ${seconds % 60}s`;
        } else {
            return `${seconds}s`;
        }
    };

    function updateCounters() {
        const sessionDuration = Date.now() - sessionData.sessionStart;
        
        mouseCounter.innerHTML = `Mouse Distance: <strong>${sessionData.mouseInches.toFixed(2)}</strong> in`;
        mouseSpeedCounter.innerHTML = `Speed: <strong>${sessionData.mouseSpeed.toFixed(1)}</strong> in/s (Max: ${sessionData.maxMouseSpeed.toFixed(1)})`;
        mouseAccelCounter.innerHTML = `Acceleration: <strong>${sessionData.mouseAcceleration.toFixed(1)}</strong> (Max: ${sessionData.maxAcceleration.toFixed(1)})`;
        scrollCounter.innerHTML = `Scrolls: <strong>${sessionData.scrollCount}</strong>`;
        scrollDistCounter.innerHTML = `Scroll Distance: <strong>${sessionData.scrollPixels.toFixed(0)}</strong> px`;
        letterCounter.innerHTML = `Keys Pressed: <strong>${sessionData.letterCount}</strong>`;
        wpmCounter.innerHTML = `Typing Speed: <strong>${calculateWPM().toFixed(1)}</strong> WPM`;
        leftClickCounter.innerHTML = `Left Clicks: <strong>${sessionData.leftClickCount}</strong>`;
        rightClickCounter.innerHTML = `Right Clicks: <strong>${sessionData.rightClickCount}</strong>`;
        middleClickCounter.innerHTML = `Middle Clicks: <strong>${sessionData.middleClickCount}</strong>`;
        doubleClickCounter.innerHTML = `Double Clicks: <strong>${sessionData.doubleClickCount}</strong>`;
        idleTimeCounter.innerHTML = `Idle Time: <strong>${formatTime(sessionData.totalIdleTime)}</strong>`;
        sessionTimeCounter.innerHTML = `Session: <strong>${formatTime(sessionDuration)}</strong>`;
        
        copyPasteCutCounter.innerHTML = `Copy/Paste/Cut: <strong>${sessionData.copyCount}</strong> / <strong>${sessionData.pasteCount}</strong> / <strong>${sessionData.cutCount}</strong>`;
        backspaceDeleteCounter.innerHTML = `Backspace/Delete: <strong>${sessionData.backspaceCount}</strong> / <strong>${sessionData.deleteCount}</strong>`;
        
        const totalArrowKeys = sessionData.arrowUpCount + sessionData.arrowDownCount + sessionData.arrowLeftCount + sessionData.arrowRightCount;
        arrowKeysCounter.innerHTML = `Arrow Keys: <strong>${totalArrowKeys}</strong> (↑${sessionData.arrowUpCount} ↓${sessionData.arrowDownCount} ←${sessionData.arrowLeftCount} →${sessionData.arrowRightCount})`;
        
        enterCounter.innerHTML = `Enter Key: <strong>${sessionData.enterCount}</strong>`;
        undoRedoCounter.innerHTML = `Undo/Redo: <strong>${sessionData.undoCount}</strong> / <strong>${sessionData.redoCount}</strong>`;

        const sortedKeys = Object.keys(sessionData.keyPressCount).sort((a, b) => {
            return sessionData.keyPressCount[b] - sessionData.keyPressCount[a];
        });

        let topKeysHTML = 'Top 5 Keys:<br><div style="margin-left: 10px; margin-top: 5px;">';
        for (let i = 0; i < Math.min(5, sortedKeys.length); i++) {
            const key = sortedKeys[i];
            const count = sessionData.keyPressCount[key];
            const barWidth = Math.min((count / sessionData.keyPressCount[sortedKeys[0]]) * 100, 100);
            topKeysHTML += `
                <div style="margin-bottom: 4px;">
                    <span style="display: inline-block; width: 60px;">${key}:</span>
                    <span style="display: inline-block; width: 40px; text-align: right; font-weight: bold;">${count}</span>
                    <div style="display: inline-block; width: 80px; height: 8px; background: #333; border-radius: 4px; margin-left: 5px; vertical-align: middle; overflow: hidden;">
                        <div style="width: ${barWidth}%; height: 100%; background: linear-gradient(90deg, #4CAF50, #8BC34A); border-radius: 4px;"></div>
                    </div>
                </div>
            `;
        }
        topKeysHTML += '</div>';
        topKeysCounter.innerHTML = topKeysHTML;

        const sortedCombos = Object.keys(sessionData.combosPressed).sort((a, b) => {
            return sessionData.combosPressed[b] - sessionData.combosPressed[a];
        });

        let topCombosHTML = 'Top 5 Combos:<br><div style="margin-left: 10px; margin-top: 5px;">';
        if (sortedCombos.length > 0) {
            for (let i = 0; i < Math.min(5, sortedCombos.length); i++) {
                const combo = sortedCombos[i];
                const count = sessionData.combosPressed[combo];
                const barWidth = Math.min((count / sessionData.combosPressed[sortedCombos[0]]) * 100, 100);
                topCombosHTML += `
                    <div style="margin-bottom: 4px;">
                        <span style="display: inline-block; width: 80px; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${combo}">${combo}:</span>
                        <span style="display: inline-block; width: 30px; text-align: right; font-weight: bold;">${count}</span>
                        <div style="display: inline-block; width: 70px; height: 8px; background: #333; border-radius: 4px; margin-left: 5px; vertical-align: middle; overflow: hidden;">
                            <div style="width: ${barWidth}%; height: 100%; background: linear-gradient(90deg, #FF9800, #FFC107); border-radius: 4px;"></div>
                        </div>
                    </div>
                `;
            }
        } else {
            topCombosHTML += '<span style="color: #999; font-style: italic;">No combos detected yet</span>';
        }
        topCombosHTML += '</div>';
        topCombosCounter.innerHTML = topCombosHTML;
    }

    function createCounter(label) {
        const counter = document.createElement('div');
        counter.style.cssText = `
            margin-bottom: 10px;
            padding: 8px 10px;
            border-radius: 8px;
            background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(139, 195, 74, 0.05));
            border-left: 3px solid #4CAF50;
            transition: all 0.2s ease;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        `;
        counter.innerHTML = `${label}: <strong>0</strong>`;
        
        counter.addEventListener('mouseenter', () => {
            counter.style.background = 'linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(139, 195, 74, 0.1))';
            counter.style.transform = 'translateX(4px)';
            counter.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        });
        
        counter.addEventListener('mouseleave', () => {
            counter.style.background = 'linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(139, 195, 74, 0.05))';
            counter.style.transform = 'translateX(0)';
            counter.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
        });
        
        return counter;
    }

    setInterval(() => {
        if (countersDiv.style.display === 'block') {
            const sessionDuration = Date.now() - sessionData.sessionStart;
            sessionTimeCounter.innerHTML = `Session: <strong>${formatTime(sessionDuration)}</strong>`;
        }
    }, 1000);

    window.addEventListener('beforeunload', saveData);

    updateCounters();

    console.log('Event Counters v4.1 loaded! Click the floating button or press Ctrl+Shift+E to toggle panel.');
})();