Gartic.io Voice Input

Gartic.io voice input

// ==UserScript==
// @name         Gartic.io Voice Input
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Gartic.io voice input
// @author       You
// @match        https://*.gartic.io/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const styles = `
        .voice-container {
            position: fixed;
            top: 80px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 12px;
            z-index: 9999;
            animation: floatIn 0.8s cubic-bezier(0.16, 1, 0.3, 1);
            background: rgba(15, 23, 42, 0.85);
            padding: 12px 20px;
            border-radius: 25px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3),
                        0 4px 8px rgba(31, 41, 55, 0.2),
                        inset 0 2px 5px rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(15px);
            border: 1px solid rgba(255, 255, 255, 0.08);
        }

        .voice-input {
            width: 300px;
            padding: 10px 18px;
            border: 2px solid rgba(255, 255, 255, 0.08);
            border-radius: 18px;
            font-size: 15px;
            background: rgba(255, 255, 255, 0.03);
            color: #fff;
            letter-spacing: 0.3px;
            box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .voice-input::placeholder {
            color: rgba(255, 255, 255, 0.3);
            font-style: italic;
        }

        .voice-input:focus {
            outline: none;
            border-color: rgba(56, 189, 248, 0.5);
            box-shadow: 0 0 20px rgba(56, 189, 248, 0.15),
                        inset 0 2px 4px rgba(0, 0, 0, 0.1);
            background: rgba(255, 255, 255, 0.05);
            transform: translateY(-1px);
        }

        .mic-button {
            width: 45px;
            height: 45px;
            border: none;
            border-radius: 50%;
            background: linear-gradient(135deg, #3b82f6, #1d4ed8);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3),
                        inset 0 -2px 5px rgba(0, 0, 0, 0.2);
            position: relative;
            overflow: hidden;
        }

        .mic-button::before {
            content: '';
            position: absolute;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 70%);
            transform: rotate(45deg);
            transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
            opacity: 0;
        }

        .mic-button:hover::before {
            opacity: 1;
            transform: rotate(225deg);
        }

        .mic-button::after {
            content: '';
            position: absolute;
            width: 100%;
            height: 100%;
            background: linear-gradient(rgba(255, 255, 255, 0.2), transparent);
            clip-path: polygon(0 0, 100% 0, 100% 25%, 0 25%);
            opacity: 0;
            transition: opacity 0.3s;
        }

        .mic-button:hover::after {
            opacity: 1;
        }

        .mic-button:hover {
            transform: translateY(-2px) scale(1.05);
            box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
            background: linear-gradient(135deg, #4f46e5, #3b82f6);
        }

        .mic-button:active {
            transform: scale(0.95);
            box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
        }

        .mic-button.recording {
            background: linear-gradient(135deg, #ef4444, #dc2626);
            animation: pulseRecord 2s infinite;
        }

        .mic-icon {
            width: 22px;
            height: 22px;
            fill: white;
            filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.2));
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .recording .mic-icon {
            animation: scaleIcon 2s infinite;
            filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
        }

        .wave-container {
            position: absolute;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }

        .wave {
            position: absolute;
            border-radius: 50%;
            border: 2px solid rgba(255, 255, 255, 0.4);
            animation: wave 2.5s infinite cubic-bezier(0.4, 0, 0.2, 1);
            opacity: 0;
        }

        .recording .wave-container .wave {
            animation: wave 2s infinite cubic-bezier(0.4, 0, 0.2, 1);
        }

        @keyframes wave {
            0% {
                width: 0;
                height: 0;
                opacity: 0.8;
                transform: translate(-50%, -50%) rotate(0deg);
            }
            100% {
                width: 200%;
                height: 200%;
                opacity: 0;
                transform: translate(-50%, -50%) rotate(180deg);
            }
        }

        @keyframes pulseRecord {
            0% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.5);
                transform: scale(1);
            }
            50% {
                box-shadow: 0 0 20px 5px rgba(239, 68, 68, 0.3);
                transform: scale(1.02);
            }
            100% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
                transform: scale(1);
            }
        }

        @keyframes scaleIcon {
            0%, 100% { 
                transform: scale(1) rotate(0deg); 
                filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
            }
            50% { 
                transform: scale(0.85) rotate(5deg); 
                filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.7));
            }
        }

        @keyframes floatIn {
            0% {
                transform: translate(-50%, -30px);
                opacity: 0;
                filter: blur(10px);
            }
            100% {
                transform: translate(-50%, 0);
                opacity: 1;
                filter: blur(0);
            }
        }

        .visualizer {
            position: absolute;
            bottom: -10px;
            left: 0;
            width: 100%;
            height: 2px;
            display: flex;
            justify-content: space-between;
            padding: 0 5px;
        }

        .visualizer-bar {
            width: 2px;
            height: 2px;
            background: rgba(255, 255, 255, 0.5);
            transform-origin: bottom;
            transition: all 0.2s ease;
            border-radius: 2px;
            box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
        }

        .recording .visualizer-bar {
            animation: glow 1.5s infinite;
        }

        @keyframes glow {
            0%, 100% {
                box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
            }
            50% {
                box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
            }
        }
    `;

    const styleSheet = document.createElement("style");
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);

    const container = document.createElement('div');
    container.className = 'voice-container';

    const input = document.createElement('input');
    input.className = 'voice-input';
    input.type = 'text';
    input.placeholder = 'Konuşmak için mikrofonu tıklayın...';

    const button = document.createElement('button');
    button.className = 'mic-button';
    button.innerHTML = `
        <div class="wave-container">
            <div class="wave"></div>
            <div class="wave" style="animation-delay: 0.4s"></div>
            <div class="wave" style="animation-delay: 0.8s"></div>
        </div>
        <svg class="mic-icon" viewBox="0 0 24 24">
            <path d="M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z"/>
        </svg>
        <div class="visualizer">
            ${Array(20).fill().map(() => '<div class="visualizer-bar"></div>').join('')}
        </div>
    `;

    let originalSend = WebSocket.prototype.send;
    let wsObj = null;

    WebSocket.prototype.send = function(data) {
        originalSend.apply(this, arguments);
        if (!wsObj) {
            wsObj = this;
            wsObj.addEventListener("message", (msg) => {
                try {
                    let data = JSON.parse(msg.data.slice(2));
                    if (data[0] == 5) {
                        wsObj.lengthID = data[1];
                        wsObj.id = data[2];
                        wsObj.roomCode = data[3];
                    }
                } catch (err) {}
            });
        }
    };

    const sendMessage = (message) => {
        if (wsObj && message.trim()) {
            wsObj.send(`42[11,${wsObj.id},"${message}"]`);
        }
    };

    const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
    recognition.lang = 'tr-TR';
    recognition.continuous = true;
    recognition.interimResults = true;

    let isRecording = false;

    button.addEventListener('click', () => {
        if (!isRecording) {
            recognition.start();
            button.classList.add('recording');
            animateVisualizer(true);
        } else {
            recognition.stop();
            button.classList.remove('recording');
            animateVisualizer(false);
        }
        isRecording = !isRecording;
    });

    input.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            sendMessage(input.value);
            input.value = '';
        }
    });

    recognition.onresult = (event) => {
        const transcript = Array.from(event.results)
            .map(result => result[0])
            .map(result => result.transcript)
            .join('');
        
        input.value = transcript;
    };

    recognition.onend = () => {
        button.classList.remove('recording');
        isRecording = false;
        animateVisualizer(false);
    };

    function animateVisualizer(active) {
        const bars = document.querySelectorAll('.visualizer-bar');
        if (active) {
            bars.forEach(bar => {
                const animate = () => {
                    const height = Math.random() * 20 + 2;
                    bar.style.height = `${height}px`;
                    bar.style.transform = `scaleY(${height/2})`;
                    if (isRecording) {
                        requestAnimationFrame(animate);
                    }
                };
                animate();
            });
        } else {
            bars.forEach(bar => {
                bar.style.height = '2px';
                bar.style.transform = 'scaleY(1)';
            });
        }
    }

    container.appendChild(input);
    container.appendChild(button);
    document.body.appendChild(container);
})();