Blackjack ToolKit V5.0

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();