OpenGuessr Legit Cheat

Undetectable | Press Tab to open the settings menu

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name    OpenGuessr Legit Cheat
// @namespace https://greasyfork.org/en/users/1588266-woggieboost
// @version    2.0
// @description    Undetectable | Press Tab to open the settings menu
// @author    woggieboost
// @license    MIT
// @match    *://*.openguessr.com/*
// @grant    GM_setValue
// @grant    GM_getValue
// @grant    GM_xmlhttpRequest
// @connect    discord.com
// @connect    nominatim.openstreetmap.org
// @icon    https://www.google.com/s2/favicons?sz=32&domain=openguessr.com
// ==/UserScript==

(function () {
    'use strict';

    // ========== Common Config ==========
    const DEFAULT_HOTKEYS = {
        openPanel: 'tab',
        sendToDiscord: 'q',
        notify: 'g',
        toggleMarker: 'x',
        toggleInfo: 'v',
    };

    const DEFAULT_TOGGLES = {
        sendToDiscord: true,
        autoSend: false,
        notify: true,
        autoNotify: false,
        toggleMarker: true,
        toggleInfo: true,
    };

    // ========== Global State ==========
    let state = {
        notificationPermission: Notification.permission,
        streetView: null,
        gameMap: null,
        mapMarker: null,
        currentAddress: null,
        isInfoDisplayed: false,
        lastCoord: null,
        originalNickname: null,
        originalFlag: null,
        logoElement: null,
        hotkeys: GM_getValue('hotkeys', DEFAULT_HOTKEYS),
        featureToggles: GM_getValue('featureToggles', DEFAULT_TOGGLES),
    };

    // Initialize missing hotkeys
    if (!state.hotkeys.openPanel) {
        state.hotkeys.openPanel = 'tab';
        GM_setValue('hotkeys', state.hotkeys);
    }

    // Initialize missing toggles
    if (state.featureToggles.autoSend === undefined) {
        state.featureToggles.autoSend = false;
        state.featureToggles.autoNotify = false;
        GM_setValue('featureToggles', state.featureToggles);
    }

    // ========== Utility Functions ==========
    function saveHotkeys() {
        GM_setValue('hotkeys', state.hotkeys);
    }

    function saveFeatureToggles() {
        GM_setValue('featureToggles', state.featureToggles);
    }

    function getMainDomain() {
        const parts = window.location.hostname.split(".");
        if (parts.length > 2) {
            return parts.slice(-2).join(".");
        }
        return window.location.hostname;
    }

    function requestNotificationPermission() {
        if (state.notificationPermission === 'default') {
            Notification.requestPermission().then(permission => {
                state.notificationPermission = permission;
            });
        }
    }

    function sendNotification(title, body) {
        if (state.notificationPermission === 'granted') {
            new Notification(title, {
                body,
                icon: `https://www.google.com/s2/favicons?sz=32&domain=${getMainDomain()}`
            });
        }
    }

    function throttle(fn, wait) {
        let lastTime = 0;
        let pendingPromise = null;

        return function (...args) {
            const now = Date.now();
            if (now - lastTime >= wait) {
                lastTime = now;
                pendingPromise = Promise.resolve(fn.apply(this, args));
                return pendingPromise;
            }
            return pendingPromise;
        };
    }

    function debounce(fn, wait) {
        let timeout;
        return function (...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => fn.apply(this, args), wait);
        };
    }

    // ========== Nominatim Service ==========
    function _getAddress(lat, lng) {
        if (state.lastCoord && state.lastCoord === [lat, lng]) {
            return Promise.resolve(state.currentAddress);
        }

        return new Promise((resolve, reject) => {
            const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&accept-language=en`;
            GM_xmlhttpRequest({
                method: 'GET',
                url,
                headers: { 'Accept': 'application/json' },
                onload: res => {
                    try {
                        resolve(JSON.parse(res.responseText));
                    } catch (err) {
                        reject(err);
                    }
                },
                onerror: err => reject(err)
            });
        });
    }

    const getAddress = throttle(_getAddress, 1000);

    // ========== Address Formatting ==========
    function formatAddress(address) {
        if (!address || !address.address) return null;

        const a = address.address;
        const parts = [
            a.country,
            a.state || a.province || a.region || a.county || a.prefecture,
            a.city || a.town || a.village,
            a.road || a.street,
        ];

        return parts.filter(Boolean).join(', ');
    }

    // ========== Tile URL Generation ==========
    function lonToTile(lng, zoom) {
        return Math.floor((lng + 180) / 360 * Math.pow(2, zoom));
    }

    function latToTile(lat, zoom) {
        return Math.floor(
            (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2
            * Math.pow(2, zoom)
        );
    }

    function buildTileUrl(lat, lng) {
        const zoom = 6;
        const tileX = lonToTile(lng, zoom);
        const tileY = latToTile(lat, zoom);
        return `https://mapsresources-pa.googleapis.com/v1/tiles?map_id=61449c20e7fc278b&version=sdk-15797339025669136861&pb=!1m5!1m4!1i${zoom}!2i${tileX}!3i${tileY}!4i256!2m3!1e0!2sm!3i773537392!3m13!2sen!3sUS!5e18!12m5!1e68!2m2!1sset!2sRoadmap!4e2!12m3!1e37!2m1!1ssmartmaps!4e0!5m2!1e3!5f2!23i46991212!23i47054750!23i47083502!23i56565656!26m2!1e2!1e3`;
    }

    // ========== Discord Integration ==========
    function createEmbed() {
        const { lat, lng } = getCoordinates();
        const query = `${lat},${lng}`;
        const formattedAddr = formatAddress(state.currentAddress) || getLocationDescription();

        return {
            title: '✅ Location Tracked',
            description: `### [${formattedAddr}](https://maps.google.com/?q=${query}&ll=${query}&z=5)`,
            color: 516235,
            image: {
                url: buildTileUrl(lat, lng)
            }
        };
    }

    function sendToDiscord(embed) {
        let webhookUrl = GM_getValue('discordWebhookUrl');
        if (!webhookUrl) {
            webhookUrl = prompt('Discord Webhook:', '');
            if (webhookUrl) {
                GM_setValue('discordWebhookUrl', webhookUrl);
            } else {
                alert('You must provide a Discord Webhook URL to continue.');
                return;
            }
        }

        const payload = JSON.stringify({
            embeds: [embed],
            username: 'OpenGuessr Legit Cheat',
            avatar_url: `https://www.google.com/s2/favicons?sz=32&domain=${getMainDomain()}`
        });

        GM_xmlhttpRequest({
            method: 'POST',
            url: webhookUrl,
            headers: { 'Content-Type': 'application/json' },
            data: payload,
            onload: res => {
                if (res.status !== 204) {
                    console.error('Discord error:', res);
                }
            },
            onerror: err => {
                console.error('Request failed:', err);
            }
        });
    }



    // Coordinate getter
    function getCoordinates() {
        try {

            const iframe = document.querySelector('#PanoramaIframe') ||
                  document.querySelector('iframe[src*="location"]') ||
                  document.querySelector('.iframeWithStreetView');

            if (iframe && (iframe.src || iframe.data)) {
                const loc = new URL(iframe.src || iframe.data).searchParams.get('location');
                if (loc) {
                    const [lat, lng] = loc.split(',').map(Number);
                    return { lat, lng };
                }
            }
        } catch (e) {
            console.error('Failed to extract coordinates:', e);
        }
        return { lat: undefined, lng: undefined };
    }


    // Nickname getter/setter
    function getNicknameElement() {
        const elements = document.querySelectorAll('.player-name');
        return elements[1] || null;
    }

    function setNickname(text) {
        const el = getNicknameElement();
        if (el) {
            el.textContent = text || getLocationDescription();
        }
    }

    // Flag getter/setter
    function getFlagElement() {
        let flagEl = document.querySelectorAll('.player-name-wrapper .player-name img')[1];
        if (!flagEl) {
            const container = getNicknameElement();
            if (container) {
                flagEl = document.createElement('img');
                Object.assign(flagEl, { src: 'https://flagcdn.com/w80/us.png' });
                Object.assign(flagEl.style, {
                    display: 'inline-block',
                    width: '1.5em',
                    height: '1em',
                    marginRight: '0.4em',
                    verticalAlign: 'middle',
                    flexShrink: '0',
                    objectFit: 'cover',
                    borderRadius: '2px'
                });
                container.appendChild(flagEl);
            }
        }
        return flagEl;
    }

    function setFlag(countryCode) {
        const el = getFlagElement();
        if (el && countryCode) {
            el.src = `https://flagcdn.com/48x36/${countryCode.toLowerCase()}.png`;
        }
    }

    // Logo element creator for OpenGuessr/WorldGuessr
    function createLogoElement() {
        const logoContainer = document.querySelector('.logo');
        if (logoContainer) {
            const imgEl = logoContainer.querySelector('img');
            let imgClass = '';
            if (imgEl) {
                imgClass = imgEl.className;
                imgEl.remove();
            }

            const newEl = document.createElement('div');
            newEl.className = imgClass;
            Object.assign(newEl.style, {
                width: '480px',
                opacity: '0',
                fontSize: '20px',
                color: '#fff',
                fontWeight: 'bold',
                textShadow: '#CC302E 2px 0px 0px, #CC302E 1.75517px 0.958851px 0px, #CC302E 1.0806px 1.68294px 0px, #CC302E 0.141474px 1.99499px 0px, #CC302E -0.832294px 1.81859px 0px, #CC302E -1.60229px 1.19694px 0px, #CC302E -1.97998px 0.28224px 0px, #CC302E -1.87291px -0.701566px 0px, #CC302E -1.30729px -1.5136px 0px, #CC302E -0.421592px -1.95506px 0px, #CC302E 0.567324px -1.91785px 0px, #CC302E 1.41734px -1.41108px 0px, #CC302E 1.92034px -0.558831px 0px'
            });
            logoContainer.appendChild(newEl);
            return newEl;
        }


        return null;
    }

    // ========== Address Display ==========
    async function updateAddressDisplay() {
        if (!state.logoElement) {
            state.logoElement = createLogoElement();
        }
        if (state.logoElement) {
            if (state.isInfoDisplayed) {
                state.logoElement.style.opacity = 1;
                state.logoElement.textContent = formatAddress(state.currentAddress);
            }
            else state.logoElement.style.opacity = 0;
        }
    }

    // ========== Map Marker Management ==========
    function spawnRipple(lat, lng) {
        if (state.gameMap) {
            // Leaflet
            const rippleIcon = L.divIcon({
                className: '',
                html: '<div class="ripple-effect"></div>',
                iconSize: [80, 80],
                iconAnchor: [40, 40]
            });

            const rippleMarker = L.marker([lat, lng], {
                icon: rippleIcon,
                interactive: false
            }).addTo(state.gameMap);

            setTimeout(() => {
                state.gameMap.removeLayer(rippleMarker);
            }, 2000);
        }
    }

    function toggleMapMarker() {
        if (!state.gameMap) return;

        const { lat, lng } = getCoordinates();
        if (lat === undefined || lng === undefined) return;

        // Leaflet
        if (state.mapMarker) {
            state.gameMap.removeLayer(state.mapMarker);
            state.mapMarker = null;
        } else {
            const customIcon = L.icon({
                iconUrl: 'https://openguessr.com/img/pins/result_pin.png',
                iconSize: [35, 35],
                iconAnchor: [17.5, 35],
            });
            state.mapMarker = L.marker([lat, lng], {
                icon: customIcon
            }).addTo(state.gameMap);
        }

        spawnRipple(lat, lng);
        setTimeout(() => spawnRipple(lat, lng), 300);
        setTimeout(() => spawnRipple(lat, lng), 600);
    }

    // ========== Settings Panel ==========
    function createSettingsPanel() {
        const panel = document.createElement('div');
        panel.id = 'cgx-settings-panel';
        panel.style.cssText = `
            position: fixed;
            top: 30%;
            left: 40%;
            width: 300px;
            background: rgba(25, 25, 25, 0.92);
            color: #fff;
            padding: 18px;
            border-radius: 12px;
            z-index: 999999;
            font-size: 14px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255,255,255,0.08);
            box-shadow: 0 8px 20px rgba(0,0,0,0.35);
            animation: cgxFadeIn 0.18s ease-out;
        `;

        panel.innerHTML = `
            <style>
                @keyframes cgxFadeIn {
                    from { opacity: 0; transform: translateY(-6px); }
                    to { opacity: 1; transform: translateY(0); }
                }

                .cgx-input {
                    width: 50px;
                    text-align: center;
                    padding: 4px 6px;
                    border-radius: 6px;
                    border: 1px solid rgba(255,255,255,0.15);
                    background: rgba(255,255,255,0.08);
                    color: #fff;
                    outline: none;
                    transition: 0.15s;
                }
                .cgx-input:focus {
                    border-color: #4caf50;
                    background: rgba(255,255,255,0.15);
                }

                .cgx-btn {
                    width: 100%;
                    padding: 8px;
                    margin-top: 10px;
                    border-radius: 8px;
                    border: none;
                    cursor: pointer;
                    font-size: 14px;
                    transition: 0.15s;
                }
                .cgx-save-btn { background: #4caf50; color: white; }
                .cgx-reset-btn { background: #2196f3; color: white; }
                .cgx-reset-btn:hover { background: #42a5f5; }
                .cgx-btn:disabled {
                    background: #c0c0c0 !important;
                    cursor: default;
                    opacity: 0.9;
                }

                .cgx-close-btn {
                    position: absolute;
                    top: 8px;
                    right: 10px;
                    font-size: 18px;
                    color: #ccc;
                    cursor: pointer;
                    transition: 0.15s;
                    user-select: none;
                }
                .cgx-close-btn:hover {
                    color: #cd312f;
                    transform: scale(1.15);
                }
                .cgx-toggle {
                    width: 36px;
                    height: 18px;
                    border-radius: 20px;
                    background: rgba(255,255,255,0.2);
                    position: relative;
                    cursor: pointer;
                    transition: 0.2s;
                }
                .cgx-toggle::after {
                    content: "";
                    position: absolute;
                    width: 14px;
                    height: 14px;
                    top: 2px;
                    left: 2px;
                    border-radius: 50%;
                    background: #fff;
                    transition: 0.2s;
                }

                .cgx-toggle.active {
                    background: #4caf50;
                    box-shadow: 0 0 6px rgba(76,175,80,0.6);
                }

                .cgx-toggle.active::after {
                    left: 20px;
                }
            </style>

            <div class="cgx-close-btn">✕</div>

            <div style="font-size:17px; text-align:center; font-weight:bold; margin-bottom:12px;">
                Hotkeys Settings
            </div>

            ${Object.keys(DEFAULT_TOGGLES).map(key => `
                <div style="margin-bottom:10px; display:flex; align-items:center; justify-content:space-between;">
                    <div style="display:flex; align-items:center; gap:8px;">
                        ${key !== 'openPanel' ? `<div class="cgx-toggle ${state.featureToggles[key] ? 'active' : ''}" data-toggle="${key}"></div>` : `<div style="width:36px;"></div>`}
                        <span>${key}</span>
                    </div>
                    ${['autoSend', 'autoNotify'].includes(key) ? '' : `<input class="cgx-input" type="text" data-key="${key}" value="${state.hotkeys[key] || ''}">`}
                </div>
            `).join('')}

            <button id="cgx-save-hotkeys" class="cgx-btn cgx-save-btn">Save</button>
            <button id="cgx-reset-hotkeys" class="cgx-btn cgx-reset-btn">Reset</button>
        `;

        document.body.appendChild(panel);

        // Close button
        panel.querySelector('.cgx-close-btn').onclick = () => {
            panel.style.display = 'none';
        };

        // Toggles
        panel.querySelectorAll('.cgx-toggle').forEach(toggle => {
            toggle.onclick = () => {
                const key = toggle.dataset.toggle;
                state.featureToggles[key] = !state.featureToggles[key];
                toggle.classList.toggle('active');
                saveFeatureToggles();
            };
        });

        // Save button
        const saveBtn = document.getElementById('cgx-save-hotkeys');
        saveBtn.onclick = () => {
            panel.querySelectorAll('.cgx-input').forEach(input => {
                const key = input.dataset.key;
                const val = input.value.trim().toLowerCase();
                if (val) state.hotkeys[key] = val;
            });

            saveHotkeys();
            saveFeatureToggles();

            saveBtn.textContent = 'Saved';
            saveBtn.disabled = true;

            setTimeout(() => {
                saveBtn.textContent = 'Save';
                saveBtn.disabled = false;
            }, 1200);
        };

        // Reset button
        const resetBtn = document.getElementById('cgx-reset-hotkeys');
        resetBtn.onclick = () => {
            state.hotkeys = { ...DEFAULT_HOTKEYS };
            saveHotkeys();

            panel.querySelectorAll('.cgx-input').forEach(input => {
                input.value = state.hotkeys[input.dataset.key];
            });

            resetBtn.textContent = 'Reset';
            resetBtn.disabled = true;
        };

        // Key input handling
        panel.querySelectorAll('.cgx-input').forEach(input => {
            input.addEventListener('keydown', e => {
                e.preventDefault();

                let key = e.key.toLowerCase();

                if (['shift', 'control', 'alt', 'meta'].includes(key)) return;

                if (key === ' ') key = 'space';

                input.value = key;
                resetBtn.disabled = false;
            });
        });
    }

    function extractStreetView() {
        try {
            const container = document.querySelector('div[data-qa="panorama"]');
            if (!container) return;

            const fiberKey = Object.keys(container).find(k => k.startsWith('__reactFiber'));
            if (!fiberKey) return;

            const fiberNode = container[fiberKey];
            state.streetView =
                fiberNode.return.return.return.sibling.memoizedProps.panorama ||
                fiberNode.return.updateQueue.lastEffect.next.next.next.next.next.next.next.next.next.next.next.deps[0];
        } catch (err) {
            state.streetView = null;
        }
    }

    function extractMapInstance() {
        try {
            const mapElement = document.querySelector("[class*='guess-map_canvas']") || document.querySelector('.leaflet-container');
            if (!mapElement) return;

            const fiberKey = Object.keys(mapElement).find(k => k.startsWith('__reactFiber$'));
            if (!fiberKey) return;

            const fiberNode = mapElement[fiberKey];
            state.gameMap =
                fiberNode?.return?.memoizedProps?.map ||
                fiberNode?.return?.updateQueue?.lastEffect?.deps?.[0] ||
                fiberNode?.child?.memoizedProps?.value?.map;
        } catch (err) {
            state.gameMap = null;
        }
    }

    // ========== Keyboard Handler ==========
    async function handleKeyDown(e) {
        if (
            e.target.tagName === 'TEXTAREA' ||
            e.target.tagName === 'INPUT' ||
            e.target.isContentEditable
        ) return;

        const key = e.key.toLowerCase();
        const isHotkey =
              key === state.hotkeys.sendToDiscord ||
              key === state.hotkeys.toggleMarker ||
              key === state.hotkeys.toggleInfo ||
              key === state.hotkeys.notify ||
              key === state.hotkeys.openPanel;

        if (!isHotkey) return;

        if (key === state.hotkeys.openPanel) {
            const panel = document.getElementById('cgx-settings-panel');
            if (panel) {
                panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
            } else {
                createSettingsPanel();
            }
        }

        if (key === state.hotkeys.sendToDiscord && state.featureToggles.sendToDiscord) {
            sendToDiscord(createEmbed());
        }

        if (key === state.hotkeys.toggleMarker && state.featureToggles.toggleMarker) {
            if(!state.gameMap) extractMapInstance();
            toggleMapMarker();
        }

        if (key === state.hotkeys.toggleInfo && state.featureToggles.toggleInfo) {
            state.isInfoDisplayed = !state.isInfoDisplayed;
            updateAddressDisplay();
        }

        if (key === state.hotkeys.notify && state.featureToggles.notify) {
            requestNotificationPermission();
            sendNotification(
                'OpenGuessr Legit Cheat',
                formatAddress(state.currentAddress) || getLocationDescription()
            );
        }

        e.stopImmediatePropagation();
        e.stopPropagation();
    }

    document.addEventListener('keydown', handleKeyDown, true);

    // Prevent focus on iframe
    setInterval(() => {
        if (document.activeElement instanceof HTMLIFrameElement || document.activeElement instanceof Object) {
            window.focus();
            document.body.focus();
        }
    }, 200);

    // Monitor coordinate changes
    const coordObserver = new MutationObserver(() => {
        const streetViewContainer = document.getElementById('panorama-iframe') ||
              document.getElementById('streetview') ||
              document.querySelector('iframe[src*="location"]') ||
              document.querySelector('.iframeWithStreetView');
        if (streetViewContainer) {
            const intervalId = setInterval(async () => {
                const { lat, lng } = getCoordinates();
                if (lat && lng) {
                    if (state.lastCoord != [lat, lng]) {
                        state.lastCoord = [lat, lng];
                        state.currentAddress = await getAddress(lat, lng);
                        updateAddressDisplay();

                        if (state.mapMarker) {
                            state.mapMarker.setLatLng([lat, lng]);
                        }
                    }
                }
            }, 500);
            coordObserver.disconnect();
        }
    });

    coordObserver.observe(document.body, { childList: true, subtree: true });

    // Initialize Leaflet map
    const mapObserver = new MutationObserver(() => {
        try {
            if (L && L.map) {
                const originalSetView = L.Map.prototype.setView;
                L.Map.prototype.setView = function (...args) {
                    state.gameMap = this;
                    return originalSetView.apply(this, args);
                };
                mapObserver.disconnect();
            }
        } catch (err) {
        }
    });

    mapObserver.observe(document.body, { childList: true, subtree: true });


    // ========== Styles ==========
    const style = document.createElement('style');
    style.innerHTML = `
        .gameplayAdArea, .venatus-ad, #worldguessr_gameui_ad, #worldguessr_home_ad {
            display: none !important;
            visibility: hidden !important;
            opacity: 0 !important;
        }
        .gmap-ripple {
            position: absolute;
            width: 80px;
            height: 80px;
            pointer-events: none;
        }

        .gmap-ripple::before,
        .gmap-ripple::after {
            content: "";
            position: absolute;
            left: 0%;
            top: 0%;
            width: 80px;
            height: 80px;
            border-radius: 50%;
            transform: translate(-50%, -50%) scale(0.6);
            background: radial-gradient(
                circle,
                rgba(255, 80, 80, 0.6) 0%,
                rgba(255, 80, 80, 0.45) 40%,
                rgba(255, 80, 80, 0.3) 70%,
                transparent 100%
            );
        }

        .gmap-ripple::before {
            animation: gmapRipple 2s ease-out;
        }

        .gmap-ripple::after {
            animation: gmapRipple 2s ease-out 0.4s;
        }

        .ripple-effect {
            position: absolute;
            width: 80px;
            height: 80px;
        }

        .ripple-effect::before,
        .ripple-effect::after {
            content: "";
            position: absolute;
            left: 50%;
            top: 50%;
            width: 80px;
            height: 80px;
            border-radius: 50%;
            background: radial-gradient(
                circle,
                rgba(255, 60, 60, 0.6) 0%,
                rgba(255, 60, 60, 0.45) 35%,
                rgba(255, 60, 60, 0.3) 65%,
                transparent 100%
            );
            transform: translate(-50%, -50%) scale(1);
        }

        .ripple-effect::before {
            animation: ripple 2s ease-out;
        }

        .ripple-effect::after {
            animation: ripple 2s ease-out 0.4s;
        }

        @keyframes gmapRipple {
            0% {
                transform: translate(-50%, -50%) scale(0.8);
                opacity: 0.7;
            }
            100% {
                transform: translate(-50%, -50%) scale(8);
                opacity: 0;
            }
        }

        @keyframes ripple {
            0% {
                transform: translate(-50%, -50%) scale(0.8);
                opacity: 0.6;
            }
            100% {
                transform: translate(-50%, -50%) scale(8);
                opacity: 0;
            }
        }
    `;
    document.head.appendChild(style);

})();