Cookie Clicker Mod Menu

Mod menu for Cookie Clicker

// ==UserScript==
// @name         Cookie Clicker Mod Menu
// @namespace    https://github.com/qba210/cookie-clicker-mod-menu
// @version      1.1
// @description  Mod menu for Cookie Clicker
// @author       qba210
// @license      MIT
// @match        *://orteil.dashnet.org/cookieclicker/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAqCAMAAAD26OlUAAAAolBMVEU0DAIKBQhqIg8A3v9DLSpuV1URLT9FARDDkzh+ernp1nP///+QgHeMAACpdiE0FRfBqIyov5EAhZXwFwC7XzaDQBTUwbxBTn1NKiPg4OAXPiCaMxaauDTdtGaVXjnKEwr/fgAAWZ5Sb03Fh1D/ieeWABzUQQ3/zC86gSvtEG/cQWKgANXMs6z67bnhwJalQh9g/1B+WkBJDQPKa0G9jnkAAADDKDlIAAAANnRSTlP//////////////////////////////////////////////////////////////////////wChj04xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGx2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDg4LCAyMDIwLzA3LzEwLTIyOjA2OjUzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIxLjAgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMi0wOC0wN1QyMDo1NToxNCswMjowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjItMDgtMDdUMjE6MDA6MjgrMDI6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjItMDgtMDdUMjE6MDA6MjgrMDI6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OWRkM2RiMTEtNWViZS01MzQzLWEyOGEtYzAwZDE2MDY3MWFhIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6MGUxNzYxNGUtOTkxNy1hMzRmLTllYWYtYzM4ZTdlYjY2MWIwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6RjVDMTE2MERDQ0NDMTFFQzlBOThCQzEzREI1QkEzRjUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIyIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGNUMxMTYwQUNDQ0MxMUVDOUE5OEJDMTNEQjVCQTNGNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNUMxMTYwQkNDQ0MxMUVDOUE5OEJDMTNEQjVCQTNGNSIvPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiNWRkNTg4NS1jNjA4LWEwNGEtYTYyMi04OTM4NjNlYjcwNGEiIHN0RXZ0OndoZW49IjIwMjItMDgtMDdUMjE6MDA6MjgrMDI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMi4wIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OWRkM2RiMTEtNWViZS01MzQzLWEyOGEtYzAwZDE2MDY3MWFhIiBzdEV2dDp3aGVuPSIyMDIyLTA4LTA3VDIxOjAwOjI4KzAyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjIuMCAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+nkWItQAAANxJREFUOMut1MEOgjAQBFDaY016bbJfsMde+P9fk3GyLg2obHESPcgjw2LbZd0isv7MEoKq/4Qiqr2rWn16Zxbuq3EZN5aCG0ccg6wFqlWVEAGOQ6+1UtWUVB9b9vQ6dAZEiG9Cp3HIy3glPgw4fotDGwSo95TKEJbPQlB8PPegDTPCWu9AG8AWBZf0DGyNfyGTEgAfRKS1OLRFa7U+ksjJ6rkIUT4uNnmFxXHotFY8hCVnMGyXOCRtDdBKWcvNPAN5pOSMSiKOwQ09B4/Hnh+K8/CYD9U34Mnr+QafGozjyeDrtEIAAAAASUVORK5CYII=
// @grant        none
// ==/UserScript==

(async function() {
    'use strict';

    class Logger {
        /**
         * 
         * @param {object} msg 
         */
        static LogInfo(msg) {
            console.log("\n[Mod Menu] ", msg);
        }

        /**
         * 
         * @param {object} msg 
         */
        static LogDebug(msg) {
            Logger.LogInfo(msg);
        }
    }

    Logger.LogInfo("Waiting for Game...")
    let gamePromise =  new Promise((res) => {var int = setInterval(() => {if (Game.Achievements) {clearInterval(int); res(Game);}}, 10)});
    await gamePromise;
    Logger.LogInfo("Game loaded!")


    let cheatMenu = document.createElement("div");

    const translations = [
        {
            lang: "pl",
            langName: "Polski",
            hacks: {
                "silent-mode": {
                    name: "Tryb cichy",
                    desc: "Unika wykrycia przez grę hackowania (np. osiągnięcie 'Oszukane ciastka smakują najgorzej')"
                },
                "cookie-spam": {
                    name: "Spamowanie ciastka",
                    desc: "Po przyciśnięciu ciastka samoczynnie zaczyna na nie klikać (kończy po odciśnięciu)"
                },
                "autoclicker": {
                    name: "Autokliker",
                    desc: "Auttomatycznie klika w ciastko"
                },
                "dev-tools": {
                    name: "Menu dewelop.",
                    desc: "Otwiera menu deweloperskie"
                },
                "set-cookies": {
                    name: "Ustaw ilość ciastek",
                    desc: "Pozwala na zmianę ilości ciastek"
                },
                "delete-save": {
                    name: "Usuń zapis"
                },
                "earn-achievement": {
                    name: "Odblokuj osiągnięcie",
                    desc: "Wybierz osiągnięcie z listy i kliknij przycisk aby je zdobyć!"
                },
                "revoke-achievement": {
                    name: "Usuń osiągnięcie",
                    desc: "Wybierz osiągnięcie z listy i kliknij przycisk aby je usunąć!"
                },
                "finish-game": {
                    name: "Ukończ grę",
                    desc: "Kończy grę i odblokowywuje wszystko"
                }
            },
            strings: {
                "dev-tools-confirm": "Jesteś pewien? Tryb cichy nie uchroni Cię od hacków które włączycz w menu deweloperskim.",
                "changes-as-you-type": "Wartość zmienia się jak piszesz",
                "confirm-save-delete": "Jesteś pewien?",
                "set-object-amount": "Ustaw ilość %s"
            }
        },
        {
            lang: "en",
            langName: "English",
            hacks: {
                "silent-mode": {
                    name: "Silent mode",
                    desc: "Avoids detecting hacks by game (ex.: achievement 'Cheated cookies tastes awful')"
                },
                "cookie-spam": {
                    name: "Cookie spam",
                    desc: "After holding the cookie, starts to click it automatically (ends after releasing)"
                },
                "autoclicker": {
                    name: "Autoclicker",
                    desc: "Automatically clicks cookie"
                },
                "dev-tools": {
                    name: "Dev menu",
                    desc: "Opens developer menu"
                },
                "set-cookies": {
                    name: "Set cookie count",
                    desc: "Allows you to change cookie count"
                },
                "delete-save": {
                    name: "Delete save"
                },
                "earn-achievement": {
                    name: "Earn achievement",
                    desc: "Select achievement from list, then click button to get it!"
                },
                "revoke-achievement": {
                    name: "Remove achievement",
                    desc: "Select achievement from list, then click button to remove it!"
                },
                "finish-game": {
                    name: "Finish game",
                    desc: "Finishes game and unlocks everything"
                }
            },
            strings: {
                "dev-tools-confirm": "Are you sure? Silent mode cannot prevent detecting cheats you activate in developer menu.",
                "changes-as-you-type": "Changes as you type",
                "confirm-save-delete": "Are you sure?",
                "set-object-amount": "Set %s amount"
            }
        }
    ]

    let lang = translations.find((lng) => lng.lang === (localStorage.getItem("cheats_lang") ?? "en"));

    document.body.append(cheatMenu);

    cheatMenu.outerHTML = `
    <div id="hack-menu" style="left: 50%; top: 35px;">
        <div id="hack-popup">Hack</div>
        <br>
        <div id="hacks" style="display: none;">
            <div class="hack-list" id="main-hacks">
                <select id="hack-lang-select"></select>
                <div class="hack hack-bool" id="hack-silent-mode"></div>
                <div class="hack hack-bool" id="hack-cookie-spam"></div>
                <div class="hack hack-bool" id="hack-autoclicker"></div>
                <div class="hack hack-btn" id="hack-dev-tools"></div>
                <div class="hack hack-btn" id="hack-set-cookies"></div>
                <div class="hack hack-btn" id="hack-delete-save"></div>
                <div class="hack hack-btn" id="hack-finish-game"></div>
                <div class="hack hack-select" id="hack-earn-achievement"></div>
                <div class="hack hack-select" id="hack-revoke-achievement"></div>
            </div>
            <div class="hack-list" id="objects-hacks">
            </div>
        </div>
    </div>
    <div id="hack-tooltip" style="opacity: 0;left:0;top:0"></div>
    <div id="hack-alert-input" style="opacity: 0;display: none;">
        <div id="hack-alert-input-popup">
            <h1 id="hack-alert-input-popup-title"></h1>
            <div id="hack-alert-input-popup-desc"></div><br>
            <input type="number" id="hack-alert-input-popup-input"/><br>
            <input type="button" id="hack-alert-input-popup-ok" value="OK"/>
        </div>
    </div>
    `;
    
    let styles = document.createElement("style");
    styles.innerText = `
        #hack-menu, #hack-tooltip {
            z-index: 9000000000;
            position: absolute;
            box-sizing: border-box;
        }
        #hack-menu, #hack-menu *:not(#hacks){
            box-sizing: border-box;
        }
        #hack-menu {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: clamp(150px, 225px, 25vw);
        }
        #hacks {
            backdrop-filter: blur(5px);
            display: flex;
            flex-direction: row;
            gap: 10px;
            padding: 10px;
            background-color: rgba(0, 0, 0, .5);
            border-radius: 13px;
            width: 200%;
            box-sizing: content-box;
        }

        .hack-list {
            display: flex;
            flex-direction: column;
            gap: 10px;
            padding: 10px;
            width: 100%;
            box-sizing: content-box;
            overflow-y: auto;
            overflow-x: hidden;
            max-height: 70vh;
        }
        .hack-list::-webkit-scrollbar-thumb {
            border: 4px solid rgb(10, 87, 242);
            background-color: rgba(10, 87, 242, .2); 
            border-radius: 5px;
            backdrop-filter: blur(3px);
            box-shadow: none;
            transition: background-color .3s linear, border .3s linear
        }
        .hack-list::-webkit-scrollbar-thumb:hover {
            border: 4px solid #00a2ff;
            background-color: rgba(0, 162, 255, .4); 
        }
        .hack-list::-webkit-scrollbar-track {
            border: 4px solid black;
            background-color: transparent;
            border-radius: 5px;
        }

        #hack-popup, .hack {
            padding: 10px;
            font-size: 20px;
            background-color: #00a2ff;
            border: 7px solid black;
            text-align: center;
            width: 100%;
        }
        
        .hack {
            cursor: pointer;
        }
        .hack:active {
            filter: brightness(0.85);
        }

        .hack-bool {
            transition: background-color .3s ease-in;
            background-color: red;
        }
        .hack-bool[on] {
            background-color: green;
        }
        #hack-tooltip {
            position: absolute;
            backdrop-filter: blur(7px);
            filter: blur(0px);
            transition: all .3s ease-out, left 0s ease, top 0s ease;
            background-color: rgba(0, 0, 0, .5);
            color: white;
            transform: translate(15px, 9.5px);
            width: 200px;
            padding: 10px;
            border-radius: 13px;
            pointer-events: none;
        }
        #hack-lang-select {
            z-index: 90000000001;
            background-color: black;
            color: white;
            border-color: white;
            width: 100%
        }
        #hack-lang-select > option {
            color: white;
        }

        #hack-popup {
            transition: opacity .35s ease-out;
        }
        #hack-popup:hover, #hack-popup[open] {
            opacity: 1;
        }
        #hack-popup:not(:hover):not([open]) {
            opacity: .8;
        }

        #hack-alert-input {
            z-index: 9500000000;
            background-color: rgba(0, 0, 0, .5);
            transition: opacity .3s ease-out;
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            backdrop-filter: blur(5px);
        }
        #hack-alert-input-popup {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            width: 60%;
            height: 50%;
            background-color: black;
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            font-family: verdana;
            text-align: center;
            padding: 20px;
            border: 3px solid white;
            border-radius: 10px;
        }
        #hack-alert-input-popup-title {
            font-size: 2.5em;
        }
        #hack-alert-input-popup-desc {
            font-size: 1.5em;
        }
        #hack-alert-input-popup-ok {
            background-color: #00a2ff;
            border: 4px solid white;
            text-align: center;
            width: 100%;
            height: 50px;
            font-weight: bold;
            cursor: pointer;
        }
        #hack-alert-input-popup-input {
            background-color: black;
            color: white;
            border-color: white;
        }
        .select-in-hack {
            width: 100%;
            border-color: black;
            color: white;
            background-color: black;
        }

    `;
    document.head.append(styles);

    if (!localStorage.getItem("cheats_lang")) {
        localStorage.setItem("cheats_lang", "en");
    }
    
    let $hackmenu = document.getElementById("hack-menu");
    let $hacks = document.getElementById("hacks");
    let $popup = document.getElementById("hack-popup");
    let $tooltip = document.getElementById("hack-tooltip");
    /**@type {HTMLSelectElement} */
    let $langselect = document.getElementById("hack-lang-select");
    let $hack_earnachievement =  document.getElementById("hack-earn-achievement")
    let $hack_revokeachievement =  document.getElementById("hack-revoke-achievement")

    let $inputalert = {
        alert: document.getElementById("hack-alert-input"),
        popup: document.getElementById("hack-alert-input-popup"),
        title: document.getElementById("hack-alert-input-popup-title"),
        desc: document.getElementById("hack-alert-input-popup-desc"),
        input: document.getElementById("hack-alert-input-popup-input"),
        ok: document.getElementById("hack-alert-input-popup-ok")
    }

    let $objecthacks = document.getElementById("objects-hacks")

    let $cookie = document.getElementById("bigCookie");

    dragElement($hackmenu, $popup);

    //boolean hack manager
    $hacks.querySelectorAll(".hack-bool").forEach(node => {
        node.addEventListener("click", (e) => {
            if (node.hasAttribute("on")) {
                node.removeAttribute("on");
            } else {
                node.setAttribute("on", "");
            }
        }) 
    })

    //populate object hacks
    gamePromise.then(() => {
        Object.entries(Game.Objects).forEach(entry => {
            let [key, value] = entry;
            let hack = document.createElement("div");
            hack.className = "hack hack-btn";
            hack.id = `hack-object-amount-${value.bsingle}`;
            hack.setAttribute("lang-string", "set-object-amount");
            hack.setAttribute("supply-val", value.dname);
            hack.setAttribute("key", key);

            hack.onclick = (e) => {
                showInputAlert(hack.innerText, lang.strings["changes-as-you-type"], "number", value.amount, (val) => {
                    value.getFree(+val - value.amount)
                    value.free = 0;
                })
            }

            $objecthacks.append(hack);
        })
        // update language after
        reloadLangs();
    })

    reloadLangs();

    //class loop
    $hackmenu.querySelectorAll(".hack").forEach(_node => {
        /**@type {HTMLElement} */
        let node = _node;

        //tooltip
        node.addEventListener("mousemove", function (e) {
            if (e.target === this) {
                showTooltip(node.id, e.pageX, e.pageY);
            } else if (e.target instanceof HTMLSelectElement) {
                if (e.target.value && e.target.value !== "") {
                    showTooltip(Array.from(this.querySelectorAll("option")).find(opt => opt.value === e.target.value).innerHTML, e.pageX, e.pageY, false);
                }
            } else {
                hideTooltip();
            }
        })
        node.addEventListener("mouseleave", (e) => {
            hideTooltip();
        })
    })

    //menu hide/show
    $popup.addEventListener("click", (e) => {
        if ($hacks.style.display === "none") {
            $hacks.style.display = "";
            $popup.setAttribute("open", "");
        } else {
            $hacks.style.display = "none";
            $popup.removeAttribute("open");
        }
    })

    //add event listener to language select
    $langselect.addEventListener("change", (e) => {
        setLang($langselect.value);
    })

    //select language
    translations.forEach((translation) => {
        let option = document.createElement("option");
        option.value = translation.lang;
        option.innerText = translation.langName;
        $langselect.append(option);
        Logger.LogInfo(`Loaded language ${translation.lang}: ${translation.langName}`);
    })

    //set current language
    $langselect.value = lang.lang;

    // Debug menu hack
    document.getElementById("hack-dev-tools").addEventListener("click", (e) => {
        if (isSilentMode()) {
            if (confirm(lang.strings["dev-tools-confirm"])) {
                Game.OpenSesame();
            }
        } else {
            Game.OpenSesame();
        }
    })

    // Cookie spam hack
    let cookieSpamWorker;

    $cookie.addEventListener("mousedown", (e) => 
    {
        if (document.getElementById("hack-cookie-spam").hasAttribute("on"))
            cookieSpamWorker = setInterval(() => $cookie.dispatchEvent(new Event("click")));
    })

    $cookie.addEventListener("mouseup", (e) => {
        clearInterval(cookieSpamWorker);
    })

    // Autoclicker hack
    let autoclickerWorker;

    document.getElementById("hack-autoclicker").addEventListener("click", (e) => 
    {
        let cookrect = $cookie.getBoundingClientRect();
        if (document.getElementById("hack-autoclicker").hasAttribute("on"))
            autoclickerWorker = setInterval(() => $cookie.dispatchEvent(new Event("click")), 0);
        else 
            clearInterval(autoclickerWorker);
    })

    // Set cookies hack
    document.getElementById("hack-set-cookies").addEventListener("click", (e) => {
        //show input to user
        showInputAlert(lang.hacks["set-cookies"].name, lang.strings["changes-as-you-type"], "number", Game.cookies.toString(), (val) => {
            //earn mode decided by silent mode
            if (isSilentMode()) {
                Game.Earn(-Game.cookies + +val);
            } else {
                Game.cookies = +val;
            }
        })
    });

    // Delete save hack
    document.getElementById("hack-delete-save").addEventListener("click", (e) => {
        //ask user to confirm
        if (confirm(lang.strings["confirm-save-delete"])) {
            //delete save from localstorage
            localStorage.removeItem("CookieClickerGame");
            //after this reload to confirm 
            window.location.reload();
        }
    });
    
    //earn achievement hack
    $hack_earnachievement.addEventListener("click", function (e) {
        //if clicked on select dont gain achevement
        if (e.target !== this) return;
        //gain selected achievement
        Game.Win($hack_earnachievement.querySelector("select").value)
    })
    //revoke achievement hack (simillar to earn)
    $hack_revokeachievement.addEventListener("click", function (e) {
        if (e.target !== this) return;
        Game.RemoveAchiev($hack_revokeachievement.querySelector("select").value)
    })

    gamePromise.then(() => {
        document.getElementById("hack-finish-game").addEventListener("click", (e) => {
            Game.RuinTheFun(true);
        })
    })

    /**
     * @param {string} lang 
     */

    function setLang(lang) {
        localStorage.setItem("cheats_lang", lang);
        reloadLangs();
    }

    function reloadLangs() {
        lang = translations.find((lng) => lng.lang === (localStorage.getItem("cheats_lang") ?? "en"))

        //for every hack
        $hackmenu.querySelectorAll(".hack").forEach(node => {
            //add value
            /**@type {string} */
            let transval = (lang.hacks[node.id.replace("hack-", "")] ?? {name: node.id} ).name;
            if (node.hasAttribute("lang-string")) {
                transval = lang.strings[node.getAttribute("lang-string")];
            }
            if (node.hasAttribute("supply-val")) {
                transval = transval.replace("%s", node.getAttribute("supply-val"));
            }
            node.innerText = (transval ?? node.id ) ?? node.id
        })

        //support for select class
        $hackmenu.querySelectorAll(".hack-select").forEach(node => {
            let select = document.createElement("select");
            select.className = "select-in-hack";
            node.append(select);
        })

        // Earn achevement hack

        //when Game is avaible do
       gamePromise.then(() => {
            // foreach achevement ingame
            Object.entries(Game.Achievements).forEach((entry) => {
                const [key, value] = entry;
                //create option with achievement
                let option = document.createElement("option");
                option.value = key;
                option.innerHTML = value.dname;
                //add it to hack
                $hack_earnachievement.querySelector("select").append(option);
                $hack_revokeachievement.querySelector("select").append(option.cloneNode(true));
            });
        })

        //set current language
        $langselect.value = lang.lang;
    }

    function isSilentMode() {
        return document.getElementById("hack-silent-mode").hasAttribute("on");
    }


    /**
     * 
     * @param {string} id 
     * @param {number} x 
     * @param {number} y 
     */
    function showTooltip(id, x, y, translatable = true) {
        //show only if id have description
        if (translatable) {
            if (!lang.hacks[id.replace("hack-", "")]) return;
            if (!lang.hacks[id.replace("hack-", "")].desc) return;
        }

        $tooltip.innerText = translatable ? lang.hacks[id.replace("hack-", "")].desc ?? "" : id;
        $tooltip.style.left = x.toString() + "px";
        $tooltip.style.top = y.toString() + "px";
        $tooltip.style.opacity = 1;
    }

    function hideTooltip() {
        //$tooltip.innerText = ""; //its needed to be comented because transistion
        $tooltip.style.opacity = 0;
    }

    /**
     * Shows input alert.
     * 
     * @param {string} title 
     * @param {string} desc 
     * @param {string} type 
     * @param {string} value 
     * @param {(value: string) => void} setter 
     */

    function showInputAlert(title, desc, type, value, setter) {
        $inputalert.ok.onclick = () => {
            setTimeout(() => $inputalert.alert.style.display = "none", 301); 
            setter($inputalert.input.value);
            $inputalert.alert.style.opacity = "0";
        }

        $inputalert.title.innerText = title;
        $inputalert.desc.innerText = desc;

        $inputalert.input.type = type;
        $inputalert.input.value = value.toString();
        $inputalert.input.onchange = (e) => setter($inputalert.input.value);

        $inputalert.alert.style.display = "";
        $inputalert.alert.style.opacity = "1";
    }

    /**
     * 
     * @param {HTMLElement} elmnt element to drag 
     * @param {HTMLElement?} dragger element dragging elmnt
     */
    function dragElement(elmnt, dragger) {
        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        if (document.getElementById(elmnt.id + "header") || dragger) {
          // if present, the header is where you move the DIV from:
          (document.getElementById(elmnt.id + "header") ?? dragger).onmousedown = dragMouseDown;
        } else {
          // otherwise, move the DIV from anywhere inside the DIV:
          elmnt.onmousedown = dragMouseDown;
        }
      
        function dragMouseDown(e) {
          e = e || window.event;
          e.preventDefault();
          // get the mouse cursor position at startup:
          pos3 = e.clientX;
          pos4 = e.clientY;
          document.onmouseup = closeDragElement;
          // call a function whenever the cursor moves:
          document.onmousemove = elementDrag;
          if (document.getElementById(elmnt.id + "header") || dragger) {
            (document.getElementById(elmnt.id + "header") ?? dragger).setAttribute("dragging", "");
          }else {
            elmnt.setAttribute("dragging", "");
          }
        }
      
        function elementDrag(e) {
          e = e || window.event;
          e.preventDefault();
          // calculate the new cursor position:
          pos1 = pos3 - e.clientX;
          pos2 = pos4 - e.clientY;
          pos3 = e.clientX;
          pos4 = e.clientY;
          // set the element's new position:
          elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
          elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        }
      
        function closeDragElement() {
          // stop moving when mouse button is released:
          document.onmouseup = null;
          document.onmousemove = null;

          if (document.getElementById(elmnt.id + "header") || dragger) {
            (document.getElementById(elmnt.id + "header") ?? dragger).removeAttribute("dragging");
          }else {
            elmnt.removeAttribute("dragging");
          }
        }
      }
})();