Blackjack ToolKit V5.0

Result shows immediately when Dealer has 2 cards. Auto-bet waits 2s. Fixed card counting.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Blackjack ToolKit V5.0
// @version       5.0
// @description   Result shows immediately when Dealer has 2 cards. Auto-bet waits 2s. Fixed card counting.
// @author        M7TEM
// @match         https://www.torn.com/page.php?sid=blackjack*
// @match         https://www.torn.com/pda.php*step=blackjack*
// @grant         GM.xmlHttpRequest
// @connect       api.torn.com
// @license       MIT
// @namespace     blackjack.toolkit
// ==/UserScript==

(function() {
    'use strict';

    // =========================== PART 1: CONSTANTS ==============================
    const PANEL_ID = 'bj-tracker-panel';
    const STORAGE = 'torn_bj_tracker_results_v2';
    const PROFIT_STORAGE = 'torn_bj_total_profit_v2';
    const API_KEY_STORAGE = 'bj_tracker_api_key';
    const LAST_SYNC_KEY = 'bj_tracker_last_sync';
    const LAST_SCANNED_TIMESTAMP = 'bj_tracker_last_scanned';
    const PANEL_POS_KEY = 'bj_tracker_pos';
    const SESSION_ACTIVE_KEY = 'bj_session_active';
    const SESSION_PROFIT_KEY = 'bj_session_profit';
    const SESSION_START_KEY = 'bj_session_start';
    const UI_MINIMIZED = 'UI_MINIMIZED';
    const RISK_LOCK_KEY = 'bj_risk_lock_target';
    const AUTO_BET_KEY = 'bj_auto_bet_settings';

    const LOG_ID_WIN = 8355;
    const LOG_ID_LOSE = 8354;
    const LOG_ID_PUSH = 8358;
    const RESULT_LOG_IDS = [LOG_ID_WIN, LOG_ID_LOSE, LOG_ID_PUSH];
    const API_SYNC_INTERVAL_MS = 5 * 1000;

    // --- State Variables ---
    let apiKey = '';
    let results = [];
    let totalProfit = 0;
    let isTrackerDragging = false;
    let isSessionActive = false;
    let sessionProfit = 0;
    let sessionStartDate = 0;
    let isSyncing = false;
    let isProcessingAction = false;
    let currentView = 'main';
    let currentStatsTimeframe = 7;
    let lastScannedTime = 0;
    let isHandActive = false;
    let helperUpdateTimeout = null;
    let isMinimized = false;
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;
    let lastRenderedState = '';
    let shiftKeyHeld = false;
    let lockedParam = localStorage.getItem(RISK_LOCK_KEY) || 'capital';

    // Auto-Bet State
    let isRoundInProgress = false;
    let isDoubleActive = false;
    let autoBetTimer = null;
    let countdownValue = 0;
    let autoBetSettings = { resetWin: false, multLoss: false, multPush: false };

    function initializeTrackerState() {
        apiKey = localStorage.getItem(API_KEY_STORAGE) || '';
        results = JSON.parse(localStorage.getItem(STORAGE) || '[]');
        totalProfit = parseFloat(localStorage.getItem(PROFIT_STORAGE) || '0');
        isMinimized = localStorage.getItem(UI_MINIMIZED) === 'true';
        isSessionActive = JSON.parse(localStorage.getItem(SESSION_ACTIVE_KEY) || 'false');
        sessionProfit = parseFloat(localStorage.getItem(SESSION_PROFIT_KEY) || '0');
        sessionStartDate = parseInt(localStorage.getItem(SESSION_START_KEY) || '0', 10);
        lastScannedTime = parseInt(localStorage.getItem(LAST_SCANNED_TIMESTAMP) || '0', 10);

        try {
            const savedAuto = JSON.parse(localStorage.getItem(AUTO_BET_KEY));
            if(savedAuto) autoBetSettings = savedAuto;
        } catch(e){}

        if (results.length > 0) {
            setTimeout(() => {
                results.sort((a,b) => b.timestamp - a.timestamp);
                refreshTrackerUI();
            }, 0);
        }
    }

    initializeTrackerState();

    // --- Utility Functions ---
    function formatNumberToKMB(num) {
        if (typeof num !== 'number' || !isFinite(num)) return 'NaN';
        if (num === 0) return '0';
        const abs = Math.abs(num);
        const sign = num < 0 ? '-' : '';
        if (abs >= 1e9) return sign + (abs / 1e9).toFixed(2) + 'b';
        if (abs >= 1e6) return sign + (abs / 1e6).toFixed(2) + 'm';
        if (abs >= 1e3) return sign + (abs / 1e3).toFixed(1) + 'k';
        return sign + abs.toLocaleString();
    }

    function safeSetBet(amount) {
        const input = document.querySelector('.input-money') || document.querySelector('input.bet');
        if (input) {
            input.value = String(amount);
            input.dispatchEvent(new Event('change', { bubbles: true }));
            input.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }

    function safeGetBet() {
        const input = document.querySelector('.input-money') || document.querySelector('input.bet');
        return input ? parseInt(input.value.replace(/,/g, ''), 10) || 0 : 0;
    }

    function getCurrentBet() { return safeGetBet(); }

    // --- UI Construction ---
    function createTrackerPanel() {
        if (document.getElementById(PANEL_ID)) return;

        const styleSheet = document.createElement("style");
        styleSheet.innerText = `
            #${PANEL_ID} {
                position: fixed; top: 10px; right: 10px; z-index: 1000;
                background-color: #1a1a1a; border: 2px solid #FFD700; border-radius: 8px;
                padding: 8px; width: 230px; box-shadow: 0 4px 12px rgba(0,0,0,0.5);
                font-family: monospace; color: #E0E0E0; line-height: 1.4;
            }
            .bj-green { color: #4CAF50; font-weight: bold; }
            .bj-red { color: #FF6F69; font-weight: bold; }
            .bj-grey { color: #AAAAAA; font-weight: normal; }
            .bj-gold { color: #FFD700; font-weight: bold; }

            .bj-trk-btn {
                background: #333; border: 1px solid #555; color: #FFD700; cursor: pointer;
                border-radius: 4px; padding: 5px; font-size: 11px; font-weight: bold; text-transform: uppercase;
            }
            .bj-trk-btn:hover { background: #444; border-color: #FFD700; }

            /* Helper Buttons */
            .bj-helper-button {
                flex-grow: 1; padding: 6px 4px; font-size: 12px; font-weight: 700;
                cursor: pointer; color: #E0E0E0; background-color: #2a2a2a;
                border: 1px solid #555; border-radius: 4px; text-transform: uppercase;
            }
            .bj-helper-button:hover { background-color: #444; }
            .bj-hit { border-color: #FF5722 !important; background-color: rgba(255, 87, 34, 0.4) !important; color: white !important; }
            .bj-stand, .bj-double { border-color: #4CAF50 !important; background-color: rgba(76, 175, 80, 0.4) !important; color: white !important; }
            .bj-split { border-color: #2196F3 !important; background-color: rgba(33, 150, 243, 0.4) !important; color: white !important; }

            /* Risk Panel & Inputs */
            .bj-risk-display {
                background: #111; border: 1px solid #333; padding: 5px; margin-top: 5px;
                font-size: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
            }
            .bj-risk-label { color: #888; text-align: left; }
            .bj-risk-val { text-align: right; font-weight: bold; }

            .bj-input-group { display:flex; align-items:center; gap:2px; }
            .bj-input { width: 100%; box-sizing: border-box; background: #333; color: white; border: 1px solid #555; padding: 3px; text-align: right; font-family:monospace;}
            .bj-lock-btn { cursor:pointer; font-size:12px; opacity:0.3; user-select:none; }
            .bj-lock-btn.locked { opacity:1; color:#4CAF50; }
            .bj-input-label { font-size: 10px; color: #aaa; display: block; margin-bottom: 2px; }

            /* History */
            .bj-history-row {
                display: grid; grid-template-columns: 35px 70px 1fr 1fr; gap: 2px;
                font-size: 10px; border-bottom: 1px solid #222; padding: 2px 0;
            }
            .bj-view { display: none; flex-direction: column; gap: 8px; }
            .bj-view.active { display: flex; }
        `;
        document.head.appendChild(styleSheet);

        const panel = document.createElement('div');
        panel.id = PANEL_ID;
        panel.style.display = 'flex'; panel.style.flexDirection = 'column'; panel.style.gap = '8px';
        document.body.appendChild(panel);

        try {
            const pos = JSON.parse(localStorage.getItem(PANEL_POS_KEY));
            if (pos) { panel.style.top = pos.top; panel.style.left = pos.left; }
        } catch(e){}

        const header = document.createElement('div');
        Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'move', borderBottom: '1px solid #333', paddingBottom: '5px' });
        header.innerHTML = '<span style="font-weight:bold; color: #FFD700;">♠️ BJ ToolKit V5.0</span>';
        const minimizeBtn = document.createElement('span');
        minimizeBtn.textContent = isMinimized ? '[+]' : '[-]'; minimizeBtn.style.cursor = 'pointer';
        minimizeBtn.onclick = () => {
            isMinimized = !isMinimized;
            minimizeBtn.textContent = isMinimized ? '[+]' : '[-]';
            document.getElementById('bj-trk-content-wrap').style.display = isMinimized ? 'none' : 'block';
            localStorage.setItem(UI_MINIMIZED, isMinimized);
        };
        header.appendChild(minimizeBtn);
        panel.appendChild(header);

        const contentWrap = document.createElement('div');
        contentWrap.id = 'bj-trk-content-wrap';
        if(isMinimized) contentWrap.style.display = 'none';
        panel.appendChild(contentWrap);

        // ================= VIEW 1: MAIN DASHBOARD =================
        const mainView = document.createElement('div');
        mainView.id = 'bj-view-main';
        mainView.className = 'bj-view active';
        contentWrap.appendChild(mainView);

        mainView.innerHTML = `
            <div style="display:grid; grid-template-columns: 1fr 1fr; gap:5px; font-size:12px;">
                <div id="bj-total-profit">Life: <span class="bj-grey">...</span></div>
                <div id="bj-daily-profit">Daily: <span class="bj-grey">...</span></div>
            </div>
            <div id="bj-session-profit" style="font-size:12px;">Session: <span class="bj-grey">...</span></div>
            <div id="bj-sync-status" style="font-size: 9px; color: #666; margin-top:2px;">Sync: --</div>
        `;

        const actionRow = document.createElement('div');
        Object.assign(actionRow.style, { display: 'flex', gap: '5px' });
        actionRow.innerHTML = `
            <button id="nav-session" class="bj-trk-btn" style="flex:1">▶</button>
            <button id="nav-stats" class="bj-trk-btn" style="flex:1">📊</button>
            <button id="nav-settings" class="bj-trk-btn" style="flex:1">⚙️</button>
            <button id="nav-sync" class="bj-trk-btn" style="flex:1">🔄</button>
        `;
        mainView.appendChild(actionRow);

        const riskDisplay = document.createElement('div');
        riskDisplay.className = 'bj-risk-display';
        riskDisplay.innerHTML = `
            <span class="bj-risk-label">Streak:</span> <span id="rk-streak" class="bj-risk-val">0</span>
            <span class="bj-risk-label">Total Loss:</span> <span id="rk-tot-loss" class="bj-risk-val" style="color:#FF6F69">$0</span>
            <span class="bj-risk-label">Pot. Loss:</span> <span id="rk-pot-loss" class="bj-risk-val" style="color:#FF6F69">$0</span>
            <span class="bj-risk-label">Pot. Profit:</span> <span id="rk-pot-prof" class="bj-risk-val" style="color:#4CAF50">$0</span>
        `;
        mainView.appendChild(riskDisplay);

        const bettingControls = document.createElement('div');
        bettingControls.id = 'bj-betting-controls';
        mainView.appendChild(bettingControls);

        const dynamicContent = document.createElement('div');
        dynamicContent.id = 'bj-dynamic-content';
        dynamicContent.innerHTML = `<div id="bj-recent-games" style="max-height: 120px; overflow-y: auto; border: 1px solid #333; padding: 2px; background: rgba(0,0,0,0.3);"></div>`;
        mainView.appendChild(dynamicContent);
        // ================= VIEW 2: SETTINGS PAGE =================
        const settingsView = document.createElement('div');
        settingsView.id = 'bj-view-settings';
        settingsView.className = 'bj-view';
        settingsView.innerHTML = `
            <button class="bj-trk-btn" id="sets-back" style="width:100%">« Back to Game</button>

            <div style="background:#111; padding:5px; border:1px solid #333;">
                <h4 style="margin:0 0 5px 0; color:#FFD700; font-size:11px;">Risk Calculator</h4>
                <div style="display:flex; flex-direction:column; gap:5px;">
                    <div style="display:grid; grid-template-columns:1fr 1fr; gap:5px;">
                        <div><span class="bj-input-label">Streak</span><input id="bj-risk-streak" class="bj-input" value="${localStorage.getItem('bj_risk_streak')||'10'}"></div>
                        <div><span class="bj-input-label">Mult (x)</span><input id="bj-multiplier-input" class="bj-input" value="${localStorage.getItem('bj_multiplier_value')||'2.2'}" step="0.1"></div>
                    </div>
                    <div>
                        <span class="bj-input-label">Total Capital</span>
                        <div class="bj-input-group">
                            <span class="bj-lock-btn" id="lock-cap" title="Lock Capital">🔒</span>
                            <input id="bj-risk-capital" class="bj-input" value="${localStorage.getItem('bj_risk_capital')||'10000000'}">
                        </div>
                    </div>
                    <div>
                        <span class="bj-input-label">Starting Bet</span>
                        <div class="bj-input-group">
                            <span class="bj-lock-btn" id="lock-bet" title="Lock Bet">🔒</span>
                            <input id="bj-start-bet-input" class="bj-input" value="${localStorage.getItem('bj_start_bet_value')||'20000'}">
                        </div>
                    </div>
                </div>
            </div>

            <div style="background:#111; padding:5px; border:1px solid #333;">
                <h4 style="margin:0 0 5px 0; color:#FFD700; font-size:11px;">Auto-Betting (2s Delay)</h4>
                <div style="display:flex; flex-direction:column; gap:4px;">
                    <label style="font-size:11px; cursor:pointer;"><input type="checkbox" id="ab-reset-win"> Reset after Win</label>
                    <label style="font-size:11px; cursor:pointer;"><input type="checkbox" id="ab-mult-loss"> Multiply after Loss</label>
                    <label style="font-size:11px; cursor:pointer;"><input type="checkbox" id="ab-mult-push"> Multiply after Push</label>
                </div>
            </div>

            <div style="background:#111; padding:5px; border:1px solid #333;">
                <h4 style="margin:0 0 5px 0; color:#FFD700; font-size:11px;">API Data</h4>
                <input type="password" id="bj-api-input" class="bj-input" value="${apiKey}" placeholder="API Key">
                <div style="display:flex; gap:5px; margin-top:5px;">
                    <button id="bj-save-api" class="bj-trk-btn" style="flex:1">Save</button>
                    <button id="bj-reset-data" class="bj-trk-btn" style="flex:1; color:tomato; border-color:tomato">Reset</button>
                </div>
            </div>
        `;
        contentWrap.appendChild(settingsView);

        // ================= VIEW 3: STATS PAGE =================
        const statsView = document.createElement('div');
        statsView.id = 'bj-view-stats';
        statsView.className = 'bj-view';
        statsView.innerHTML = `
            <button class="bj-trk-btn" id="stats-back" style="width:100%">« Back to Game</button>
            <div style="background:#1a1a1a; padding:5px; border:1px solid #444;">
                <canvas id="bj-graph-canvas" width="200" height="150"></canvas>
                <div style="display:flex; gap:2px; justify-content:center; margin-top:5px;">
                    <button class="bj-trk-btn" data-days="1">1D</button>
                    <button class="bj-trk-btn" data-days="7">7D</button>
                    <button class="bj-trk-btn" data-days="30">30D</button>
                    <button class="bj-trk-btn" data-days="365">All</button>
                </div>
            </div>
            <div id="bj-winrate" style="text-align:center; font-size:10px; margin-top:5px; color:#aaa;"></div>
        `;
        contentWrap.appendChild(statsView);

        // --- Event Wiring ---
        document.getElementById('nav-session').onclick = toggleSession;
        document.getElementById('nav-sync').onclick = () => importApiData(false);
        document.getElementById('nav-settings').onclick = () => switchView('settings');
        document.getElementById('nav-stats').onclick = () => switchView('stats');
        document.getElementById('sets-back').onclick = () => switchView('main');
        document.getElementById('stats-back').onclick = () => switchView('main');

        statsView.querySelectorAll('[data-days]').forEach(btn => {
            btn.onclick = () => { currentStatsTimeframe = parseInt(btn.getAttribute('data-days')); updateGraph(); };
        });

        setupSettingsEvents();
        setupPersistentControls();

        // Drag Logic
        header.onmousedown = function(e) {
            isTrackerDragging = true;
            const offsetX = e.clientX - panel.offsetLeft;
            const offsetY = e.clientY - panel.offsetTop;
            function mouseMove(e) {
                if (!isTrackerDragging) return;
                let newX = e.clientX - offsetX;
                let newY = e.clientY - offsetY;
                const r = panel.getBoundingClientRect();
                newX = Math.max(0, Math.min(newX, window.innerWidth - r.width));
                newY = Math.max(0, Math.min(newY, window.innerHeight - r.height));
                panel.style.left = newX + 'px'; panel.style.top = newY + 'px';
            }
            function mouseUp() {
                isTrackerDragging = false;
                localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ top: panel.style.top, left: panel.style.left }));
                document.removeEventListener('mousemove', mouseMove);
                document.removeEventListener('mouseup', mouseUp);
            }
            document.addEventListener('mousemove', mouseMove);
            document.addEventListener('mouseup', mouseUp);
        };
    }

    function switchView(viewName) {
        currentView = viewName;
        document.querySelectorAll('.bj-view').forEach(el => el.classList.remove('active'));
        document.getElementById(`bj-view-${viewName}`).classList.add('active');
        refreshTrackerUI();
        if(viewName === 'stats') setTimeout(updateGraph, 50);
    }

    function setupSettingsEvents() {
        document.getElementById('bj-save-api').onclick = () => {
            apiKey = document.getElementById('bj-api-input').value.trim();
            localStorage.setItem(API_KEY_STORAGE, apiKey);
            localStorage.setItem(LAST_SCANNED_TIMESTAMP, '0');
            lastScannedTime = 0;
            importApiData(false);
        };
        document.getElementById('bj-reset-data').onclick = () => {
            if(confirm("Reset all data?")) { localStorage.clear(); location.reload(); }
        };

        const elCap = document.getElementById('bj-risk-capital');
        const elStreak = document.getElementById('bj-risk-streak');
        const elMult = document.getElementById('bj-multiplier-input');
        const elBet = document.getElementById('bj-start-bet-input');
        const lCap = document.getElementById('lock-cap');
        const lBet = document.getElementById('lock-bet');

        // Auto-Bet Checkboxes
        const abReset = document.getElementById('ab-reset-win');
        const abMultLoss = document.getElementById('ab-mult-loss');
        const abMultPush = document.getElementById('ab-mult-push');

        abReset.checked = autoBetSettings.resetWin;
        abMultLoss.checked = autoBetSettings.multLoss;
        abMultPush.checked = autoBetSettings.multPush;

        const saveAutoBet = () => {
            autoBetSettings = { resetWin: abReset.checked, multLoss: abMultLoss.checked, multPush: abMultPush.checked };
            localStorage.setItem(AUTO_BET_KEY, JSON.stringify(autoBetSettings));
        };
        abReset.onchange = saveAutoBet;
        abMultLoss.onchange = saveAutoBet;
        abMultPush.onchange = saveAutoBet;

        const clean = (val) => parseFloat(String(val).replace(/,/g, '')) || 0;

        function updateLockUI() {
            lCap.classList.toggle('locked', lockedParam === 'capital');
            lBet.classList.toggle('locked', lockedParam === 'bet');
            elCap.style.borderColor = lockedParam === 'capital' ? '#4CAF50' : '#555';
            elBet.style.borderColor = lockedParam === 'bet' ? '#4CAF50' : '#555';
        }

        updateLockUI();

        lCap.onclick = () => { lockedParam = 'capital'; localStorage.setItem(RISK_LOCK_KEY, lockedParam); updateLockUI(); };
        lBet.onclick = () => { lockedParam = 'bet'; localStorage.setItem(RISK_LOCK_KEY, lockedParam); updateLockUI(); };

        function calculateRisk(source) {
            const streak = clean(elStreak.value);
            const mult = clean(elMult.value);
            if (mult <= 1 || streak <= 0) return;

            const factor = (1 - Math.pow(mult, streak)) / (1 - mult);

            if (source === 'capital') {
                lockedParam = 'capital';
                const cap = clean(elCap.value);
                const newBet = Math.floor(cap / factor);
                elBet.value = newBet;
                localStorage.setItem('bj_start_bet_value', newBet);
                localStorage.setItem('bj_risk_capital', cap);
            }
            else if (source === 'bet') {
                lockedParam = 'bet';
                const bet = clean(elBet.value);
                const newCap = Math.ceil(bet * factor);
                elCap.value = newCap;
                localStorage.setItem('bj_risk_capital', newCap);
                localStorage.setItem('bj_start_bet_value', bet);
            }
            else if (source === 'calc') {
                if (lockedParam === 'capital') {
                    const cap = clean(elCap.value);
                    const newBet = Math.floor(cap / factor);
                    elBet.value = newBet;
                    localStorage.setItem('bj_start_bet_value', newBet);
                } else {
                    const bet = clean(elBet.value);
                    const newCap = Math.ceil(bet * factor);
                    elCap.value = newCap;
                    localStorage.setItem('bj_risk_capital', newCap);
                }
            }

            localStorage.setItem(RISK_LOCK_KEY, lockedParam);
            localStorage.setItem('bj_risk_streak', streak);
            localStorage.setItem('bj_multiplier_value', mult);
            updateLockUI();
        }

        elCap.addEventListener('keyup', () => calculateRisk('capital'));
        elBet.addEventListener('keyup', () => calculateRisk('bet'));
        elStreak.addEventListener('keyup', () => calculateRisk('calc'));
        elMult.addEventListener('keyup', () => calculateRisk('calc'));
    }
    function setupPersistentControls() {
        const controls = document.getElementById('bj-betting-controls');
        if(!controls) return;
        controls.innerHTML = '';

        const row = document.createElement('div');
        row.style.display = 'flex'; row.style.gap = '5px'; row.style.marginTop = '5px';
        const btnStart = document.createElement('button'); btnStart.textContent = 'Deal'; btnStart.className = 'bj-helper-button'; btnStart.style.backgroundColor = '#F9A825'; btnStart.style.color='#333';
        const btnReset = document.createElement('button'); btnReset.textContent = 'Reset'; btnReset.className = 'bj-helper-button';
        const btnMult = document.createElement('button'); btnMult.id = 'bj-ctrl-mult'; btnMult.className = 'bj-helper-button';
        const btnDiv = document.createElement('button'); btnDiv.id = 'bj-ctrl-div'; btnDiv.className = 'bj-helper-button';

        const mult = parseFloat(localStorage.getItem('bj_multiplier_value') || '2.2').toFixed(1);
        btnMult.textContent = `x${mult}`; btnDiv.textContent = `/${mult}`;
        row.append(btnStart, btnReset, btnMult, btnDiv);
        controls.appendChild(row);

        setupHelperButtonEvents(btnStart, btnReset, btnMult, btnDiv, null, null, null, null);
    }

    function renderHelperContent(advice, handInfo, actionColor) {
        const container = document.createElement('div');
        Object.assign(container.style, { padding: '5px', background: '#1a1a1a', border: '1px solid #444', borderRadius: '4px', display:'flex', flexDirection:'column', gap:'5px'});

        container.innerHTML = `
            <div class="bj-trk-advice" style="--bj-border-color:${actionColor}">${advice}</div>
            <div id="bj-helper-hand-total" style="font-size:12px; text-align:center; color:#ccc;">${handInfo}</div>
        `;

        const row = document.createElement('div');
        row.style.display = 'flex'; row.style.gap = '5px';
        const bHit = document.createElement('button'); bHit.textContent='HIT'; bHit.className='bj-helper-button';
        const bStd = document.createElement('button'); bStd.textContent='STAND'; bStd.className='bj-helper-button';
        const bDbl = document.createElement('button'); bDbl.textContent='DBL'; bDbl.className='bj-helper-button';
        const bSpl = document.createElement('button'); bSpl.textContent='SPLIT'; bSpl.className='bj-helper-button';

        row.append(bHit, bStd, bDbl, bSpl);
        container.appendChild(row);

        bHit.style.display = 'none';
        bStd.style.display = 'none';
        bDbl.style.display = 'none';
        bSpl.style.display = 'none';

        let visibleBtn = null;
        if(advice === 'Hit') { bHit.style.display = 'block'; bHit.classList.add('bj-hit'); visibleBtn = bHit; }
        else if(advice === 'Stand') { bStd.style.display = 'block'; bStd.classList.add('bj-stand'); visibleBtn = bStd; }
        else if(advice === 'Double') { bDbl.style.display = 'block'; bDbl.classList.add('bj-double'); visibleBtn = bDbl; }
        else if(advice === 'Split') { bSpl.style.display = 'block'; bSpl.classList.add('bj-split'); visibleBtn = bSpl; }

        if (visibleBtn) {
            visibleBtn.style.width = '100%';
            visibleBtn.style.padding = '10px';
            visibleBtn.style.fontSize = '14px';
        }

        setupHelperButtonEvents(null,null,null,null, bHit, bStd, bDbl, bSpl);
        return container;
    }

    function updateRiskDisplay() {
        const elStreak = document.getElementById('rk-streak');
        const elTotLoss = document.getElementById('rk-tot-loss');
        const elPotLoss = document.getElementById('rk-pot-loss');
        const elPotProf = document.getElementById('rk-pot-prof');
        if (!elStreak) return;

        const sorted = results.slice().sort((a,b) => b.timestamp - a.timestamp);
        let streak = 0;
        let totalLoss = 0;
        for (let r of sorted) {
            if (r.result === 'lose') { streak++; totalLoss += Math.abs(r.profit); }
            else if (r.result === 'push') { continue; }
            else { break; }
        }

        const currentBet = getCurrentBet();
        const potentialLoss = totalLoss + currentBet;
        const potentialProfit = currentBet - totalLoss;

        elStreak.textContent = streak;
        elTotLoss.textContent = '$' + formatNumberToKMB(totalLoss);
        elPotLoss.textContent = '$' + formatNumberToKMB(potentialLoss);
        elPotProf.textContent = (potentialProfit >= 0 ? '+' : '-') + '$' + formatNumberToKMB(Math.abs(potentialProfit));
        elPotProf.style.color = potentialProfit >= 0 ? '#4CAF50' : '#FF6F69';
    }

    function renderRecentGamesList() {
        const container = document.getElementById('bj-recent-games');
        if (!container) return;

        let chronological = results.slice().sort((a,b) => a.timestamp - b.timestamp);
        let runningTotal = 0;
        const processed = chronological.map(r => {
            runningTotal += r.profit;
            return { ...r, accumulated: runningTotal };
        });

        const display = processed.sort((a,b) => b.timestamp - a.timestamp).slice(0, 100);
        let html = '';
        display.forEach((r, idx) => {
             let color = '#888'; let sign = '';
             let outcome = r.result.toUpperCase();
             if (r.isNatural) outcome = 'NATURAL';
             else if (r.isDouble && r.result==='win') outcome = 'WIN DBL';
             else if (r.isDouble && r.result==='lose') outcome = 'LOSE DBL';

             if (r.result === 'win') { color = '#4CAF50'; sign = '+'; }
             else if (r.result === 'lose') { color = '#E53935'; sign = '-'; }
             else if (r.result === 'push') { color = '#FFC107'; sign = ''; }

             let accColor = r.accumulated >= 0 ? '#4CAF50' : '#E53935';

             html += `
             <div class="bj-history-row">
                 <span style="color:#666; text-align:left;">#${results.length - idx}</span>
                 <span style="color:${color}; text-align:left; font-weight:bold; white-space:nowrap; overflow:hidden;">${outcome}</span>
                 <span style="color:${color}; text-align:right;">${sign}${formatNumberToKMB(Math.abs(r.profit))}</span>
                 <span style="color:${accColor}; text-align:right;">${formatNumberToKMB(r.accumulated)}</span>
             </div>`;
        });
        container.innerHTML = html;
    }

    function setupHelperButtonEvents(btnStart, btnResetBet, btnMult, btnDiv, btnHit, btnStand, btnDouble, btnSplit) {
        if (typeof isProcessingAction === 'undefined') window.isProcessingAction = false;

        const clickAction = (step, confirm) => {
            if (window.isProcessingAction) return;
            window.isProcessingAction = true;
            document.querySelectorAll('.bj-helper-button').forEach(b => b.style.opacity = '0.5');

            // --- TRACK DOUBLE DOWN ---
            if (step === 'doubleDown') isDoubleActive = true;
            else if (step === 'hit' || step === 'stand' || step === 'split') isDoubleActive = false;

            const area = document.querySelector(`[data-step="${step}"]`);
            if(area) {
                area.dispatchEvent(new MouseEvent('click', {bubbles:true}));
                if(confirm) setTimeout(() => {
                    const yes = document.querySelector('.confirm-action.yes') || document.querySelector('.action-yes');
                    if(yes) yes.click();
                }, 100);
            }

            setTimeout(() => { window.isProcessingAction = false; document.querySelectorAll('.bj-helper-button').forEach(b => b.style.opacity = '1'); }, 200);
        };

        if(btnHit) btnHit.onclick = () => clickAction('hit');
        if(btnStand) btnStand.onclick = () => clickAction('stand');
        if(btnDouble) btnDouble.onclick = () => clickAction('doubleDown', true);
        if(btnSplit) btnSplit.onclick = () => clickAction('split', true);

        if(btnStart) btnStart.onclick = () => {
            const btn = document.querySelector('[data-step="startGame"]');
            if(btn) {
                isDoubleActive = false; // Reset
                btn.click();
                setTimeout(() => {
                    const yes = document.querySelector('.bet-confirm .yes') || document.querySelector('.confirm-yes');
                    if(yes) yes.click();
                }, 100);
            }
        };

        if(btnResetBet) btnResetBet.onclick = () => {
            const val = document.getElementById('bj-start-bet-input').value;
            safeSetBet(parseInt(val.replace(/,/g,''),10));
        };
        if(btnMult) btnMult.onclick = () => {
            const mult = parseFloat(document.getElementById('bj-multiplier-input').value) || 2.2;
            safeSetBet(Math.round(safeGetBet() * mult));
        };
        if(btnDiv) btnDiv.onclick = () => {
            const mult = parseFloat(document.getElementById('bj-multiplier-input').value) || 2.2;
            safeSetBet(Math.max(100, Math.round(safeGetBet() / mult)));
        };
    }

    function refreshTrackerUI() {
        if(currentView === 'main') {
            const totalEl = document.getElementById('bj-total-profit');
            if (totalEl) totalEl.innerHTML = `Life: <span class="${totalProfit >= 0 ? 'bj-green' : 'bj-red'}">$${formatNumberToKMB(totalProfit)}</span>`;
            const sessionEl = document.getElementById('bj-session-profit');
            if (sessionEl) sessionEl.innerHTML = `Session: <span class="${isSessionActive ? (sessionProfit >= 0 ? 'bj-green' : 'bj-red') : 'bj-grey'}">${isSessionActive ? '$' + formatNumberToKMB(sessionProfit) : 'Inactive'}</span>`;

            const today = new Date();
            today.setHours(0,0,0,0);
            const startDay = Math.floor(today.getTime()/1000);
            let dailyProfit = results.filter(r => r.timestamp >= startDay).reduce((acc, r) => acc + r.profit, 0);
            const dailyEl = document.getElementById('bj-daily-profit');
            if (dailyEl) dailyEl.innerHTML = `Daily: <span class="${dailyProfit >= 0 ? 'bj-green' : 'bj-red'}">$${formatNumberToKMB(dailyProfit)}</span>`;

            const btnSess = document.getElementById('nav-session');
            if(btnSess) btnSess.textContent = isSessionActive ? '⏹' : '▶';

            const status = document.getElementById('bj-sync-status');
            if (status) {
                const next = new Date(Date.now() + API_SYNC_INTERVAL_MS).toLocaleTimeString();
                status.textContent = `${isSyncing?'Syncing...':(lastScannedTime>1?'Scanned':'Partial')} | Next: ${next}`;
            }

            const mult = parseFloat(localStorage.getItem('bj_multiplier_value') || '2.2').toFixed(1);
            const bm = document.getElementById('bj-ctrl-mult'); if(bm) bm.textContent=`x${mult}`;
            const bd = document.getElementById('bj-ctrl-div'); if(bd) bd.textContent=`/${mult}`;

            updateRiskDisplay();
            renderRecentGamesList();
        }
        else if (currentView === 'stats') {
             const wins = results.filter(r => r.result === 'win').length;
             const losses = results.filter(r => r.result === 'lose').length;
             const pushes = results.filter(r => r.result === 'push').length;
             const wr = document.getElementById('bj-winrate');
             if(wr) wr.innerHTML = `<span class="bj-green">W:${wins}</span> <span class="bj-red">L:${losses}</span> <span class="bj-gold">P:${pushes}</span>`;
        }
    }

    function toggleSession() {
        isSessionActive = !isSessionActive;
        if (isSessionActive) {
            sessionProfit = 0;
            sessionStartDate = Date.now();
            sessionProfit = results.filter(r => r.timestamp * 1000 >= sessionStartDate).reduce((sum, r) => sum + r.profit, 0);
        } else {
            sessionStartDate = 0;
        }
        localStorage.setItem(SESSION_ACTIVE_KEY, JSON.stringify(isSessionActive));
        localStorage.setItem(SESSION_PROFIT_KEY, sessionProfit.toString());
        localStorage.setItem(SESSION_START_KEY, sessionStartDate.toString());
        refreshTrackerUI();
    }

    async function importApiData(silent = true) {
        if(isSyncing || !apiKey) return; isSyncing = true;
        try {
            const forward = await fetchLogs((results[0]?.timestamp || (Date.now()/1000)-86400));
            if(forward.length) processLogs(forward);

            if(!lastScannedTime) {
                let scanning=true, pt=results[results.length-1]?.timestamp || Math.floor(Date.now()/1000);
                while(scanning) {
                    const back = await fetchLogs(null, pt);
                    if(back.length) {
                        processLogs(back);
                        pt = Math.min(...back.map(l=>l.timestamp))-1;
                        if(back.length<100) { scanning=false; lastScannedTime=1; }
                    } else { scanning=false; lastScannedTime=1; }
                }
                localStorage.setItem(LAST_SCANNED_TIMESTAMP, lastScannedTime);
            }

            totalProfit = results.reduce((s,r)=>s+r.profit,0);
            sessionProfit = results.filter(r=>r.timestamp*1000 >= sessionStartDate).reduce((s,r)=>s+r.profit,0);
            localStorage.setItem(STORAGE, JSON.stringify(results));
            localStorage.setItem(PROFIT_STORAGE, totalProfit);
            localStorage.setItem(SESSION_PROFIT_KEY, sessionProfit);

            refreshTrackerUI();
            if(currentView === 'stats') updateGraph();

        } catch(e) { console.error(e); } finally { isSyncing=false; refreshTrackerUI(); }
    }

    async function fetchLogs(from, to) {
        let url = `https://api.torn.com/user/?selections=log&log=${RESULT_LOG_IDS.join(',')}&key=${apiKey}`;
        if(from) url+=`&from=${from+1}`; if(to) url+=`&to=${to}`;
        return new Promise(res => GM.xmlHttpRequest({method:"GET", url, onload:r=>{
            try{ res(Object.values(JSON.parse(r.responseText).log||{})); }catch{res([]);}
        }}));
    }
    function processLogs(logs) {
        const exist = new Set(results.map(r=>r.timestamp));
        for(let l of logs) {
            if(exist.has(l.timestamp)) continue;
            let res, prof=0, isNat=(l.data.win_state||'').includes('natural');
            if(l.log===LOG_ID_WIN) { prof=l.data.winnings-(l.data.winnings/(isNat?2.5:2)); res='win'; }
            else if(l.log===LOG_ID_LOSE) { prof=-(l.data.losses||0); res='lose'; }
            else if(l.log===LOG_ID_PUSH) { res='push'; }
            if(res) results.push({result:res, profit:prof, timestamp:l.timestamp, isNatural:isNat, isDouble: (l.data.winnings/(isNat?2.5:2)) > 10000000 });
        }
        results.sort((a,b)=>b.timestamp-a.timestamp);
    }

    function updateGraph() {
        const c = document.getElementById('bj-graph-canvas'); if(!c) return;
        const ctx = c.getContext('2d'); ctx.clearRect(0,0,c.width,c.height);
        const pts = results.filter(r=>r.timestamp >= (Date.now()/1000)-(currentStatsTimeframe*86400)).sort((a,b)=>a.timestamp-b.timestamp);
        if(pts.length<2) return;
        let acc=0; const data=pts.map(p=>{acc+=p.profit; return {t:p.timestamp, v:acc};});
        const p=20, W=c.width-p*2, H=c.height-p*2;
        const minV=Math.min(0,...data.map(d=>d.v)), maxV=Math.max(0,...data.map(d=>d.v));
        const minT=data[0].t, rangeT=data[data.length-1].t-minT, rangeV=maxV-minV||1;
        ctx.strokeStyle='#444'; const zY = (c.height-p) - ((0-minV)/rangeV)*H;
        ctx.beginPath(); ctx.moveTo(p, zY); ctx.lineTo(c.width-p, zY); ctx.stroke();
        ctx.strokeStyle='#FFD700'; ctx.lineWidth=1.5; ctx.beginPath();
        data.forEach((d,i)=>{
            const x = p + ((d.t-minT)/rangeT)*W;
            const y = (c.height-p) - ((d.v-minV)/rangeV)*H;
            if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
        });
        ctx.stroke();
    }

    function updateHelper() {
        if(currentView !== 'main') return;
        const dc = document.getElementById('bj-dynamic-content'); if(!dc) return;
        const panel = document.getElementById(PANEL_ID);
        const pHand = getHandInfo('.player-cards');
        const dHand = getHandInfo('.dealer-cards');

        // --- STRICT LOGIC: Wait for 2 dealer cards (unless player busted) ---
        let gameState = 'waiting';
        if (pHand.cards.length >= 2) {
            if (pHand.total > 21) {
                // Player Bust -> Immediate End
                gameState = 'over';
            } else if (dHand.cards.length === 1) {
                // Dealer 1 Card -> Active Round (Strategy)
                gameState = 'active';
            } else if (dHand.cards.length >= 2) {
                // Dealer 2+ Cards -> Round Over (Result)
                gameState = 'over';
            }
        }

        updateRiskDisplay();

        const stateKey = `${gameState}_${pHand.total}_${dHand.total}_${results.length}`;

        // If timer is running, allow re-render for countdown text only
        if(stateKey === lastRenderedState && !autoBetTimer) return;
        lastRenderedState = stateKey;

        if (gameState === 'active') {
            // --- ACTIVE: SHOW STRATEGY ---
            isRoundInProgress = true;

            if (autoBetTimer) { clearInterval(autoBetTimer); autoBetTimer = null; }

            const dVal = dHand.cards[0]===11?1:dHand.cards[0];
            let strat = getStrategyAction(pHand, dVal);
            if (pHand.total === 21) strat = 'Stand';

            const color = strat.includes('Hit')?'#FF5722':(strat.includes('Stand')?'#4CAF50':'#2196F3');

            const pTxt = `[${pHand.cardTexts.join(',')}]`;
            const dTxt = `[${dHand.cardTexts.join(',')}]`;

            dc.innerHTML=''; dc.appendChild(renderHelperContent(strat, `P: ${pHand.total} ${pTxt} | D: ${dHand.total} ${dTxt}`, color));
            panel.style.border = `2px solid ${color}`;

        } else if (gameState === 'over') {
            // --- OVER: RESULT & COUNTDOWN ---
            panel.style.border = '2px solid #FFD700';

            let resText = "Processing Result...", resColor = "#888";
            let outcome = null;

            const pTxt = `[${pHand.cardTexts.join(',')}]`;
            const dTxt = `[${dHand.cardTexts.join(',')}]`;

            if(pHand.total > 0) {
                if(pHand.total>21) {
                    resText = `YOU LOSE (BUST ${pHand.total} ${pTxt})`; resColor="#E53935"; outcome='lose';
                }
                else if(dHand.total>21) {
                    resText = `YOU WIN (DEALER BUST ${dHand.total} ${dTxt})`; resColor="#4CAF50"; outcome='win';
                }
                else if(pHand.total>dHand.total) {
                    resText = `YOU WIN (${pHand.total} ${pTxt} > ${dHand.total} ${dTxt})`; resColor="#4CAF50"; outcome='win';
                }
                else if(dHand.total>pHand.total) {
                    resText = `YOU LOSE (DEALER ${dHand.total} ${dTxt} > YOU ${pHand.total} ${pTxt})`; resColor="#E53935"; outcome='lose';
                }
                else {
                    resText=`PUSH (${pHand.total} ${pTxt} = ${dHand.total} ${dTxt})`; resColor="#FFC107"; outcome='push';
                }
            }

            // --- IMMEDIATE VISUAL UPDATE ---
            // Update the result text regardless of the timer status
            // This ensures "YOU WIN" is seen immediately.
            const timerHtml = autoBetTimer ? `<br><span style="color:#FFD700; font-size:10px; font-weight:normal; display:block; margin-top:3px;">Auto-betting in ${countdownValue}s...</span>` : '';

            dc.innerHTML = `<div id="bj-result-message" style="text-align:center; padding:5px; font-weight:bold; color:${resColor}; font-size:11px; border-bottom:1px solid #333; text-transform:uppercase;">${resText}${timerHtml}</div><div id="bj-recent-games" style="max-height:120px; overflow-y:auto; border:1px solid #333; padding:2px; background:rgba(0,0,0,0.3);"></div>`;
            renderRecentGamesList();

            // --- AUTO BET TRIGGER ---
            // Only start timer if round WAS in progress and NOT already counting down
            if (isRoundInProgress && outcome && !autoBetTimer) {
                isRoundInProgress = false;
                const mult = parseFloat(document.getElementById('bj-multiplier-input').value) || 2.0;
                const startBetVal = parseInt(document.getElementById('bj-start-bet-input').value.replace(/,/g, '')) || 0;

                const shouldBet = (outcome === 'win' && autoBetSettings.resetWin) ||
                                  (outcome === 'lose' && autoBetSettings.multLoss) ||
                                  (outcome === 'push' && autoBetSettings.multPush);

                if (shouldBet) {
                    countdownValue = 2;
                    // Re-render to show the initial "in 2s"
                    const el = document.getElementById('bj-result-message');
                    if (el) el.innerHTML = `${resText}<br><span style="color:#FFD700; font-size:10px; font-weight:normal; display:block; margin-top:3px;">Auto-betting in ${countdownValue}s...</span>`;

                    autoBetTimer = setInterval(() => {
                        countdownValue--;
                        const elUpdate = document.getElementById('bj-result-message');

                        // Update visuals every second
                        if (elUpdate) elUpdate.innerHTML = `${resText}<br><span style="color:#FFD700; font-size:10px; font-weight:normal; display:block; margin-top:3px;">Auto-betting in ${countdownValue}s...</span>`;

                        if (countdownValue <= 0) {
                            clearInterval(autoBetTimer);
                            autoBetTimer = null;

                            if (outcome === 'win') {
                                safeSetBet(startBetVal);
                            }
                            else if (outcome === 'lose') {
                                if (isDoubleActive) safeSetBet(Math.round(safeGetBet() * mult * mult));
                                else safeSetBet(Math.round(safeGetBet() * mult));
                            }
                            else if (outcome === 'push') {
                                safeSetBet(Math.round(safeGetBet() * mult));
                            }

                            // Clean up the text after betting
                            if (elUpdate) elUpdate.innerHTML = resText;
                        }
                    }, 1000);
                }
            }
        } else {
            // --- WAITING: 0 Dealer Cards ---
            panel.style.border = '2px solid #FFD700';
            dc.innerHTML = `<div id="bj-result-message" style="text-align:center; padding:5px; font-weight:bold; color:#888; font-size:12px; border-bottom:1px solid #333;">Waiting for Deal...</div><div id="bj-recent-games" style="max-height:120px; overflow-y:auto; border:1px solid #333; padding:2px; background:rgba(0,0,0,0.3);"></div>`;
            renderRecentGamesList();
        }
    }

    function getStrategyAction(p, d) {
        const dIdx = d===1?11:d;
        let action = 'Hit';
        if(p.isPair && p.cards.length === 2) {
            const v = p.cards[0]===11?11:p.cards[0];
            if(v===11||v===8) action = 'Split';
            else if(v===5) action = (dIdx<10)?'Double':'Hit';
            else if(v===10) action = 'Stand';
            else if(v===9) action = (dIdx!==7 && dIdx!==10 && dIdx!==11)?'Split':'Stand';
            else if(v===7) action = (dIdx<=7)?'Split':'Hit';
            else if(v===6) action = (dIdx<=6)?'Split':'Hit';
            else if(v===4) action = (dIdx===5||dIdx===6)?'Split':'Hit';
            else if(v===3||v===2) action = (dIdx<=7)?'Split':'Hit';
            else action = 'Hit';
        }
        else if(p.isSoft) {
            if(p.total>=19) action = 'Stand';
            else if(p.total===18) {
                if (p.cards.length > 2) action = (dIdx <= 8) ? 'Stand' : 'Hit';
                else action = (dIdx>=3 && dIdx<=6)?'Double':(dIdx<=8?'Stand':'Hit');
            }
            else {
                if (p.cards.length > 2) action = 'Hit';
                else action = (dIdx>=3 && dIdx<=6)?'Double':'Hit';
            }
        }
        else {
            if(p.total>=17) action = 'Stand';
            else if(p.total>=13) action = (dIdx<=6)?'Stand':'Hit';
            else if(p.total===12) action = (dIdx>=4&&dIdx<=6)?'Stand':'Hit';
            else if(p.total===11) action = 'Double';
            else if(p.total===10) action = (dIdx<10)?'Double':'Hit';
            else if(p.total===9) action = (dIdx>=3&&dIdx<=6)?'Double':'Hit';
            else action = 'Hit';
        }
        if (action === 'Double' && p.cards.length > 2) action = 'Hit';
        return action;
    }

    // --- UPDATED CARD PARSER (Fixes sum errors & extracts string) ---
    function getHandInfo(selector) {
       const container = document.querySelector(selector);
       if (!container) return { cards: [], cardTexts: [], total: 0, isSoft: false, isPair: false };

       // Select all card divs that contain 'card-' but are NOT the back
       const cardElements = Array.from(container.querySelectorAll('div[class*="card-"]'));
       const values = [];
       const texts = [];

       cardElements.forEach(el => {
           const match = el.className.match(/card-\w+-(\w+)/);
           if (match && !el.classList.contains('card-back')) {
               const rank = match[1].toUpperCase();
               texts.push(rank);

               let val = 0;
               if (['J', 'Q', 'K', '10'].includes(rank)) val = 10;
               else if (rank === 'A') val = 11;
               else val = parseInt(rank, 10);

               if(!isNaN(val)) values.push(val);
           }
       });

       let sum = values.reduce((a, b) => a + b, 0);
       let aces = values.filter(v => v === 11).length;

       while (sum > 21 && aces > 0) {
           sum -= 10;
           aces--;
       }

       return {
           cards: values,
           cardTexts: texts,
           total: sum,
           isSoft: aces > 0,
           isPair: values.length === 2 && values[0] === values[1]
       };
   }

    function initialize() {
        createTrackerPanel(); refreshTrackerUI();
        setInterval(() => { if(apiKey) importApiData(true); }, API_SYNC_INTERVAL_MS);
        new MutationObserver(() => { if(helperUpdateTimeout) clearTimeout(helperUpdateTimeout); helperUpdateTimeout=setTimeout(updateHelper,200); })
            .observe(document.body, {childList:true, subtree:true, attributes:true});
    }

    if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', initialize);
    else initialize();
})();