DeepCo Croaker

Queue empty, async unarmed, Layer done, and bonus tooltip alerts with persistent settings, test buttons, and a live log.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         DeepCo Croaker
// @author       M3P / ChatGPT
// @namespace    local
// @license      MIT
// @version      2026.04.14
// @description  Queue empty, async unarmed, Layer done, and bonus tooltip alerts with persistent settings, test buttons, and a live log.
// @match        https://*.deepco.app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deepco.app
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    "use strict";

    /* ----------------- CONFIG ----------------- */
    const DEBUG = false;
    const SETTINGS_KEY = "CROAKER_ALERTS_V2";
    const SOUND_DATA = "data:audio/mpeg;base64,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VAivJFIIEd9yIhvKLOyiMYgMBkYTmhyuNNACgwWDJgYBgEBFABLABVEpYFgAQgGlbkgCV1cnssstZZVsu28+8////////////////rRbcb//BEJszwzuzOzg7ibfb62yNtJhVUUrUWsu4WVIRw9cc9WEtQj84D5bZ+5zDoGVsZqw6URUGJ5hYQAFQGrRakIKjHyY8pLNCJZMyw7/+7DE64Ax7dkh+d4AA9UzIv8xsAAeUBSUgTe0y7XMBHCIRpXaMNCxYUasyKSM6aREaaliEOy95QaEmElhyqEJFaCKKKApGlYNHoFlEojr6yybpZa+67k3C5ibocDSjGWO0/z+hQJwr1r2L7RtWOZV0wZkr0ymtKn77hZ1+d2pEOY49yw58zRyq7D8CP/Hp+UzMo73U3S5fr+ZUdzHvb+8cvabjHeNJh6JTlFj+X575/493l/////9z1jhySczx/v29Vq3S5enhlcQAABX15gIDB0lfCciZQUA921ul2zCkM0FoZupjTsKFSY0+ULemEC5gMAmCwmYcJ5kAmgUNGGQk64OChgEVqUGADyZkzaRw4DTOjlMPgMxCEUk5eaZ2xxcyGEQgYtEBzTymYgO1xWIu2uuLuW5aKBhsdGSzEYKFIOHidQwAy7MLi611NGWUS7FMC+SeogBpggFonAUAMCXag5A0Vf2KRfPTT2QMkU7a0zphxVABgUHGEBLS40tzGPFqggCN1YO1+X35fT3zI4UNVAYHJNNJWxU0H0MBAAEkoMMIgJpL8ynL7kUwxdhrDiZRyHIceuvLYFa9KmkwVEnxssphmHaWrATOrOU7HF3QPDcBMEREcutzOpbp8tbUMVYFAYYBD4gFIWBkMuHJ3Heh7EU2TPkw6o5UTaSoK03K1rlNHca3//P///////////////56GJFV////////////////pthUVMxMzcEHv7K3lvUqGD/Ti+oWuifZZEQfD9Kr7vOO4a9N1Zps2NLALtNeZc0piRhuHawZAYGVWbDENUcnyxdp3r7vRbHcczy7buaz//7oMTXgCxuDSn5vgJKkySkP5+QBHGaTtPYqsplsrm4ams/3ylt5Sv+P8/3ea/CSdrZ7x+ZlTky2X38uc3ZxmcJi3GsOb7XlnL9/X63Q3qaV9pvpqaxudsYR/CPUdg/U8spbIowUrAhADSh1aK0VZqXiquoIL7Sy3SsY2JbZM792wsLLFLAN1LE0QhKsLLVclnJjeWejDGTpjF0V5bE7JEpjd1E9PRx7t67hWk3eBDg1mjwWpIIpsrHw22zqA41bZfj5nz923Dlh2kkXBMj+WI8HbGxseLWgwIUt3OBakPFrb3H16t7na2Na+o0uN3rNX1tH3iXVIG6MDhSWvxqlt7jxKbh+9LZpesPd96prGdatSla0pSmsv36sVndX9ebeXSOCoH0+fAkGg9BhgoY0BGOpRwzaDFU20uQ3MWJDRhky1qKiOXGMWJjJAo0lQMZMQSRBRkMvlEyOKTBYyMnjox4VTVwSqGCQyBh2Y/Ghg1LmUnQWA0YCBBiWtFpDF4RKoAEgIb7SYhIhh0AKPmjwuY4EMh0YbBapkrDCIHQHmAw0YPByURdYEgRlr2W0OyYU8YTCJhMGo1mBwGYBAZgcGF7RwJGFQozlIkcApeZJ2OvZKy9YGBDTW3MBAIwAAFIUEljMVjSaCXE9OMaZMKggwqJDAYHTqMCAJoD+RiHH/f+RmYSEYGCpf19HP/7sMTNgBV9oyP094AmHsImvzfACG5yYZEYWE5eZfj+xjn6j8lcuB4IRUbxMRdj8QO0hzXkedL5YdDWLz7utaXdD0Tsu/YjL/XWcuE2Veixi9DW08C66fctht+7D6RRaCARuIQFzGo1MTBwwAAVWrGVLGaCMUzcVlMrMBBFWZjziL1gfWXZ2bkMfn+Xuc////////////////+dlvM////////////////5LM7f5d/MzNzEAHtqN9sifIeU1Nh2pGc4zZisCSmIOstpQJl0ZYK8MUyiT/P9WpaKVMCAIC0RjAaYGsSpcX2dqNX8efTVcJ2Uy2K0u7+OXcu67vVrKGlTO9DUqrXcbmss7mVb/yyyppdayx1KpdnXlMmmY3EXFd1/Wuu7g/07x/n+hqjxxx///fN1bNNa/v7q0tL3GrS83v8sccccf3ytKYzS2caWzWpv3j++Vn+h5/YdoJ6Ov7DuOOX/vHHHHH8ccf+Zhl/Y13HHHGZjMMy6VXdVV1kwAfmVtC4H++TbTMwuosNEGjdfwxicAmBCcbZkVSBkqWCLvoFEWcR4hQWWC8Tgt4kpaUbpkieImkVioShBi7SR2Q1mLqNyLDBG4QUhiZMJl9NMumqBdfUaoyezjGCyZHARYiRFRYxkSqTxmoaJVK5iakHWaMiUn84ifaormiBoXDZqL0CnKZvums+tKvQOumYMZv63pXmobHinmBICA/XQIXAkSUHYYga7Jp6GTyesTgJ1HAQeQx/Tgye+xA3eT9JIvDvBYKSAJgggdRAGLiAUC3hEYiZGPs0LomkLk+0gdJTAg4hH2P0g99GVDxjAtDIMGEoi//uQxOIAGX2jL/2MACJiqOT+nwAF6K0H3buv9WOmNBIQKJGXDxkRcCB97WHMLjSXr8IestL8LszSvMjES+iZhMMAUBbBDoYByFxYw5Etd516BuC6S46earXXaihmDgNV6dT1sQaTHl2KvksC2YZzbm473rwWYXbSHYw0BKxjary4yJEMWYeePbPXid+L134zlEP9/mRETskXWGB6Gjf0vf1SUDnl2Hnta///+Z/Yz7ykxt5/8vywUdQWL8Q0sUBC5h4KjOmepepmpMxUNKAbdj5XIJzKXTNyX9iDYIrhz/////////////////oVeY/////////////////2iub/P63MAwAALbNUhBkNSbSJZEIEVZ3N5AShOMwNMAV6sxTUVtZrStilWC7m2wmneUqaEvExEM2RcuMABbLmlQNMU8ZyluMa5e3ZlNjdjHHHeNmhnZp2X9mI6wLGzS4b7vHHGrhj3eOOOPP///dmDWuyhOZMWSR1drsw7S445Zf/4444/+OOP81KozZvRJ/oahq7OwzFZmlpaXH/s9/99//////5//ugxPWAKNoNO/mdpANAtKZ7saAAqVSmkhqRVbNa1nVs8q2e8//3jjjhlnWlMt1llTdqymmylTtO9Go0/0qpbOL//u7/xxE9UclCyQsA7mIlS2DZDUcCCZJLJM3CC3xZ7AhKIQ9ARIAdFwk9i1TAIxZLBJDbghNCgZCQpeeL66hFJqEhakp8zOu468tDoHQA99atv++8/qMsjOGZ6cDwFQVITQhFiICmVL89dm///lor1e5EqDKwKjpMoGkwDB0jkgQ6oiJkOs5/////sCWNoc9IirZLlWhjUJZX/8VXx/8v02v/QqmITMubebmbKCF1K1NKYBtialKwJR4oh1uCmdI9QkHSS7WGhPDqeuc6JiNsNTq9LCaCkXU6lZ0kokOUM1mJWEvV7C7mVqNdK7/+ZLWj6IiIDYbrGZDH1jjq/qRZIVnFcItXIUZImIhTIyYjC10fRMQci6f6qjm1I00uWUmmzUu3+WQ4URKTRqEC7Zsnenbmv1/7QU793//SVOZFJGPkByKENb/////zK7SOzdu8zJcQ+pW718WcSkAfB/0U6R6vZnJh0nHHT0/Rwj0sJysq9CgxemBJiHhwhumk4K46mZ8+fWsfuWpqcp1croUX/5x/Xtz9EikI5roxPYOoMX//9gqQD2N18Y5M1FuxqvMolTToud/RGVEkvyqjSLM/r860ZLBQCgOi//uAxOSAFP2fN+wxL6qXsmW89adM0RQei04kKhsNSP5p3/OJK1C41FJA443/8VA5MJO7s8M8RCBeSyAc0jLVWmkteZmCRKZFAJqwqGcihQlBUjBEoKsFT8//7yElLE00LK2fysMylB8DoaAWHqrSv//FkodNfK7Wq0te1z1rTObDesWqqgtTNMNK1KrAslFVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+2DE6YATBZUt55j+oXUjI7xkoiVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=";
    const WATCHDOG_TIMEOUT = 500;   // seconds before forcing a check
    const WATCHDOG_INTERVAL = 100;  // how often watchdog scans

    /* ---------------- DEFAULT ALERTS ---------------- */

    const DEFAULT_ALERTS = {

        QUEUE_EMPTY: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Queue Empty",
            initialDelay: 1000,
            repeat: 10000
        },

        ASYNC_UNARMED: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Async Unarmed",
            repeat: 20000
        },

        LAYER_COMPLETE: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Layer Completed",
            repeat: 20000
        },

        BONUS_TOOLTIP: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Bonus Tooltip"
        }

    };
    const WATCHDOG = {
        checkQueue: { last: Date.now(), fn: checkQueue },
        checkAsync: { last: Date.now(), fn: checkAsync },
        checkLayer: { last: Date.now(), fn: checkLayer },
        checkBonus: { last: Date.now(), fn: checkBonus }
    };
    /* ---------------- UTIL ---------------- */

    const clone = v => JSON.parse(JSON.stringify(v));

    function gmGet(k,f){try{return GM_getValue(k,f);}catch{return f}}
    function gmSet(k,v){try{GM_setValue(k,v);}catch{}}

    /* ---------------- SETTINGS LOAD ---------------- */

    let ALERTS = gmGet(SETTINGS_KEY,null);

    if(!ALERTS){

        ALERTS = clone(DEFAULT_ALERTS);
        gmSet(SETTINGS_KEY,ALERTS);

    }else{

        for(const k in DEFAULT_ALERTS){

            if(!ALERTS[k]) ALERTS[k] = clone(DEFAULT_ALERTS[k]);

            for(const f in DEFAULT_ALERTS[k]){
                if(!(f in ALERTS[k])) ALERTS[k][f]=DEFAULT_ALERTS[k][f];
            }

        }

        gmSet(SETTINGS_KEY,ALERTS);

    }

    let saveTimer=null;

    function saveAlerts(){

        clearTimeout(saveTimer);

        saveTimer=setTimeout(()=>{

            gmSet(SETTINGS_KEY,ALERTS);

        },300);

    }

    /* ---------------- AUDIO CACHE ---------------- */

    const AUDIO_CACHE = new Map();

    function getAudio(src){

        if(!AUDIO_CACHE.has(src)){

            const audio = new Audio(src);
            audio.preload="auto";

            AUDIO_CACHE.set(src,audio);

        }

        return AUDIO_CACHE.get(src);

    }

    function playSound(alert,reason){

        try{

            const base = getAudio(alert.sound || SOUND_DATA);

            const audio = base.cloneNode();

            audio.volume = alert.volume ?? 1;
            audio.playbackRate = alert.speed ?? 1;

            audio.play().catch(console.warn);

            croaker(`[${alert.message}]`,1);

            log(`PLAY ${alert.message}${reason?` (${reason})`:''}`);

        }catch(e){

            console.warn(e);
            log("Audio error");

        }

    }

    /* ---------------- LOG ---------------- */

    const LOG=[];

    function log(msg){

        const line = new Date().toLocaleTimeString()+" "+msg;
        if (DEBUG) console.log(line);
        LOG.push(line);
        if(LOG.length>200)LOG.shift();

        const el=document.getElementById("croaker-log");
        if(el) el.textContent = LOG.slice().reverse().join("\n");

    }

    /* ---------------- CROAKER HEADER ---------------- */

    function croaker(message,delay){

        const anchor=document.querySelector("header.navbar div.flex-1 a");
        if(!anchor)return;

        let span=anchor.querySelector("span.croaker");

        if(!span){

            span=document.createElement("span");
            span.className="croaker";
            span.className="valuable";
            anchor.appendChild(span);

        }

        if(span.textContent) return;

        span.textContent=message;

        setTimeout(()=>{

            if(span.textContent===message) span.textContent="";

        },delay*1000);

    }

    /* ---------------- ALERT STATE ---------------- */

    const ALERT_STATE={};

    for(const k in ALERTS){

        ALERT_STATE[k]={active:false,timer:null,repeat:null};

    }

    let bonusArmed=true;

    /* ---------------- ALERT CONTROL ---------------- */

    function startAlert(key){

        const a=ALERTS[key];
        const s=ALERT_STATE[key];

        if(!a.enabled || s.active) return;

        s.active=true;

        const fire=()=>playSound(a,key);

        if(a.initialDelay){

            s.timer=setTimeout(()=>{

                fire();

                if(a.repeat) s.repeat=setInterval(fire,a.repeat);

            },a.initialDelay);

        }else{

            fire();

            if(a.repeat) s.repeat=setInterval(fire,a.repeat);

        }

        log("START "+key);

    }

    function stopAlert(key) {
        const s = ALERT_STATE[key];
        if (!s || (!s.timer && !s.repeat && !s.active)) return; // skip if nothing to stop

        if (s.timer) clearTimeout(s.timer);
        if (s.repeat) clearInterval(s.repeat);

        s.timer = null;
        s.repeat = null;
        s.active = false;

        log("STOP " + key);
    }
    /*    function stopAlert(key){

        const s=ALERT_STATE[key];

        if(s.timer)clearTimeout(s.timer);
        if(s.repeat)clearInterval(s.repeat);

        s.timer=null;
        s.repeat=null;
        s.active=false;

        log("STOP "+key);

    }
*/
    /* ---------------- CONDITIONS ---------------- */

    function queueEmpty(){

        const t=document.title.toLowerCase();
        return !t.includes("queue") && !t.includes("async") ;

    }

    function asyncActive(){
        const t=document.title.toLowerCase();
        return t.includes("async");
    }

    function countTileWrappers() {
        return document.querySelectorAll('div[id^="tile_wrapper_"]').length;
    }

    function countQueuedTiles() {
        return document.querySelectorAll('div[id^="tile_inner_"].you-are-queued-here').length;
    }

    function countMiningTiles() {
        return document.querySelectorAll('div[id^="tile_inner_"].you-are-mining-here').length;
    }

    function layerComplete() {
        return document.querySelector('div#layer-complete-text') !== null
    }

    function checkLayer() {
        if (DEBUG) console.log('------- layerComplete - ' + layerComplete());
        if (layerComplete()) {
            startAlert("LAYER_COMPLETE");
        } else {
            stopAlert("LAYER_COMPLETE");
        }
    }

    function checkQueue(){
        if (DEBUG) console.log(' ===== check queue');
        //console.log('CkQ  layer-' + layerComplete() + ' queueempty-' + queueEmpty() + ' async-' + asyncActive() + ' queuedtiles-' + countQueuedTiles() + ' miningtiles-' + countMiningTiles() + ' tilescnt-' + countTileWrappers());

        if((queueEmpty() && countTileWrappers() > 0 && countMiningTiles() == 0) && !layerComplete() ) startAlert("QUEUE_EMPTY");
        else stopAlert("QUEUE_EMPTY");
    }

    /*     function checkAsync(){
        if (DEBUG) console.log(' //// check async');
        const el=document.querySelector('span[data-role="auto-async-indicator"]');
        if(!el)return;

        const t=document.title.toLowerCase();

        if(el.classList.contains("hidden") && !t.includes("async"))
            startAlert("ASYNC_UNARMED");
        else
            stopAlert("ASYNC_UNARMED");

    }
 */

    function checkAsync(){
        if (DEBUG) console.log(' //// check async');

        const header = document.querySelector('div#auto-async-header');
        if (!header) return;

        const hasPending = Array.from(header.querySelectorAll('span'))
        .some(s => s.textContent.includes('[PENDING]'));

        const t = document.title.toLowerCase();

        if (!hasPending && !t.includes("async")) {
            startAlert("ASYNC_UNARMED");
        } else {
            stopAlert("ASYNC_UNARMED");
        }
    }
    function checkBonus(){
        if (DEBUG) console.log(' @@@@ check bonus');
        const panel=document.querySelector("turbo-frame#bonus-panel");
        if(!panel)return;

        const tooltip=panel.querySelector("div.tooltip:not(.bonus-active)");

        if(tooltip){
            if(bonusArmed){
                playSound(ALERTS.BONUS_TOOLTIP,"BONUS");
                bonusArmed=false;
            }
        }else{
            bonusArmed=true;
        }
    }

    /* ---------------- UI ---------------- */

    GM_addStyle(`
#croaker-gear{position:fixed;bottom:10px;left:10px;background:#222;color:#fff;padding:6px;border-radius:6px;cursor:pointer;z-index:9999}
#croaker-panel{position:fixed;bottom:50px;left:10px;background:#111;color:#eee;padding:10px;border-radius:8px;width:320px;max-height:60vh;overflow:auto;z-index:9999}
#croaker-panel input[type=range]{width:120px}
#croaker-log{white-space:pre;font-family:monospace;font-size:11px;background:#000;padding:6px;margin-top:6px;max-height:150px;overflow:auto}
button{cursor:pointer}
`);

    function buildUI(){

        const gear=document.createElement("div");
        gear.id="croaker-gear";
        gear.textContent="⚙";

        const panel=document.createElement("div");
        panel.id="croaker-panel";
        panel.style.display="none";

        document.body.appendChild(gear);
        document.body.appendChild(panel);

        gear.onclick=()=>{

            panel.style.display = panel.style.display==="none"?"block":"none";

        };

        for(const key in ALERTS){

            const a=ALERTS[key];

            const row=document.createElement("div");

            row.innerHTML=`
<label>
<input type="checkbox" ${a.enabled?"checked":""}> <b>${key}</b>
</label>

<br>vol <input type="range" min="0" max="1" step="0.05" value="${a.volume}">
<br>spd <input type="range" min="0.5" max="2" step="0.1" value="${a.speed}">
<button class="test">Test</button>

<div>
<input class="url" placeholder="Sound URL" style="width:90%">
<input type="file" class="file" accept="audio/*">
<button class="default">Default</button>
</div>
<br>
`;

            const checkbox=row.querySelector("input[type=checkbox]");
            const sliders=row.querySelectorAll("input[type=range]");
            const test=row.querySelector(".test");
            const url=row.querySelector(".url");
            const file=row.querySelector(".file");
            const def=row.querySelector(".default");

            checkbox.onchange=()=>{

                a.enabled=checkbox.checked;
                saveAlerts();

            };

            sliders[0].oninput=()=>{

                a.volume=parseFloat(sliders[0].value);
                saveAlerts();

            };

            sliders[1].oninput=()=>{

                a.speed=parseFloat(sliders[1].value);
                saveAlerts();

            };

            test.onclick=()=>playSound(a,"TEST");

            url.onchange=()=>{

                a.sound=url.value.trim();
                a.soundType="url";

                saveAlerts();
                log(key+" URL sound");

            };

            file.onchange=()=>{

                const reader=new FileReader();

                reader.onload=e=>{

                    a.sound=e.target.result;
                    a.soundType="file";

                    AUDIO_CACHE.delete(a.sound);

                    saveAlerts();
                    log(key+" file sound");

                };

                reader.readAsDataURL(file.files[0]);

            };

            def.onclick=()=>{

                a.sound=DEFAULT_ALERTS[key].sound;
                a.soundType="base64";

                saveAlerts();
                log(key+" default sound");

            };

            panel.appendChild(row);

        }

        const logBox=document.createElement("div");
        logBox.id="croaker-log";
        panel.appendChild(logBox);

    }

    setInterval(()=>{
        const now = Date.now();
        for(const key in WATCHDOG){
            const w = WATCHDOG[key];
            if(now - w.last > WATCHDOG_TIMEOUT){
                log("WATCHDOG fired: "+key);
                w.last = now;
                try{
                    w.fn();
                }catch(e){
                    console.warn("watchdog error",key,e);
                }
            }
        }
    }, WATCHDOG_INTERVAL);
    /* ---------------- INIT ---------------- */

    buildUI();

    checkQueue();
    checkAsync();
    checkBonus();
    checkLayer();

    log("Croaker ready");

})();