Blackjack ToolKit V3

based on S17 basic strategy , all in one tool buttons on the display to skip animations and fast bet buttons

当前为 2025-11-20 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Blackjack ToolKit V3
// @version       3.1
// @description   based on S17 basic strategy , all in one tool buttons on the display to skip animations and fast bet buttons
// @author        M7TEM
// @match         https://www.torn.com/page.php?sid=blackjack*
// @match         https://www.torn.com/pda.php*step=blackjack*
// @grant         none
// @license       MIT
// @namespace blackjack.toolkit
// ==/UserScript==

(function() {
    'use strict';

    // ============================================================================
    // =========================== PART 1: THE TRACKER ============================
    // ============================================================================

    // --- Constants & Storage Keys ---
    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';

    // --- Log IDs ---
    const LOG_ID_WIN = 8355;   // Blackjack Win (contains Winnings)
    const LOG_ID_LOSE = 8354;  // Blackjack Loss (contains Losses)
    const LOG_ID_PUSH = 8358;  // Blackjack Push
    const RESULT_LOG_IDS = [LOG_ID_WIN, LOG_ID_LOSE, LOG_ID_PUSH];
    const API_SYNC_INTERVAL_MS = 15 * 1000;
    const API_PULL_LIMIT = 100; // API default limit

    // --- State Variables ---
    let apiKey = '';
    let results = [];
    let totalProfit = 0;
    let isTrackerDragging = false;
    let currentOpacity = 0.8;
    let maxDisplayMatches = 50000;
    let isSessionActive = false;
    let sessionProfit = 0;
    let dailyProfit = 0;
    let sessionStartDate = 0;
    let isSyncing = false;
    let showSettings = false;
    let showStatsPanel = false;
    let currentStatsTimeframe = 7;
    let lastScannedTime = 0;
    let isHandActive = false;
    let chartInstance = null; // For the graph
    let helperUpdateTimeout = null; // <<< ADD THIS LINE for throttling
    let isMinimized = false;
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;
    let shiftKeyHeld = false; // New state variable for Shift key
    // --- Initialization ---
    function initializeTrackerState() {
        apiKey = localStorage.getItem(API_KEY_STORAGE) || '';
        results = JSON.parse(localStorage.getItem(STORAGE) || '[]');
        totalProfit = parseFloat(localStorage.getItem(PROFIT_STORAGE) || '0');
        maxDisplayMatches = parseInt(localStorage.getItem('bj_max_display') || '50000', 10);
        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);

        if (results.length > 0) {
            // FIX: Use setTimeout to ensure this expensive sort runs AFTER the browser finishes rendering the initial page content.
            setTimeout(() => {
                results.sort((a,b) => b.timestamp - a.timestamp);
                refreshTrackerUI();
            }, 0);
        }
    }

    initializeTrackerState();

    // --- Utility Functions ---

    function getTornDayStartTimestamp() {
        // Torn server time is UTC. Find the timestamp for the beginning of the current UTC day (00:00:00 UTC).
        const now = new Date();
        // Use UTC date methods to get the start of the current UTC day.
        const utcStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0));
        // Convert to Unix timestamp (seconds)
        return Math.floor(utcStart.getTime() / 1000);
    }
    function formatNumberToKMB(num) {
        if (typeof num !== 'number' || !isFinite(num)) return 'NaN/Error';

        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 generateNewApiKey() {
        const url = 'https://www.torn.com/preferences.php#tab=api?step=addNewKey&title=BlackJack+TK&user=log';
        window.open(url, '_blank');
        alert("Please Paste The API Key into the field below. You may need to refresh after saving the key.");
    }

    function getCurrentBet() {
        const visibleBetInput = document.querySelector('input.bet.input-money[type="text"]');
        if (visibleBetInput) {
            // Remove commas before parsing
            return parseInt(visibleBetInput.value.replace(/,/g, ''), 10) || 0;
        }
        return 0;
    }

    function setBet(newBetValue) {
        const visibleBetInput = document.querySelector('input.bet.input-money[type="text"]');
        const finalBetValue = Math.round(newBetValue);

        if (visibleBetInput) {
            // Torn's input expects a string representation
            visibleBetInput.value = String(finalBetValue);
            // Trigger events to update Torn's internal bet state
            visibleBetInput.dispatchEvent(new Event('change', { bubbles: true }));
            visibleBetInput.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }


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

        // Inject Tracker Styles
        const styleSheet = document.createElement("style");
        styleSheet.innerText = `
    /* --- Main Panel Styles --- */
    #${PANEL_ID} {
        position: fixed; top: 10px; right: 10px; z-index: 1000;
        background-color: #1a1a1a; /* Darker background */
        border: 2px solid #FFD700; /* Gold border for visibility */
        border-radius: 8px;
        padding: 8px;
        width: 180px;
        box-shadow: 0 4px 12px rgba(0,0,0,0.5);
        font-family: monospace;
        color: #E0E0E0; /* Light grey text for better contrast */
        line-height: 1.4;
    }

    /* --- Status Colors --- */
    .bj-green { color: #4CAF50; font-weight: bold; } /* Standard Win Green */
    .bj-red { color: #FF6F69; font-weight: bold; } /* Brighter red */
    .bj-grey { color: #AAAAAA; font-weight: normal; } /* Lighter grey for general data */
    .bj-gold { color: #FFD700; font-weight: bold; } /* New: Gold for highlights */

    /* --- Tracker Buttons (Sync, Settings, Stats) --- */
    .bj-trk-btn {
        background: #333; /* Darker background */
        border: 1px solid #555;
        color: #FFD700; /* Gold text */
        cursor: pointer;
        border-radius: 4px;
        padding: 5px;
        transition: background 0.2s, border-color 0.2s;
        font-size: 11px;
        font-weight: bold;
        line-height: 1;
        text-transform: uppercase;
    }
    .bj-trk-btn:hover {
        background: #444;
        border-color: #FFD700;
    }

    /* --- Strategy Helper Buttons (Hit, Stand, etc.) --- */
    .bj-helper-button {
        flex-grow: 1;
        padding: 6px 4px;
        font-size: 10px;
        font-weight: 700;
        cursor: pointer;
        color: #E0E0E0;
        background-color: #2a2a2a;
        border: 1px solid #555;
        border-radius: 4px;
        transition: background-color 0.15s;
        text-transform: uppercase;
    }
    .bj-helper-button:hover {
        background-color: #444;
    }

    /* --- Helper Advice Display --- */
    .bj-trk-advice {
        font-size: 20px;
        font-weight: 800;
        text-align: center;
        margin-bottom: 5px;
        padding: 5px 0;
        border-bottom: 1px solid #444;
        text-shadow: 0 0 5px rgba(0,0,0,0.8);
    }
    .bj-trk-hand-info { /* New class for hand detail */
        font-size: 10px;
        color: #aaa;
        text-align: center;
        margin-bottom: 5px;
    }

    /* --- Action Highlight Colors --- */
    .bj-hit { border-color: #FF5722 !important; background-color: rgba(255, 87, 34, 0.2) !important; }
    .bj-stand, .bj-double { border-color: #4CAF50 !important; background-color: rgba(76, 175, 80, 0.2) !important; }
    .bj-split { border-color: #2196F3 !important; background-color: rgba(33, 150, 243, 0.2) !important; }
    .bj-surrender { border-color: #FFD700 !important; background-color: rgba(255, 215, 0, 0.2) !important; }
    .bj-idle { border-color: transparent !important; }
`;
        document.head.appendChild(styleSheet);

        const panel = document.createElement('div');
        panel.id = PANEL_ID;
        Object.assign(panel.style, {
            position: 'fixed', top: '50px', left: '20px', width: '350px',
            background: `rgba(0,0,0,${currentOpacity})`, color: '#fff',
            fontFamily: 'monospace', fontSize: '13px', padding: '10px',
            borderRadius: '8px', boxShadow: '0 0 15px rgba(0,0,0,0.8)',
            zIndex: '999998', transformOrigin: 'top left',
            display: 'flex', flexDirection: 'column', gap: '8px',
            border: '2px solid transparent', // Dynamic border for helper mode
        });
        document.body.appendChild(panel);

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

        // Header / Drag Handle
        const header = document.createElement('div');
        Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'move', borderBottom: '1px solid #333', paddingBottom: '5px', marginBottom: '5px' });
        header.innerHTML = '<span style="font-weight:bold; color: #FFD700;">♠️ BJ Tracker & Helper</span>';
        const minimizeBtn = document.createElement('span');
        minimizeBtn.textContent = isMinimized ? '[+]' : '[-]'; minimizeBtn.style.cursor = 'pointer';


        minimizeBtn.onclick = () => {
            isMinimized = !isMinimized;
            minimizeBtn.textContent = isMinimized ? '[+]' : '[-]';
            const c = document.getElementById('bj-trk-content');
            c.style.display = c.style.display === 'none' ? 'flex' : 'none';
            localStorage.setItem('bj_tracker_minimized', c.style.display === 'none' ? 'true' : 'false');
        };
        header.appendChild(minimizeBtn);
        panel.appendChild(header);

        // Restore minimized state
        if (localStorage.getItem('bj_tracker_minimized') === 'true') {
            const contentToMinimize = document.getElementById('bj-trk-content');
            if(contentToMinimize) contentToMinimize.style.display = 'none';
        }

        // 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;
                panel.style.left = (e.clientX - offsetX) + 'px';
                panel.style.top = (e.clientY - offsetY) + '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);
        };

        // Content Container
        const content = document.createElement('div');
        content.id = 'bj-trk-content';
        Object.assign(content.style, { display: 'flex', flexDirection: 'column', gap: '8px' });
        panel.appendChild(content);

        // Stats Display
        const statsContainer = document.createElement('div');
        statsContainer.style.display = 'block';
        statsContainer.style.gap = '5px';
        statsContainer.innerHTML = `
            <div id="bj-total-profit" title="All Time Profit">Life Time Profit: <span class="bj-grey">loading...</span></div>
            <div id="bj-daily-profit" title="Profit since Torn Daily Reset">Daily Profit: <span class="bj-grey">--</span></div>
            <div id="bj-session-profit" title="Session Profit">Session Profit: <span class="bj-grey">--</span></div>
            <div id="bj-winrate" style="grid-column: 1 / -1; text-align: center; font-size: 11px; color: #aaa;">Wins: 0 | Losses: 0 | Pushes: 0</div>
            <div id="bj-sync-status" style="grid-column: 1 / -1; text-align: center; font-size: 10px; color: #666;">Next Sync: --</div>
        `;
        content.appendChild(statsContainer);

        // Buttons Row (Session, Stats, Settings, Sync)
        const actionRow = document.createElement('div');
        Object.assign(actionRow.style, { display: 'flex', gap: '5px', marginTop: '5px' });

        const btnSettings = document.createElement('button'); btnSettings.className = 'bj-trk-btn'; btnSettings.textContent = '⚙️'; btnSettings.style.flex = '1';
        btnSettings.onclick = () => { showSettings = !showSettings; showStatsPanel = false; refreshTrackerUI(); };

        const btnStats = document.createElement('button'); btnStats.className = 'bj-trk-btn'; btnStats.textContent = '📊 Graph'; btnStats.style.flex = '1';
        btnStats.onclick = () => { showStatsPanel = !showStatsPanel; showSettings = false; refreshTrackerUI(); if(showStatsPanel) updateGraph(); };

        const btnSession = document.createElement('button'); btnSession.className = 'bj-trk-btn'; btnSession.textContent = isSessionActive ? '⏹ Stop' : '▶ Start'; btnSession.style.flex = '1';
        btnSession.onclick = toggleSession;

        const btnSync = document.createElement('button'); btnSync.className = 'bj-trk-btn'; btnSync.textContent = '🔄'; btnSync.style.flex = '0.5';
        btnSync.onclick = () => importApiData(false);

        actionRow.append(btnSession, btnStats, btnSettings, btnSync);
        content.appendChild(actionRow);

        // Persistent Betting Controls Container (FIX: Always visible)
        const bettingControls = document.createElement('div');
        bettingControls.id = 'bj-betting-controls';
        content.appendChild(bettingControls);

        // Dynamic Content Container (Helper or Recent Games List)
        const dynamicContent = document.createElement('div');
        dynamicContent.id = 'bj-dynamic-content';
        content.appendChild(dynamicContent);
        // Add the persistent list container structure (only once)
        dynamicContent.innerHTML = `
    <div id="bj-recent-games" style="max-height: 100px; overflow-y: auto; border: 1px solid #333; padding: 4px; background: rgba(0,0,0,0.3);">
    </div>
`;

        // Settings Panel (Tracker + Helper Settings)
        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'bj-settings-panel';
        Object.assign(settingsPanel.style, { display: 'none', flexDirection: 'column', gap: '8px', padding: '10px', background: '#111', border: '1px solid #444', marginTop: '5px' });
        settingsPanel.innerHTML = `
    <h4 style="margin:0; border-bottom:1px solid #333; padding-bottom:3px;">API & Data Settings</h4>
    <label style="display:block; margin-bottom: 3px; font-size: 0.9em;">API Key: <input type="password" id="bj-api-input" value="${apiKey}" style="width:95%; background:#222; border:1px solid #555; color:white; padding: 2px; box-sizing: border-box;"></label>
    <div style="display:flex; gap: 5px; margin-bottom: 5px;">
        <button id="bj-save-api" class="bj-trk-btn" style="flex: 1.5; padding: 3px;">Save Key & Full Sync</button>
        <button id="bj-generate-api" class="bj-trk-btn" style="flex: 1; padding: 3px;">Generate Key</button>
    </div>

    <h4 style="margin:5px 0 3px 0; border-bottom:1px solid #333; padding-bottom:3px;">Helper & Bet Settings</h4>

    <div style="display:flex; flex-direction: column; gap: 3px;">
        <label style="font-size: 0.9em;">
            Start Bet:
            <input type="text" id="bj-start-bet-input" value="${localStorage.getItem('bj_start_bet_value') || '20000'}"
                   style="width:80px; text-align:right; background:#333; color:white; border:1px solid #555; padding: 2px;">
        </label>
        <label style="font-size: 0.9em;">
            Multiplier:
            <input type="number" id="bj-multiplier-input" value="${localStorage.getItem('bj_multiplier_value') || '2.2'}" step="0.1"
                   style="width:60px; background:#333; color:white; border:1px solid #555; padding: 2px;">
        </label>
    </div>

    <button id="bj-reset-data" class="bj-trk-btn" style="color: #E53935; border-color: #E53935; margin-top: 8px; padding: 3px;">Reset All Data</button>
`;
        content.appendChild(settingsPanel);

        setupSettingsEvents();
        setupPersistentControls();

        // Stats/Graph Panel
        const statsPanel = document.createElement('div');
        statsPanel.id = 'bj-stats-panel';
        Object.assign(statsPanel.style, { display: 'none', flexDirection: 'column', marginTop: '10px', padding: '5px', background: '#1a1a1a', border: '1px solid #444' });
        statsPanel.innerHTML = `
            <canvas id="bj-graph-canvas" width="320" height="180"></canvas>
            <div style="display:flex; gap:5px; 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>
        `;
        content.appendChild(statsPanel);
        statsPanel.querySelectorAll('button').forEach(btn => {
            btn.onclick = () => { currentStatsTimeframe = parseInt(btn.getAttribute('data-days')); updateGraph(); }
        });
    }

    function setupSettingsEvents() {
        setTimeout(() => {
            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'); // Force full scan next time
                lastScannedTime = 0;
                importApiData(false); // Force silent=false to give feedback
            };
            document.getElementById('bj-generate-api').onclick = generateNewApiKey;

            // --- FIXED RESET LOGIC ---
            document.getElementById('bj-reset-data').onclick = () => {
                if(confirm("Reset ALL tracking data? This will clear all games, profit, session data, AND the API key, forcing a fresh start.")) {
                    // Tracker Data Reset
                    results = []; totalProfit = 0; sessionProfit = 0;
                    localStorage.removeItem(STORAGE);
                    localStorage.removeItem(PROFIT_STORAGE);
                    localStorage.removeItem(LAST_SCANNED_TIMESTAMP);
                    localStorage.removeItem(SESSION_ACTIVE_KEY);
                    localStorage.removeItem(SESSION_PROFIT_KEY);
                    localStorage.removeItem(SESSION_START_KEY);
                    lastScannedTime = 0;

                    // API Key Reset
                    apiKey = '';
                    localStorage.removeItem(API_KEY_STORAGE);
                    const apiInput = document.getElementById('bj-api-input');
                    if(apiInput) apiInput.value = '';

                    // Clear graph instance (if exists)
                    if (chartInstance) chartInstance = null;

                    // Re-initialize state and refresh UI
                    refreshTrackerUI();
                    updateHelper(); // Force dynamic content update to list
                }
            };

            // Helper/Betting Events
            document.getElementById('bj-start-bet-input').onchange = (e) => localStorage.setItem('bj_start_bet_value', e.target.value);
            document.getElementById('bj-multiplier-input').onchange = (e) => {
                localStorage.setItem('bj_multiplier_value', e.target.value);
            };

        }, 500);
    }

    // Function to create and setup the persistent betting/play controls
    function setupPersistentControls() {
        const bettingControls = document.getElementById('bj-betting-controls');
        if (!bettingControls) return;

        // Betting Actions Row
        const betRow = document.createElement('div');
        Object.assign(betRow.style, { display: 'flex', gap: '5px', marginTop: '5px' });

        const btnStart = document.createElement('button'); btnStart.id = 'bj-start-game'; btnStart.className = 'bj-helper-button'; btnStart.textContent = 'PLAY / Deal'; btnStart.style.backgroundColor = '#F9A825'; btnStart.style.color = '#333';
        const btnResetBet = document.createElement('button'); btnResetBet.id = 'bj-reset-bet'; btnResetBet.className = 'bj-helper-button'; btnResetBet.textContent = 'Starting Bet';
        const btnMult = document.createElement('button'); btnMult.id = 'bj-multiply-bet'; btnMult.className = 'bj-helper-button';
        const btnDiv = document.createElement('button'); btnDiv.id = 'bj-divide-bet'; btnDiv.className = 'bj-helper-button';

        const mult = parseFloat(localStorage.getItem('bj_multiplier_value') || '2.5').toFixed(2);
        btnMult.textContent = `x ${mult}`;
        btnDiv.textContent = `/ ${mult}`;

        betRow.append(btnStart, btnResetBet, btnMult, btnDiv);
        bettingControls.appendChild(betRow);

        // Setup events for the persistent buttons (passing null for non-existent game buttons)
        setupHelperButtonEvents(btnStart, btnResetBet, btnMult, btnDiv, null, null, null, null);
    }


    // --- Dynamic Content Rendering ---

    function renderRecentGamesList() {
        const listContainer = document.getElementById('bj-recent-games');
        if (!listContainer) return; // Must have the container created above

        listContainer.style.maxHeight = '100px'; // Increased height for better visibility
        listContainer.style.overflowY = 'auto';
        listContainer.style.border = '1px solid #333';
        listContainer.style.padding = '4px';
        listContainer.style.background = 'rgba(0,0,0,0.3)';

        const sortedResults = results.slice().sort((a,b) => b.timestamp - a.timestamp);
        let html = '';

        sortedResults.slice(0, maxDisplayMatches).forEach((r, index) => { // ADDED INDEX
            let color = '#888'; let sign = '';
            let displayProfit = Math.abs(r.profit);

            // *** STEP 3 LOGIC INTEGRATION ***
            let outcome = r.result.toUpperCase();
            if (r.isNatural) {
                outcome = 'NATURAL';
            } else if (r.result === 'win' && r.isDouble) {
                outcome = 'WIN DOUBLE';
            } else if (r.result === 'lose' && r.isDouble) {
                outcome = 'LOSE DOUBLE';
            }
            // ... color logic remains the same
            if (r.result === 'win') {
                color = '#4CAF50';
                sign = '+';
            }
            else if (r.result === 'lose') {
                color = '#E53935';
                sign = '-';
            }
            else if (r.result === 'push') {
                color = '#FFC107';
                sign = '';
            }
            // ********************************

            html += `<div style="font-size:11px; display:flex; justify-content:space-between; border-bottom:1px solid #222;">
            <span style="color:#aaa;">#${results.length - index}</span>
            <span style="color:${color}; font-weight:bold;">${outcome}</span>
            <span style="color:${color};">${sign}$${formatNumberToKMB(displayProfit)}</span>
        </div>`;
        });

        listContainer.innerHTML = html; // Only update the inner content
        // NO RETURN STATEMENT NEEDED
    }

    // Only creates advice and game buttons
    function renderHelperContent(advice, handInfo, actionColor) {
        const helperContainer = document.createElement('div');
        helperContainer.id = 'bj-helper-content';
        Object.assign(helperContainer.style, {
            padding: '5px',
            background: '#1a1a1a',
            border: '1px solid #444',
            borderRadius: '4px',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px'
        });

        // 1. Advice Text
        const adviceDiv = document.createElement('div');
        adviceDiv.className = 'bj-trk-advice';
        adviceDiv.id = 'bj-helper-advice-text';
        adviceDiv.textContent = advice;
        adviceDiv.style.setProperty('--bj-border-color', actionColor);
        helperContainer.appendChild(adviceDiv);

        // 2. Hand Info
        const infoDiv = document.createElement('div');
        infoDiv.id = 'bj-helper-hand-total';
        infoDiv.style.fontSize = '12px';
        infoDiv.style.opacity = '0.9';
        infoDiv.textContent = handInfo;
        helperContainer.appendChild(infoDiv);


        // 3. Game Actions (Dynamic: Hit, Stand, Double, Split)
        const gameRow = document.createElement('div');
        Object.assign(gameRow.style, { display: 'flex', gap: '5px' });

        const btnHit = document.createElement('button'); btnHit.id = 'bj-hit-btn'; btnHit.className = 'bj-helper-button'; btnHit.textContent = 'HIT';
        const btnStand = document.createElement('button'); btnStand.id = 'bj-stand-btn'; btnStand.className = 'bj-helper-button'; btnStand.textContent = 'STAND';
        const btnDouble = document.createElement('button'); btnDouble.id = 'bj-double-btn'; btnDouble.className = 'bj-helper-button'; btnDouble.textContent = 'DBL';
        const btnSplit = document.createElement('button'); btnSplit.id = 'bj-split-btn'; btnSplit.className = 'bj-helper-button'; btnSplit.textContent = 'SPLIT';

        gameRow.append(btnHit, btnStand, btnDouble, btnSplit);
        helperContainer.appendChild(gameRow);

        // Setup dynamic button classes and events
        setupHelperButtonClasses(advice, btnHit, btnStand, btnDouble, btnSplit);
        // Only set up game button events here (passing null for betting buttons)
        setupHelperButtonEvents(null, null, null, null, btnHit, btnStand, btnDouble, btnSplit);

        return helperContainer;
    }

    function setupHelperButtonClasses(advice, btnHit, btnStand, btnDouble, btnSplit) {
        // Reset all buttons
        [btnHit, btnStand, btnDouble, btnSplit].forEach(btn => btn.className = 'bj-helper-button');

        // Highlight the recommended action
        if (advice.startsWith(AC.H)) {
            btnHit.classList.add('bj-hit');
        } else if (advice.startsWith(AC.S)) {
            btnStand.classList.add('bj-stand');
        } else if (advice.startsWith(AC.D)) {
            btnDouble.classList.add('bj-double');
        } else if (advice.startsWith(AC.SP)) {
            btnSplit.classList.add('bj-split');
        }
    }

    function setupHelperButtonEvents(btnStart, btnResetBet, btnMult, btnDiv, btnHit, btnStand, btnDouble, btnSplit) {
        // Game Actions (only set up if buttons exist)
        const clickAction = (step, confirm) => {
            const area = document.querySelector(`area[data-step="${step}"]`);
            if(area) { area.dispatchEvent(new MouseEvent('click', {bubbles:true})); if(confirm) setTimeout(() => { const c=document.querySelector('.action-wrap .confirm-action.yes'); if(c) c.click(); }, 100); }
        };
        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);

        // Betting Actions (only set up if buttons exist - these are the persistent ones)
        if(btnStart) btnStart.onclick = () => {
            const btn = document.querySelector('a.startGame[data-step="startGame"]');
            if(btn) { btn.click(); setTimeout(() => { const yes=document.querySelector('.bet-confirm .yes'); if(yes) yes.click(); }, 100); }
        };
        if(btnResetBet) btnResetBet.onclick = () => {
            const val = document.getElementById('bj-start-bet-input').value;
            setBet(parseInt(val.replace(/,/g,''),10));
        };
        if(btnMult) btnMult.onclick = () => {
            const mult = parseFloat(document.getElementById('bj-multiplier-input').value) || 2.5;
            setBet(Math.round(getCurrentBet() * mult));
        };
        if(btnDiv) btnDiv.onclick = () => {
            const mult = parseFloat(document.getElementById('bj-multiplier-input').value) || 2.5;
            setBet(Math.max(100, Math.round(getCurrentBet() / mult)));
        };
    }

    // This function updates the UI elements with current data (non-freezing)
    function refreshTrackerUI() {
        const totalEl = document.getElementById('bj-total-profit');
        if (totalEl) totalEl.innerHTML = `Life Time Profit: <span class="${totalProfit >= 0 ? 'bj-green' : 'bj-red'}">$${formatNumberToKMB(totalProfit)}</span>`;
        const dailyEl = document.getElementById('bj-daily-profit'); // ADD THIS LINE
        if (dailyEl) dailyEl.innerHTML = `Daily Profit: <span class="${dailyProfit >= 0 ? 'bj-green' : 'bj-red'}">$${formatNumberToKMB(dailyProfit)}</span>`; // ADD THIS LINE
        const sessionEl = document.getElementById('bj-session-profit');
        if (sessionEl) sessionEl.innerHTML = `Session Profit: <span class="${isSessionActive ? (sessionProfit >= 0 ? 'bj-green' : 'bj-red') : 'bj-grey'}">${isSessionActive ? '$' + formatNumberToKMB(sessionProfit) : 'Inactive'}</span>`;

        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 winrateEl = document.getElementById('bj-winrate');
        if (winrateEl) {
            winrateEl.innerHTML = `
        <span class="bj-green">Wins:</span> ${wins} |
        <span class="bj-red">Losses:</span> ${losses} |
        <span class="bj-gold">Pushes:</span> ${pushes} |
        Total: ${results.length}
    `;
            // Note: The total matches text 'Total:' is gray by default from the container style,
            // but the number will be white (or default)
        }
        const settingsEl = document.getElementById('bj-settings-panel');
        if (settingsEl) settingsEl.style.display = showSettings ? 'flex' : 'none';

        const statsEl = document.getElementById('bj-stats-panel');
        if (statsEl) statsEl.style.display = showStatsPanel ? 'flex' : 'none';

        // Update session button text
        const btnSession = document.querySelector('.bj-trk-btn[textContent*="Start"], .bj-trk-btn[textContent*="Stop"]');
        if (btnSession) btnSession.textContent = isSessionActive ? '⏹ Stop' : '▶ Start';

        // Update sync status with scan status
        const status = document.getElementById('bj-sync-status');
        if (status) {
            const nextSyncTime = new Date(Date.now() + API_SYNC_INTERVAL_MS).toLocaleTimeString();
            const scanStatus = lastScannedTime > 1 ? 'Fully Scanned' : (lastScannedTime === 0 ? 'No Data/API Key Missing' : 'Partial Scan (Recent)');
            status.textContent = `${isSyncing ? 'Syncing...' : scanStatus} | Next Sync: ${nextSyncTime}`;
        }
    }

    function toggleSession() {
        isSessionActive = !isSessionActive;
        if (isSessionActive) {
            sessionProfit = 0;
            sessionStartDate = Date.now();
            // Recalculate session profit from existing results
            const currentSessionProfit = results
            .filter(r => r.timestamp * 1000 >= sessionStartDate)
            .reduce((sum, r) => sum + r.profit, 0);
            sessionProfit = currentSessionProfit;

        } 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();
    }

    // --- API Sync (Tracking functions) ---
    async function importApiData(silent = true) {
        if (isSyncing) return;
        if (!apiKey) { if(!silent) alert("Please enter your API key in the settings."); return; }
        isSyncing = true;
        const status = document.getElementById('bj-sync-status');
        if(status) status.textContent = "Syncing... (0 games found)";

        const initialTotalGames = results.length;
        const latestTimestamp = results.length > 0 ? results[0].timestamp : 0;
        let oldestTimestamp = results.length > 0 ? results[results.length - 1].timestamp : 0;

        let totalNewGames = 0;
        let isFullScanNeeded = latestTimestamp === 0 || lastScannedTime < 2 || !silent;

        try {
            // PHASE 1: FORWARD/CATCHUP SCAN
            if (latestTimestamp > 0 || !isFullScanNeeded) {
                const fromTime = latestTimestamp > 0 ? latestTimestamp : (Date.now()/1000) - (24 * 3600);
                const forwardLogs = await fetchLogs(fromTime);
                if (forwardLogs.resultCount > 0) {
                    processLogs(forwardLogs.resultLogs);}
            }

            // PHASE 2: BACKWARD/FULL SCAN
            if (isFullScanNeeded) {
                if(status) status.textContent = `Syncing... Starting full history scan.`;

                let currentScanPoint = oldestTimestamp > 0 ? oldestTimestamp : Math.floor(Date.now() / 1000);
                let keepScanning = true;
                let backwardCount = 0;
                let scanIterations = 0;

                while (keepScanning && scanIterations < 500) {
                    if (lastScannedTime > 1 && currentScanPoint < lastScannedTime) {
                        keepScanning = false;
                        break;
                    }

                    const backwardLogs = await fetchLogs(null, currentScanPoint);

                    if (backwardLogs.resultCount > 0) {
                        const oldestLogTimestamp = Math.min(...backwardLogs.resultLogs.map(l => l.timestamp));
                        processLogs(backwardLogs.resultLogs);
                        backwardCount = backwardLogs.resultLogs.length;

                        oldestTimestamp = results.length > 0 ? results[results.length - 1].timestamp : 0;
                        if(status) status.textContent = `Syncing... Games found: ${results.length} (Batch: ${backwardCount})`;

                        if (backwardCount < API_PULL_LIMIT || oldestLogTimestamp === currentScanPoint) {
                            keepScanning = false;
                            lastScannedTime = 1; // Mark as done only if the API returned less than limit
                        } else {
                            currentScanPoint = oldestLogTimestamp - 1;
                        }
                    } else {
                        keepScanning = false;
                        lastScannedTime = oldestTimestamp === 0 ? currentScanPoint : oldestTimestamp;
                    }
                    scanIterations++;
                }

                localStorage.setItem(LAST_SCANNED_TIMESTAMP, lastScannedTime.toString());
            }

            totalProfit = results.reduce((sum, r) => sum + r.profit, 0);
            sessionProfit = results.filter(r => r.timestamp * 1000 >= sessionStartDate).reduce((sum, r) => sum + r.profit, 0);
            totalNewGames = results.length - initialTotalGames;
            const tornDayStart = getTornDayStartTimestamp();
            dailyProfit = results.filter(r => r.timestamp >= tornDayStart).reduce((sum, r) => sum + r.profit, 0);
            localStorage.setItem(STORAGE, JSON.stringify(results));
            localStorage.setItem(PROFIT_STORAGE, totalProfit.toString());
            localStorage.setItem(SESSION_PROFIT_KEY, sessionProfit.toString());
            localStorage.setItem(LAST_SYNC_KEY, Date.now().toString());

            if(!silent && totalNewGames > 0) alert(`Synced ${totalNewGames} new games.`);
            else if (!silent && totalNewGames === 0) alert("No new games found.");

            // FIX: Sorting is now deferred to prevent UI freeze after data update
            setTimeout(() => {
                results.sort((a,b) => b.timestamp - a.timestamp);
                refreshTrackerUI();
                if(showStatsPanel) updateGraph();
            }, 10);

        } catch (e) {
            console.error("API Sync Error:", e);
            if(!silent) alert(`Sync failed. Check console for details. Error: ${e.message}`);
        }
        finally {
            isSyncing = false;
            refreshTrackerUI();
        }
    }

    async function fetchLogs(fromTime = null, toTime = null) {
        let resultLogUrl = `https://api.torn.com/user/?selections=log&log=${RESULT_LOG_IDS.join(',')}&key=${apiKey}`;

        if (fromTime) {
            resultLogUrl += `&from=${fromTime + 1}`;
        }
        if (toTime) {
            resultLogUrl += `&to=${toTime}`;
        }


        let rawResultLogs = [];

        const fetchResultLogs = new Promise(resolve => {
            GM.xmlHttpRequest({
                method: "GET", url: resultLogUrl,
                onload: (r) => {
                    try { rawResultLogs = Object.values(JSON.parse(r.responseText).log || {}); } catch(e) { console.error("Error fetching result logs:", e); }
                    resolve();
                },
                onerror: () => resolve()
            });
        });

        await Promise.all([fetchResultLogs]);

        return {
            resultLogs: rawResultLogs,
            resultCount: rawResultLogs.length
        };
    }

    function processLogs(rawResultLogs) {
        const existingResults = new Set(results.map(r => r.timestamp));

        // 2. Process Result Logs and Match Bets
        for (const log of rawResultLogs) {
            if (existingResults.has(log.timestamp)) continue;

            let res, prof = 0, bet = 0;

            // --- PROFIT CALCULATION FIX ---
            // 1. Natural Win Detection (Use the reliable win_state string)
            const isNaturalWin = log.log === LOG_ID_WIN && (log.data.win_state || '').includes('natural');


            // --- PROFIT CALCULATION FIX: Relying on API Winnings/Losses for Profit ---
            if (log.log === LOG_ID_WIN) {
                let winningsValue = log.data.winnings || 0;

                // The WINNINGS field is the Total Return (Stake + Profit).
                // To get the Profit, we calculate the original stake and subtract it from the winnings.
                if (isNaturalWin) {
                    // Natural pays 1.5x profit (2.5x total return). Profit is Winnings / 2.5 * 1.5
                    const stake = winningsValue / 2.5;
                    prof = winningsValue - stake; // OR prof = stake * 1.5;
                } else {
                    // Standard/Double Win pays 1x profit (2x total return). Profit is Winnings / 2
                    const stake = winningsValue / 2;
                    prof = winningsValue - stake; // OR prof = stake * 1;
                }

                res = 'win';

            } else if (log.log === LOG_ID_LOSE) {
                // LOSE: The 'losses' field contains the full stake lost.
                prof = -1 * (log.data.losses || 0);
                res = 'lose';

            } else if (log.log === LOG_ID_PUSH) {
                res = 'push';
                prof = 0;
            }
            // -----------------------------------------

            if (res) {
                results.push({
                    result: res,
                    profit: prof,
                    timestamp: log.timestamp,
                    logId: log.log,
                    isNatural: isNaturalWin
                });
                existingResults.add(log.timestamp);
            }
        }
    }

    // --- NEW FUNCTION: Countdown Logic ---
    let syncCountdownInterval = null;

    function updateSyncCountdown() {
        const nextSyncTime = parseInt(localStorage.getItem(LAST_SYNC_KEY) || '0', 10) + API_SYNC_INTERVAL_MS;
        const now = Date.now();
        const timeLeftMs = nextSyncTime - now;
        const syncStatusEl = document.getElementById('bj-sync-status');

        if (!syncStatusEl) {
            clearInterval(syncCountdownInterval);
            syncCountdownInterval = null;
            return;
        }

        if (timeLeftMs > 0) {
            const seconds = Math.floor(timeLeftMs / 1000) % 60;
            const minutes = Math.floor(timeLeftMs / 1000 / 60);

            const minutesStr = String(minutes).padStart(2, '0');
            const secondsStr = String(seconds).padStart(2, '0');

            syncStatusEl.innerHTML = `Next Sync: ${minutesStr}:${secondsStr}`;
        } else {
            syncStatusEl.textContent = 'Next Sync: Soon...';
        }
    }

    // --- Dragging Functions ---
    function startDragging(e) {
        const panel = document.getElementById(PANEL_ID);
        if (!panel || e.button !== 0 || !shiftKeyHeld) return; // Only drag on left-click AND Shift hold

        isDragging = true;
        panel.style.cursor = 'grabbing';

        // Calculate the offset from the mouse to the panel's top-left corner
        dragOffsetX = e.clientX - panel.getBoundingClientRect().left;
        dragOffsetY = e.clientY - panel.getBoundingClientRect().top;

        panel.style.position = 'fixed';
        e.preventDefault();
    }

    function stopDragging() {
        if (!isDragging) return;

        const panel = document.getElementById(PANEL_ID);
        if (panel) panel.style.cursor = 'grab';

        isDragging = false;
    }

    function dragPanel(e) {
        if (!isDragging) return;

        const panel = document.getElementById(PANEL_ID);
        if (!panel) return;

        // Calculate new position
        let newX = e.clientX - dragOffsetX;
        let newY = e.clientY - dragOffsetY;

        // --- JUMP TO STEP 3: APPLY BOUNDARY CHECKS HERE ---
        const panelRect = panel.getBoundingClientRect();
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;

        // Boundary Check (Cannot go over edges)
        newX = Math.max(0, Math.min(newX, windowWidth - panelRect.width));
        newY = Math.max(0, Math.min(newY, windowHeight - panelRect.height));
        // --- END BOUNDARY CHECK ---

        panel.style.left = `${newX}px`;
        panel.style.top = `${newY}px`;
    }

    function updateGraph() {
        const canvas = document.getElementById('bj-graph-canvas');
        if (!canvas) return;

        if (!showStatsPanel) return;

        const ctx = canvas.getContext('2d');
        ctx.clearRect(0,0,canvas.width,canvas.height);

        const cutoff = (Date.now()/1000) - (currentStatsTimeframe*24*3600);
        const points = results.filter(r => r.timestamp >= cutoff).sort((a,b) => a.timestamp - b.timestamp);

        if(points.length > 0) {
            let acc = 0;
            // Calculate accumulated profit for the selected timeframe
            points.forEach(p => { acc += p.profit; });
            dailyProfit = acc; // Store the result
        } else {
            dailyProfit = 0;
        }
        if(points.length < 2) {
            ctx.fillStyle = '#666';
            ctx.font = '14px monospace';
            ctx.textAlign = 'center';
            ctx.fillText('Not enough data to graph.', canvas.width / 2, canvas.height / 2);
            return;
        }

        let acc = 0;
        const data = points.map(p => { acc += p.profit; return { t: p.timestamp, v: acc }; });

        const pad = 20;
        const W = canvas.width - pad*2; const H = canvas.height - pad*2;
        const minV = Math.min(0, ...data.map(d=>d.v)); const maxV = Math.max(0, ...data.map(d=>d.v));
        const minT = data[0].t; const maxT = data[data.length-1].t;

        const vRange = maxV - minV || 1;
        const tRange = maxT - minT || 1;

        const mapX = t => pad + ((t-minT)/tRange)*W;
        const mapY = v => (canvas.height-pad) - ((v-minV)/vRange)*H;

        // Zero Line
        const zy = mapY(0);
        ctx.strokeStyle = '#444'; ctx.beginPath(); ctx.moveTo(pad, zy); ctx.lineTo(canvas.width-pad, zy); ctx.stroke();

        // Graph Line
        ctx.strokeStyle = '#FFD700'; ctx.lineWidth = 2; ctx.beginPath();
        ctx.moveTo(mapX(data[0].t), mapY(data[0].v));
        data.forEach(d => ctx.lineTo(mapX(d.t), mapY(d.v)));
        ctx.stroke();
    }


    // ============================================================================
    // ======================== PART 2: THE STRATEGY HELPER =======================
    // ============================================================================

    const AC = { H: 'Hit', S: 'Stand', D: 'Double', SP: 'Split', SURR: 'Surrender' };
    const strategy = {
        hard: {
            3: Array(11).fill(AC.H), 4: Array(11).fill(AC.H), 5: Array(11).fill(AC.H), 6: Array(11).fill(AC.H), 7: Array(11).fill(AC.H), 8: Array(11).fill(AC.H),
            9: [AC.H, AC.D, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            10: [AC.H, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H],
            11: [AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.H],
            12: [AC.H, AC.H, AC.S, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            13: [AC.S, AC.S, AC.S, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            14: [AC.S, AC.S, AC.S, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            15: [AC.S, AC.S, AC.S, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            16: [AC.S, AC.S, AC.S, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            17: Array(11).fill(AC.S), 18: Array(11).fill(AC.S), 19: Array(11).fill(AC.S), 20: Array(11).fill(AC.S), 21: Array(11).fill(AC.S),
        },
        soft: {
            13: [AC.H, AC.H, AC.H, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            14: [AC.H, AC.H, AC.H, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            15: [AC.H, AC.H, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            16: [AC.H, AC.H, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            17: [AC.H, AC.D, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            18: [AC.S, AC.D, AC.D, AC.D, AC.D, AC.S, AC.S, AC.H, AC.H, AC.H, AC.H],
            19: Array(11).fill(AC.S), 20: Array(11).fill(AC.S),
        },
        pair: {
            2: [AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.H, AC.H, AC.H, AC.H, AC.H],
            3: [AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.H, AC.H, AC.H, AC.H, AC.H],
            4: [AC.H, AC.H, AC.H, AC.SP, AC.SP, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            5: [AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.D, AC.H, AC.H, AC.H],
            6: [AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.H, AC.H, AC.H, AC.H, AC.H, AC.H],
            7: [AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.H, AC.H, AC.H, AC.H, AC.H],
            8: Array(11).fill(AC.SP),
            9: [AC.SP, AC.SP, AC.SP, AC.SP, AC.SP, AC.S, AC.SP, AC.SP, AC.S, AC.S, AC.S],
            10: Array(11).fill(AC.S),
            11: Array(11).fill(AC.SP),
        }
    };

    // --- Helper Logic (Card Reading & Strategy) ---
    function getCardValue(el) {
        if(!el) return 0;
        const match = el.className.match(/card-\w+-(\w+)/);
        if(!match) return 0;
        const r = match[1];
        return (['10','J','Q','K'].includes(r)) ? 10 : (r === 'A' ? 11 : parseInt(r,10));
    }

    function getHandInfo(sel) {
        const els = document.querySelectorAll(`${sel} div[class*="card-"]:not(.card-back)`);
        const cards = Array.from(els).map(e => getCardValue(e)).filter(v=>v>0);
        let initialSum = cards.reduce((a,b)=>a+b,0);
        let aces = cards.filter(v=>v===11).length;
        let sum = initialSum;
        while(sum>21 && aces-->0) sum-=10;

        const isPair = cards.length===2 && cards[0]===cards[1];

        // Correct Soft Logic: The sum of the cards (with Aces=11) is greater than the final sum (with reduced Aces),
        // AND the final sum is 21 or less. This means at least one Ace is counting as 11.
        const isSoft = sum <= 21 && (initialSum > sum); // Use initialSum variable

        return { cards, total: sum, isSoft, isPair };
    }

    // Throttled wrapper for updateHelper to prevent freezing on DOM changes
    function throttleUpdateHelper() {
        if (helperUpdateTimeout) {
            clearTimeout(helperUpdateTimeout);
        }
        helperUpdateTimeout = setTimeout(() => {
            helperUpdateTimeout = null;
            updateHelper();
        }, 50); // 50ms delay is usually sufficient
    }

    function updateHelper() {
        const dealerEl = document.querySelector('.dealer-cards div[class*="card-"]:not(.card-back)');
        const pHand = getHandInfo('.player-cards');
        const dHand = getHandInfo('.dealer-cards');
        const dynamicContent = document.getElementById('bj-dynamic-content');
        const panel = document.getElementById(PANEL_ID);
        if(!dynamicContent || showSettings || showStatsPanel) return;

        isHandActive = pHand.cards.length >= 2 && pHand.total <= 21 && dHand.cards.length > 0;
        let advice = '---';
        let handInfo = 'Waiting for hand';
        let actionClass = 'bj-idle';

        if (isHandActive) {
            const dVal = dealerEl ? getCardValue(dealerEl) : 0;
            const dIdx = dVal===11 ? 1 : dVal;

            if (pHand.total > 21) {
                advice = 'Bust (Lose)';
            } else {
                if(pHand.isPair) {
                    const pVal = pHand.cards[0]===11?11:pHand.cards[0];
                    advice = strategy.pair[pVal]?.[dIdx] || AC.H;
                } else if(pHand.isSoft) {
                    advice = strategy.soft[Math.max(13, pHand.total)]?.[dIdx] || AC.S;
                } else {
                    advice = strategy.hard[Math.max(4, pHand.total)]?.[dIdx] || AC.S;
                }
            }

            let handType = '';
            if (pHand.isPair) handType = ' (Pair)';
            else if (pHand.isSoft) handType = ' (Soft)';

            const dCardList = dHand.cards.length >= 2 ? dHand.cards.join(', ') : (dealerEl ? dVal : '?');
            const dInfo = dHand.cards.length >= 2 ? `D: ${dHand.total} (${dCardList})` : `D Up: ${dCardList}`;
            handInfo = `P: ${pHand.total}${handType} (${pHand.cards.join(', ')}) | ${dInfo}`;
            actionClass = `bj-${advice.toLowerCase().split(' ')[0]}`;

            // Render Helper UI (Advice and Game Actions)
            dynamicContent.innerHTML = '';
            dynamicContent.appendChild(renderHelperContent(advice, handInfo, actionClass));

            // Set panel border style based on advice
            const colorMap = { 'bj-hit': '#FF5722', 'bj-stand': '#4CAF50', 'bj-double': '#4CAF50', 'bj-split': '#2196F3', 'bj-idle': 'transparent' };
            panel.style.border = `2px solid ${colorMap[actionClass] || '#FFD700'}`;

        } else {
            renderRecentGamesList();
            panel.style.border = '2px solid transparent';
        }
    }


    // --- Main Execution ---
    function initialize() {
        createTrackerPanel();
        refreshTrackerUI();
        // START NEW COUNTDOWN TIMER
        updateSyncCountdown();
        if (syncCountdownInterval === null) {
            syncCountdownInterval = setInterval(updateSyncCountdown, 1000);
        }
        // END NEW COUNTDOWN TIMER
        // Auto-Sync Interval
        setInterval(() => { if(apiKey) importApiData(true); }, API_SYNC_INTERVAL_MS);

        // Observer for Game State
        const observer = new MutationObserver(throttleUpdateHelper);
        const gameWrap = document.querySelector('.blackjack-wrap') || document.body;
        observer.observe(gameWrap, { childList: true, subtree: true, attributes: true });
        // --- NEW: Handle Shift Key State ---
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Shift') shiftKeyHeld = true;
        });
        document.addEventListener('keyup', (e) => {
            if (e.key === 'Shift') shiftKeyHeld = false;
            if (isDragging) stopDragging(); // Stop dragging immediately if Shift is released
        });
        // --- END Shift Key State ---


        const panel = document.getElementById(PANEL_ID);
        if (panel) {
            // Start dragging event listener (on panel content, not just the header)
            panel.addEventListener('mousedown', startDragging);
        }

        // Global move and stop listeners
        document.addEventListener('mousemove', dragPanel);
        document.addEventListener('mouseup', stopDragging);
    }
    // Initial update and sync (silent, partial/catch-up scan)
    updateHelper();
    importApiData(true);


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