IdlePixel - Criptoe Bulk Invest

Invest more than 10,000,000 criptoe in one go. Also accepts a percentage of your uninvested balance ("50%") and adds 25/50/75/100% quick buttons.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

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

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         IdlePixel - Criptoe Bulk Invest
// @namespace    com.eisa.idlepixel
// @version      1.0.0
// @description  Invest more than 10,000,000 criptoe in one go. Also accepts a percentage of your uninvested balance ("50%") and adds 25/50/75/100% quick buttons.
// @author       Eisa (StoneSinew)
// @license      MIT
// @match        *://idle-pixel.com/login/play*
// @grant        none
// @icon         https://cdn.idle-pixel.com/images/criptoe_coin.png
// @require      https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==

(function () {
    "use strict";

    const PLUGIN_ID   = "criptoe_bulk_invest";
    const STORAGE_KEY = "criptoe_bulk_invest_config";
    const IMG         = "https://cdn.idle-pixel.com/images/";

    const WIRE_MAX = 10000000;

    const DEFAULT_INTERVAL_S = 5;
    const MIN_INTERVAL_S     = 1;
    const MAX_INTERVAL_S     = 120;

    const MAX_SENDS = 500000;

    const WALLETS = [1, 2, 3, 4];

    class CriptoeBulkInvestPlugin extends IdlePixelPlusPlugin {
        constructor() {
            super(PLUGIN_ID, {
                about: {
                    name: GM_info.script.name,
                    version: GM_info.script.version,
                    author: GM_info.script.author,
                    description: GM_info.script.description,
                },
            });
            this.cfg = this.loadCfg();
            this.dlg = null;
            this.queue = null;
            this._timer = null;
            this._ui = null;
            this._origInvest = null;
        }

        loadCfg() {
            try {
                const raw = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
                const cfg = Object.assign({ intervalS: DEFAULT_INTERVAL_S }, raw);
                cfg.intervalS = this.clampInterval(cfg.intervalS);
                return cfg;
            } catch (e) { return { intervalS: DEFAULT_INTERVAL_S }; }
        }
        saveCfg() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.cfg)); } catch (e) {  } }
        clampInterval(v) {
            let n = parseFloat(v);
            if (!isFinite(n)) n = DEFAULT_INTERVAL_S;
            if (n < MIN_INTERVAL_S) n = MIN_INTERVAL_S;
            if (n > MAX_INTERVAL_S) n = MAX_INTERVAL_S;
            return n;
        }
        intervalMs() { return Math.round(this.clampInterval(this.cfg.intervalS) * 1000); }

        num(k) { try { return IdlePixelPlus.getVarOrDefault(k, 0, "int"); } catch (e) { return 0; } }
        bal() { return Math.max(0, this.num("criptoe")); }
        invested(n) { return this.num("wallet" + n + "_invested"); }
        commas(n) { return String(Math.floor(Number(n) || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); }
        esc(s) { return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }

        isSundayUtc() { try { return new Date().getUTCDay() === 0; } catch (e) { return false; } }

        parseAmount(raw, bal) {
            const s = String(raw == null ? "" : raw).trim().toLowerCase().replace(/[,\s_]/g, "");
            if (!s) return { error: "enter an amount" };
            if (s.charAt(s.length - 1) === "%") {
                const pct = parseFloat(s.slice(0, -1));
                if (!isFinite(pct) || pct <= 0) return { error: "percent must be greater than 0" };
                if (pct > 100) return { error: "percent can't be above 100" };
                return { amount: Math.floor(bal * pct / 100), pct: pct };
            }
            const m = /^(\d+(?:\.\d+)?)([kmb])?$/.exec(s);
            if (!m) return { error: "not a number — try 25000000, 25m or 50%" };
            let v = parseFloat(m[1]);
            if (m[2] === "k") v *= 1e3;
            else if (m[2] === "m") v *= 1e6;
            else if (m[2] === "b") v *= 1e9;
            v = Math.floor(v);
            if (!isFinite(v) || v <= 0) return { error: "amount must be greater than 0" };
            return { amount: v };
        }

        planChunks(total) {
            const out = [];
            let left = Math.floor(total);
            while (left > 0 && out.length < MAX_SENDS) {
                const c = Math.min(WIRE_MAX, left);
                out.push(c);
                left -= c;
            }
            return { chunks: out, truncated: left > 0, dropped: left };
        }

        etaText(sendCount) {
            const secs = Math.max(0, sendCount - 1) * this.clampInterval(this.cfg.intervalS);
            if (secs <= 0) return "instant";
            if (secs < 60) return "~" + Math.round(secs) + "s";
            const m = Math.floor(secs / 60), s = Math.round(secs % 60);
            return "~" + m + "m" + (s ? " " + s + "s" : "");
        }

        onLogin() {
            try { this.hookInvestDialogue(); } catch (e) { console.error("Criptoe Bulk Invest: hook failed", e); }
            try { this.injectStyle(); } catch (e) { console.error("Criptoe Bulk Invest: style failed", e); }
            try { this.injectPanelBox(); } catch (e) { console.error("Criptoe Bulk Invest: panel inject failed", e); }
        }
        onPanelChanged() {
            try { this.injectPanelBox(); } catch (e) {  }
        }
        onVariableSet(key) {
            if (key === "criptoe" || /^wallet[1-4]_invested$/.test(String(key))) {
                if (this.dlg) this.refreshDialog();
                if (this.queue) this.renderProgress();
                this.refreshPanelBox();
            }
        }
        onConfigsChanged() {}

        hookInvestDialogue() {
            if (typeof Modals === "undefined" || !Modals || typeof Modals.open_criptoe_invest_dialogue !== "function") {
                console.warn("Criptoe Bulk Invest: Modals.open_criptoe_invest_dialogue not found — leaving the game's dialogue alone.");
                return;
            }
            if (this._origInvest) return;
            this._origInvest = Modals.open_criptoe_invest_dialogue;
            const self = this;
            Modals.open_criptoe_invest_dialogue = function (walletId) {
                try { self.openDialog(parseInt(walletId, 10)); }
                catch (e) {
                    console.error("Criptoe Bulk Invest: dialog failed, falling back to native dialog", e);
                    try { self._origInvest.call(Modals, walletId); } catch (e2) {  }
                }
            };
        }

        injectStyle() {
            if (document.getElementById("cbi-style")) return;
            const st = document.createElement("style");
            st.id = "cbi-style";
            st.textContent =
                "#cbi-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:20000;display:flex;align-items:center;justify-content:center;}" +
                "#cbi-dialog{background:#343e59;color:#fff;border:1px solid #6d7ba0;border-radius:10px;padding:18px 20px;width:min(560px,92vw);max-height:90vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);}" +
                "#cbi-dialog h3{margin:0 0 4px 0;font-size:1.15em;}" +
                "#cbi-dialog .cbi-sub{color:#b9c2d8;font-size:.86em;margin-bottom:12px;}" +
                "#cbi-dialog .cbi-bal{background:#2b2d3e;border-radius:6px;padding:8px 10px;margin-bottom:12px;font-size:.92em;}" +
                "#cbi-dialog .cbi-bal b{color:#ffd700;}" +
                "#cbi-dialog input[type=text]{color:#000;width:100%;box-sizing:border-box;padding:7px 9px;border-radius:6px;border:1px solid #888;font-size:1.05em;}" +
                "#cbi-box input[type=number]{color:#000;width:78px;padding:4px 6px;border-radius:5px;border:1px solid #888;}" +
                "#cbi-row-pct{display:flex;gap:8px;flex-wrap:wrap;margin-top:9px;}" +
                ".cbi-btn{cursor:pointer;padding:5px 13px;border:1px solid #8a97bd;border-radius:6px;background:#3f4a69;display:inline-block;user-select:none;font-size:.92em;}" +
                ".cbi-btn:hover{background:#4d5a80;}" +
                ".cbi-btn.cbi-primary{background:#2e7d32;border-color:#4caf50;}" +
                ".cbi-btn.cbi-primary:hover{background:#388e3c;}" +
                ".cbi-btn.cbi-danger{background:#8e2f2f;border-color:#c05c5c;}" +
                ".cbi-btn.cbi-danger:hover{background:#a33a3a;}" +
                ".cbi-btn.cbi-off{opacity:.45;cursor:not-allowed;}" +
                ".cbi-warn{color:#ffb347;} .cbi-err{color:#ff8080;} .cbi-ok{color:#9ee493;}" +
                "#cbi-preview{margin-top:12px;background:#2b2d3e;border-radius:6px;padding:9px 11px;font-size:.9em;line-height:1.5;min-height:2.4em;}" +
                "#cbi-dialog .cbi-note{color:#b9c2d8;font-size:.82em;margin-top:10px;line-height:1.45;}" +
                "#cbi-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:14px;}" +
                "#cbi-progress{position:fixed;right:18px;bottom:18px;z-index:19500;background:#343e59;color:#fff;border:1px solid #6d7ba0;border-radius:10px;padding:12px 14px;width:300px;box-shadow:0 6px 26px rgba(0,0,0,.55);font-size:.9em;}" +
                "#cbi-progress .cbi-ptitle{font-weight:bold;margin-bottom:6px;}" +
                "#cbi-progress .cbi-bar{height:7px;background:#22243a;border-radius:4px;overflow:hidden;margin:8px 0;}" +
                "#cbi-progress .cbi-fill{height:100%;background:#4caf50;transition:width .25s;}" +
                "#cbi-progress .cbi-pline{color:#c8d0e4;line-height:1.5;}" +
                "#cbi-progress .cbi-pdone{color:#9ee493;} #cbi-progress .cbi-pstop{color:#ffb347;}" +
                "#cbi-progress .cbi-pactions{display:flex;gap:8px;justify-content:flex-end;margin-top:9px;}" +
                "#cbi-box{margin:14px 0;padding:12px 14px;border:1px solid #666;border-radius:8px;max-width:820px;background:rgba(0,0,0,.18);}" +
                "#cbi-box .cbi-btitle{font-weight:bold;margin-bottom:5px;}" +
                "#cbi-box .cbi-brow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:8px;}" +
                "#cbi-box .cbi-bnote{color:#b9c2d8;font-size:.85em;line-height:1.5;}";
            document.head.appendChild(st);
        }

        injectPanelBox() {
            const panel = document.getElementById("panel-criptoe-market");
            if (!panel || document.getElementById("cbi-box")) return;
            const box = document.createElement("div");
            box.id = "cbi-box";
            box.innerHTML =
                '<div class="cbi-btitle"><img src="' + IMG + 'criptoe_coin.png" style="height:18px;vertical-align:middle;"> Criptoe Bulk Invest</div>' +
                '<div class="cbi-bnote">A single quantity entry is capped at ' + this.commas(WIRE_MAX) + '. Use <b>Add Criptoe</b> as normal and enter any larger amount,' +
                'it is split into back-to-back investments of at most ' + this.commas(WIRE_MAX) + ' each. You can also enter a percentage of your uninvested balance, like <b>50%</b>.</div>' +
                '<div class="cbi-brow">' +
                '<label>Seconds between sends: <input id="cbi-interval" type="number" min="' + MIN_INTERVAL_S + '" max="' + MAX_INTERVAL_S + '" step="0.5" value="' + this.cfg.intervalS + '"></label>' +
                '<span class="cbi-bnote">Criptoe investing is rate-limited, raise this number if sends start getting dropped.</span>' +
                '</div>' +
                '<div class="cbi-brow"><span id="cbi-box-status" class="cbi-bnote"></span></div>';
            const anchor = panel.querySelector(".charts-content");
            if (anchor) panel.insertBefore(box, anchor); else panel.appendChild(box);

            const self = this;
            const inp = box.querySelector("#cbi-interval");
            inp.addEventListener("change", function () {
                const v = self.clampInterval(this.value);
                this.value = v;
                self.cfg.intervalS = v;
                self.saveCfg();
                self.refreshPanelBox();
            });
            this.refreshPanelBox();
        }
        refreshPanelBox() {
            const el = document.getElementById("cbi-box-status");
            if (!el) return;
            const b = this.bal();
            let txt = "Uninvested right now: " + this.commas(b) + " criptoe";
            if (b > WIRE_MAX) txt += " — that is " + this.planChunks(b).chunks.length + " send(s) to invest in full.";
            if (this.queue) txt += "  ·  a queue is running (see the panel in the corner).";
            el.textContent = txt;
        }

        openDialog(walletId) {
            const w = WALLETS.indexOf(walletId) >= 0 ? walletId : 1;
            this.closeDialog();
            this.injectStyle();
            this.dlg = { wallet: w };

            const back = document.createElement("div");
            back.id = "cbi-backdrop";
            back.innerHTML =
                '<div id="cbi-dialog" role="dialog" aria-modal="true">' +
                    '<h3><img src="' + IMG + 'criptoe_coin.png" style="height:20px;vertical-align:middle;"> Add Investment — Wallet ' + w + '</h3>' +
                    '<div class="cbi-sub">Amounts above ' + this.commas(WIRE_MAX) + ' are sent as several investments in a row.</div>' +
                    '<div class="cbi-bal" id="cbi-balline"></div>' +
                    '<input type="text" id="cbi-amount" placeholder="e.g. 25000000, 25m, or 50%" autocomplete="off">' +
                    '<div id="cbi-row-pct">' +
                        '<span class="cbi-btn cbi-pct" data-pct="25">25%</span>' +
                        '<span class="cbi-btn cbi-pct" data-pct="33">33%</span>' +
                        '<span class="cbi-btn cbi-pct" data-pct="50">50%</span>' +
                        '<span class="cbi-btn cbi-pct" data-pct="75">75%</span>' +
                        '<span class="cbi-btn cbi-pct" data-pct="100">100%</span>' +
                    '</div>' +
                    '<div id="cbi-preview"></div>' +
                    '<div class="cbi-note">' +
                        '<b>Note that it is impossible to withdraw criptoe from a wallet you invested in on the same day.</b>' +
                        (this.isSundayUtc() ? '<br><span class="cbi-warn">It is Sunday (UTC) — wallet percentages are not published today.</span>' : '') +
                    '</div>' +
                    '<div id="cbi-actions">' +
                        '<span class="cbi-btn" id="cbi-cancel">Cancel</span>' +
                        '<span class="cbi-btn cbi-primary" id="cbi-go">Invest</span>' +
                    '</div>' +
                '</div>';
            document.body.appendChild(back);

            const self = this;
            const input = back.querySelector("#cbi-amount");
            back.querySelectorAll(".cbi-pct").forEach(function (b) {
                b.addEventListener("click", function () {
                    input.value = this.getAttribute("data-pct") + "%";
                    self.refreshDialog();
                    input.focus();
                });
            });
            input.addEventListener("input", function () { self.refreshDialog(); });
            input.addEventListener("keydown", function (ev) {
                if (ev.key === "Enter") { ev.preventDefault(); self.submitDialog(); }
                else if (ev.key === "Escape") { ev.preventDefault(); self.closeDialog(); }
            });
            back.querySelector("#cbi-cancel").addEventListener("click", function () { self.closeDialog(); });
            back.querySelector("#cbi-go").addEventListener("click", function () { self.submitDialog(); });
            back.addEventListener("mousedown", function (ev) { if (ev.target === back) self.closeDialog(); });

            this.refreshDialog();
            try { input.focus(); } catch (e) {  }
        }

        closeDialog() {
            const el = document.getElementById("cbi-backdrop");
            if (el && el.parentNode) el.parentNode.removeChild(el);
            this.dlg = null;
        }

        evaluate() {
            const bal = this.bal();
            const inp = document.getElementById("cbi-amount");
            const parsed = this.parseAmount(inp ? inp.value : "", bal);
            if (parsed.error) return { bal: bal, error: parsed.error };
            if (bal <= 0) return { bal: bal, error: "you have no uninvested criptoe" };

            let amount = parsed.amount;
            let clamped = false;
            if (amount > bal) { amount = bal; clamped = true; }
            if (amount <= 0) return { bal: bal, error: "that rounds down to 0 criptoe" };

            const plan = this.planChunks(amount);
            return {
                bal: bal, amount: amount, clamped: clamped, pct: parsed.pct,
                chunks: plan.chunks, truncated: plan.truncated, dropped: plan.dropped,
            };
        }

        refreshDialog() {
            if (!this.dlg) return;
            const w = this.dlg.wallet;
            const balLine = document.getElementById("cbi-balline");
            if (balLine) {
                balLine.innerHTML =
                    "Uninvested: <b>" + this.commas(this.bal()) + "</b> criptoe" +
                    " &nbsp;·&nbsp; already in wallet " + w + ": <b>" + this.commas(this.invested(w)) + "</b>";
            }

            const pv = document.getElementById("cbi-preview");
            const go = document.getElementById("cbi-go");
            if (!pv) return;
            const ev = this.evaluate();

            if (ev.error) {
                pv.innerHTML = '<span class="cbi-err">' + this.esc(ev.error) + "</span>";
                if (go) go.classList.add("cbi-off");
                return;
            }
            if (go) go.classList.remove("cbi-off");

            const n = ev.chunks.length;
            let html = '<span class="cbi-ok">Investing ' + this.commas(ev.amount) + " criptoe</span> into wallet " + w + "<br>";
            if (n === 1) {
                html += "1 send — within the " + this.commas(WIRE_MAX) + " cap.";
            } else {
                const full = ev.chunks.filter(function (c) { return c === WIRE_MAX; }).length;
                const rem = ev.chunks[n - 1] === WIRE_MAX ? 0 : ev.chunks[n - 1];
                html += "<b>" + n + " sends</b> — " + full + " x " + this.commas(WIRE_MAX) +
                        (rem ? " then " + this.commas(rem) : "") +
                        " &nbsp;·&nbsp; " + this.etaText(n) + " at " + this.clampInterval(this.cfg.intervalS) + "s apart";
            }
            if (ev.pct) html += '<br><span class="cbi-warn">' + ev.pct + "% of your " + this.commas(ev.bal) + " uninvested balance.</span>";
            if (ev.clamped) html += '<br><span class="cbi-warn">Reduced to your full uninvested balance of ' + this.commas(ev.bal) + ".</span>";
            if (ev.truncated) html += '<br><span class="cbi-warn">Capped at ' + MAX_SENDS + " sends — " + this.commas(ev.dropped) + " criptoe left over. Run it again afterwards.</span>";
            pv.innerHTML = html;
        }

        submitDialog() {
            if (!this.dlg) return;
            if (this.queue) {
                alert("A criptoe investment queue is already running. Cancel it first, or wait for it to finish.");
                return;
            }
            const ev = this.evaluate();
            if (ev.error) { this.refreshDialog(); return; }

            const w = this.dlg.wallet;
            if (ev.chunks.length > 1) {
                const msg = "Invest " + this.commas(ev.amount) + " criptoe into wallet " + w + "?\n\n" +
                    "This is sent as " + ev.chunks.length + " separate investments of at most " + this.commas(WIRE_MAX) +
                    ", about " + this.clampInterval(this.cfg.intervalS) + "s apart (" + this.etaText(ev.chunks.length) + " total).\n\n" +
                    "You cannot withdraw from a wallet on the same day you invest in it.";
                if (!confirm(msg)) return;
            }
            this.closeDialog();
            this.startQueue(w, ev.chunks);
        }

        startQueue(wallet, chunks) {
            this.queue = {
                wallet: wallet,
                chunks: chunks,
                i: 0,
                planned: chunks.reduce(function (a, b) { return a + b; }, 0),
                sentTotal: 0,
                nextAt: 0,
                note: "",
                done: false,
            };
            this.refreshPanelBox();
            this.renderProgress();
            const self = this;
            this._ui = setInterval(function () { if (self.queue) self.renderProgress(); }, 1000);
            this.pump();
        }

        pump() {
            const q = this.queue;
            if (!q || q.done) return;
            if (q.i >= q.chunks.length) {
                this.finishQueue("All " + this.commas(q.sentTotal) + " criptoe invested.", "done");
                return;
            }

            const balNow = this.bal();
            const amt = Math.min(q.chunks[q.i], balNow);
            if (amt <= 0) {
                this.finishQueue("Stopped — no uninvested criptoe left. " + this.commas(q.sentTotal) +
                    " of " + this.commas(q.planned) + " invested.", "stop");
                return;
            }
            if (amt < q.chunks[q.i]) q.note = "last send trimmed to your remaining balance";

            try {
                IdlePixelPlus.sendMessage("INVEST_WALLET=wallet_" + q.wallet + "~" + amt);
            } catch (e) {
                console.error("Criptoe Bulk Invest: send failed", e);
                this.finishQueue("Send failed — stopped after " + this.commas(q.sentTotal) + ".", "stop");
                return;
            }
            q.sentTotal += amt;
            q.i++;

            if (q.i >= q.chunks.length) {
                this.finishQueue("All " + this.commas(q.sentTotal) + " criptoe invested into wallet " + q.wallet + ".", "done");
                return;
            }

            const gap = this.intervalMs();
            q.nextAt = Date.now() + gap;
            const self = this;
            this._timer = setTimeout(function () { self.pump(); }, gap);
            this.renderProgress();
        }

        cancelQueue() {
            const q = this.queue;
            if (!q) return;
            this.finishQueue("Cancelled — " + this.commas(q.sentTotal) + " of " + this.commas(q.planned) +
                " invested (" + q.i + " of " + q.chunks.length + " sends).", "stop");
        }

        finishQueue(msg, kind) {
            if (this._timer) { clearTimeout(this._timer); this._timer = null; }
            if (this._ui) { clearInterval(this._ui); this._ui = null; }
            if (this.queue) {
                this.queue.done = true;
                this.queue.finalMsg = msg;
                this.queue.finalKind = kind;
            }
            this.renderProgress();
            this.queue = null;
            this.refreshPanelBox();
            try { console.log("Criptoe Bulk Invest:", msg); } catch (e) {  }

            setTimeout(function () {
                const el = document.getElementById("cbi-progress");
                if (el && el.getAttribute("data-final") === "1" && el.parentNode) el.parentNode.removeChild(el);
            }, kind === "done" ? 9000 : 14000);
        }

        renderProgress() {
            const q = this.queue;
            if (!q) return;
            let el = document.getElementById("cbi-progress");
            if (!el) {
                el = document.createElement("div");
                el.id = "cbi-progress";
                document.body.appendChild(el);
            }

            const total = q.chunks.length;
            const pctDone = total ? Math.round((q.i / total) * 100) : 100;
            const self = this;

            if (q.done) {
                el.setAttribute("data-final", "1");
                el.innerHTML =
                    '<div class="cbi-ptitle"><img src="' + IMG + 'criptoe_coin.png" style="height:16px;vertical-align:middle;"> Wallet ' + q.wallet + '</div>' +
                    '<div class="cbi-pline ' + (q.finalKind === "done" ? "cbi-pdone" : "cbi-pstop") + '">' + this.esc(q.finalMsg || "") + '</div>' +
                    '<div class="cbi-pactions"><span class="cbi-btn" id="cbi-pclose">Close</span></div>';
                const c = el.querySelector("#cbi-pclose");
                if (c) c.addEventListener("click", function () {
                    const e2 = document.getElementById("cbi-progress");
                    if (e2 && e2.parentNode) e2.parentNode.removeChild(e2);
                });
                return;
            }

            el.removeAttribute("data-final");
            const waiting = Math.max(0, Math.ceil((q.nextAt - Date.now()) / 1000));
            el.innerHTML =
                '<div class="cbi-ptitle"><img src="' + IMG + 'criptoe_coin.png" style="height:16px;vertical-align:middle;"> Investing into Wallet ' + q.wallet + '</div>' +
                '<div class="cbi-bar"><div class="cbi-fill" style="width:' + pctDone + '%;"></div></div>' +
                '<div class="cbi-pline">Send <b>' + q.i + '</b> of <b>' + total + '</b> &nbsp;·&nbsp; ' +
                    this.commas(q.sentTotal) + ' of ' + this.commas(q.planned) + '</div>' +
                '<div class="cbi-pline">' + (q.i >= total ? "finishing…" : "next send in " + waiting + "s") +
                    (q.note ? " · " + this.esc(q.note) : "") + '</div>' +
                '<div class="cbi-pactions"><span class="cbi-btn cbi-danger" id="cbi-pcancel">Cancel</span></div>';
            const b = el.querySelector("#cbi-pcancel");
            if (b) b.addEventListener("click", function () { self.cancelQueue(); });
        }
    }

    IdlePixelPlus.registerPlugin(new CriptoeBulkInvestPlugin());
})();