Blackjack ToolKit V3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Blackjack ToolKit V3
// @version       3.2
// @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        GM.xmlHttpRequest
// @connect      api.torn.com
// @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();
    }
})();