您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sure, here is a simple and customer-friendly description on how to use the tracker:
当前为
// ==UserScript== // @name TornPDA RR Tracker v5.0 (All Features) // @namespace https://greasyfork.org/users/1493252 // @version 5.0 // Increased version for all integrated features // @description Sure, here is a simple and customer-friendly description on how to use the tracker: //###Your Torn RR Tracker: Quick Guide //###Auto-Tracking //###Just open Russian Roulette, and your tracker starts automatically. It records every win and loss, showing your profit, win rate, and streaks. //###Panel Controls //###* Drag: Click and drag the ☰ icon to move the panel anywhere you like. //###* Collapse/Expand: Click the ► (or ▪) icon to shrink or expand the panel. //###* Hide/Show: If Auto-Hide is on in settings, the tracker hides when you leave RR and reappears when you return. //###Settings (⚙️ icon) //###* Panel Opacity: Adjust how see-through the panel is. //###* Profit/Loss Alerts: Set targets to be notified when you hit a certain profit or loss. //###* Mini-Bar Count: Choose how many recent games show in the collapsed view. //###* Reset Data: Clear all your tracked stats and profit. // @match https://www.torn.com/page.php?sid=russianRoulette* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function waitUntilReady() { const PANEL_ID = 'rr-tracker-panel'; const STORAGE = 'torn_rr_tracker_results'; const PROFIT_STORAGE = 'torn_rr_total_profit'; const POS_KEY = 'rr_panelPos'; const COLLAPSE_KEY = 'rr_panelCollapsed'; const AUTOHIDE_KEY = 'rr_autoHide'; const MAX_DISPLAY_KEY = 'rr_maxDisplayMatches'; // NEW: Opacity Storage Key const OPACITY_KEY = 'rr_panelOpacity'; // NEW: Alert Threshold Storage Keys const PROFIT_TARGET_KEY = 'rr_profitTarget'; const LOSS_LIMIT_KEY = 'rr_lossLimit'; const ALERT_SHOWN_PROFIT_KEY = 'rr_alertShownProfit'; const ALERT_SHOWN_LOSS_KEY = 'rr_alertShownLoss'; // NEW: Mini-Bar Count Storage Key const MINI_BAR_COUNT_KEY = 'rr_miniBarCount'; if (document.getElementById(PANEL_ID)) return; if (!document.body.innerText.includes('Password') && !document.body.innerText.includes('POT MONEY')) { return setTimeout(waitUntilReady, 200); } let lastPot = 0; let roundActive = true; let hasTracked = false; let results = JSON.parse(localStorage.getItem(STORAGE) || '[]'); let totalProfit = parseFloat(localStorage.getItem(PROFIT_STORAGE) || '0'); let collapsed = JSON.parse(localStorage.getItem(COLLAPSE_KEY) || 'false'); let autoHide = JSON.parse(localStorage.getItem(AUTOHIDE_KEY) || 'true'); let showSettings = false; // Initialize maxDisplayMatches from localStorage, default to 100 let maxDisplayMatches = parseInt(localStorage.getItem(MAX_DISPLAY_KEY) || '100', 10); if (isNaN(maxDisplayMatches) || maxDisplayMatches < 1) { maxDisplayMatches = 100; localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString()); } // Initialize currentOpacity from localStorage, default to 0.6 let currentOpacity = parseFloat(localStorage.getItem(OPACITY_KEY) || '0.6'); if (isNaN(currentOpacity) || currentOpacity < 0.1 || currentOpacity > 1.0) { currentOpacity = 0.6; // Sanitize if value is bad localStorage.setItem(OPACITY_KEY, currentOpacity.toString()); } // Initialize Alert Thresholds and Status let profitTarget = parseFloat(localStorage.getItem(PROFIT_TARGET_KEY) || '0'); // 0 means disabled let lossLimit = parseFloat(localStorage.getItem(LOSS_LIMIT_KEY) || '0'); // 0 means disabled let alertShownProfit = JSON.parse(localStorage.getItem(ALERT_SHOWN_PROFIT_KEY) || 'false'); let alertShownLoss = JSON.parse(localStorage.getItem(ALERT_SHOWN_LOSS_KEY) || 'false'); // NEW: Initialize miniBarCount from localStorage, default to 10 let miniBarCount = parseInt(localStorage.getItem(MINI_BAR_COUNT_KEY) || '10', 10); if (isNaN(miniBarCount) || miniBarCount < 1 || miniBarCount > 50) { // Max 50 for sanity miniBarCount = 10; localStorage.setItem(MINI_BAR_COUNT_KEY, miniBarCount.toString()); } const panel = document.createElement('div'); panel.id = PANEL_ID; Object.assign(panel.style, { position: 'fixed', top: '12px', left: '12px', background: `rgba(0,0,0,${currentOpacity})`, // Uses dynamic opacity color: '#fff', fontFamily: 'monospace', fontSize: '14px', padding: '36px 12px 12px', borderRadius: '10px', boxShadow: '0 0 12px rgba(255,0,0,0.3)', zIndex: '9999999', userSelect: 'none', display: 'flex', flexDirection: 'column', gap: '8px', minWidth: '140px' }); document.body.appendChild(panel); try { const pos = JSON.parse(localStorage.getItem(POS_KEY) || '{}'); if (pos.top && pos.left) { panel.style.top = pos.top; panel.style.left = pos.left; } } catch {} // NEW: Alert Message Div - Placed before miniBar and statsGroup const alertMessageDiv = document.createElement('div'); alertMessageDiv.id = 'rr-alert-message'; Object.assign(alertMessageDiv.style, { display: 'none', // Hidden by default padding: '8px', marginBottom: '8px', borderRadius: '6px', textAlign: 'center', fontWeight: 'bold', fontSize: '16px', color: 'white', cursor: 'pointer', // Make it clickable to dismiss border: '1px solid transparent', // Will be colored later transition: 'background-color 0.3s, border-color 0.3s' }); panel.appendChild(alertMessageDiv); const miniBar = document.createElement('div'); Object.assign(miniBar.style, { display: 'none', flexWrap: 'wrap', gap: '2px', padding: '4px 0' }); panel.appendChild(miniBar); const profitMini = document.createElement('div'); Object.assign(profitMini.style, { display: 'none', fontSize: '14px', fontFamily: 'monospace', margin: '2px 0' }); panel.appendChild(profitMini); const statusDiv = document.createElement('div'); Object.assign(statusDiv.style, { position: 'absolute', top: '8px', left: '8px', width: '20px', height: '20px', fontSize: '18px', cursor: 'pointer', color: 'rgba(255,255,255,0.7)' }); panel.appendChild(statusDiv); const dragHandle = document.createElement('div'); dragHandle.textContent = '☰'; Object.assign(dragHandle.style, { position: 'absolute', top: '8px', right: '8px', width: '20px', height: '20px', fontSize: '18px', cursor: 'move', color: 'rgba(255,255,255,0.7)', touchAction: 'none' }); panel.appendChild(dragHandle); // --- Main Stats Group --- const statsGroup = document.createElement('div'); Object.assign(statsGroup.style, { display: 'flex', flexDirection: 'column', gap: '4px' }); panel.appendChild(statsGroup); const profitDiv = document.createElement('div'); const winrateDiv = document.createElement('div'); const streakDiv = document.createElement('div'); statsGroup.append(profitDiv, winrateDiv, streakDiv); const resultsContainer = document.createElement('div'); Object.assign(resultsContainer.style, { maxHeight: '140px', overflowY: 'auto', marginTop: '4px' }); statsGroup.appendChild(resultsContainer); // --- Settings Panel --- const settingsPanel = document.createElement('div'); Object.assign(settingsPanel.style, { display: 'none', flexDirection: 'column', gap: '8px', padding: '12px 0' }); panel.appendChild(settingsPanel); const settingsTitle = document.createElement('div'); settingsTitle.textContent = 'Settings'; Object.assign(settingsTitle.style, { fontSize: '16px', fontWeight: 'bold', marginBottom: '4px' }); settingsPanel.appendChild(settingsTitle); const backButton = document.createElement('button'); backButton.textContent = '← Back'; Object.assign(backButton.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginBottom: '8px' }); backButton.onmouseenter = () => backButton.style.background = 'rgba(255,255,255,0.2)'; backButton.onmouseleave = () => backButton.style.background = 'rgba(255,255,255,0.1)'; backButton.onclick = () => { showSettings = false; refreshAll(); }; settingsPanel.appendChild(backButton); // Max Matches Displayed Setting const maxMatchesSettingDiv = document.createElement('div'); Object.assign(maxMatchesSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const maxMatchesLabel = document.createElement('label'); maxMatchesLabel.textContent = 'Max Matches Displayed:'; maxMatchesLabel.htmlFor = 'max-matches-input'; Object.assign(maxMatchesLabel.style, { flexShrink: '0' }); const maxMatchesInput = document.createElement('input'); maxMatchesInput.type = 'number'; maxMatchesInput.id = 'max-matches-input'; maxMatchesInput.min = '1'; // Minimum 1 match maxMatchesInput.max = '500'; // Reasonable upper limit maxMatchesInput.value = maxDisplayMatches; Object.assign(maxMatchesInput.style, { width: '60px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); maxMatchesInput.onchange = () => { let newValue = parseInt(maxMatchesInput.value, 10); if (isNaN(newValue) || newValue < 1) { newValue = 1; // Default to 1 if invalid } if (newValue > 500) newValue = 500; // Cap at 500 maxDisplayMatches = newValue; maxMatchesInput.value = maxDisplayMatches; // Update input field to sanitized value localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString()); // If we reduce the max matches, also trim the results array immediately while (results.length > maxDisplayMatches) { results.pop(); } saveResults(); refreshAll(); }; maxMatchesSettingDiv.append(maxMatchesLabel, maxMatchesInput); settingsPanel.appendChild(maxMatchesSettingDiv); // FEATURE: Panel Opacity Slider Setting const transparencySettingDiv = document.createElement('div'); Object.assign(transparencySettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const transparencyLabel = document.createElement('label'); transparencyLabel.textContent = 'Panel Opacity:'; transparencyLabel.htmlFor = 'transparency-slider'; Object.assign(transparencyLabel.style, { flexShrink: '0' }); const transparencySlider = document.createElement('input'); transparencySlider.type = 'range'; transparencySlider.id = 'transparency-slider'; transparencySlider.min = '0.1'; // Minimum opacity (10%) transparencySlider.max = '1.0'; // Maximum opacity (100%) transparencySlider.step = '0.05'; // Increment/decrement by 5% transparencySlider.value = currentOpacity; // Set initial value from the loaded variable Object.assign(transparencySlider.style, { width: '100px', // Adjust width as desired padding: '4px', border: 'none', background: 'transparent', cursor: 'pointer' }); transparencySlider.oninput = () => { currentOpacity = parseFloat(transparencySlider.value); panel.style.background = `rgba(0,0,0,${currentOpacity})`; // Apply new opacity to panel localStorage.setItem(OPACITY_KEY, currentOpacity.toString()); // Save the new opacity }; transparencySettingDiv.append(transparencyLabel, transparencySlider); settingsPanel.appendChild(transparencySettingDiv); // END OPACITY SLIDER FEATURE // FEATURE: Profit/Loss Threshold Settings const profitTargetSettingDiv = document.createElement('div'); Object.assign(profitTargetSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const profitTargetLabel = document.createElement('label'); profitTargetLabel.textContent = 'Profit Target ($):'; profitTargetLabel.htmlFor = 'profit-target-input'; Object.assign(profitTargetLabel.style, { flexShrink: '0' }); const profitTargetInput = document.createElement('input'); profitTargetInput.type = 'number'; profitTargetInput.id = 'profit-target-input'; profitTargetInput.min = '0'; // Can't have negative target profitTargetInput.value = profitTarget; // Set initial value Object.assign(profitTargetInput.style, { width: '80px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); profitTargetInput.onchange = () => { let newValue = parseInt(profitTargetInput.value, 10); if (isNaN(newValue) || newValue < 0) newValue = 0; profitTarget = newValue; profitTargetInput.value = profitTarget; localStorage.setItem(PROFIT_TARGET_KEY, profitTarget.toString()); alertShownProfit = false; // Reset alert shown flag to allow new alert localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); refreshAll(); }; profitTargetSettingDiv.append(profitTargetLabel, profitTargetInput); settingsPanel.appendChild(profitTargetSettingDiv); const lossLimitSettingDiv = document.createElement('div'); Object.assign(lossLimitSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const lossLimitLabel = document.createElement('label'); lossLimitLabel.textContent = 'Loss Limit ($):'; lossLimitLabel.htmlFor = 'loss-limit-input'; Object.assign(lossLimitLabel.style, { flexShrink: '0' }); const lossLimitInput = document.createElement('input'); lossLimitInput.type = 'number'; lossLimitInput.id = 'loss-limit-input'; lossLimitInput.min = '0'; // Can't have negative limit (it's absolute) lossLimitInput.value = lossLimit; // Set initial value Object.assign(lossLimitInput.style, { width: '80px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); lossLimitInput.onchange = () => { let newValue = parseInt(lossLimitInput.value, 10); if (isNaN(newValue) || newValue < 0) newValue = 0; lossLimit = newValue; lossLimitInput.value = lossLimit; localStorage.setItem(LOSS_LIMIT_KEY, lossLimit.toString()); alertShownLoss = false; // Reset alert shown flag to allow new alert localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); refreshAll(); }; lossLimitSettingDiv.append(lossLimitLabel, lossLimitInput); settingsPanel.appendChild(lossLimitSettingDiv); const clearAlertsBtn = document.createElement('button'); clearAlertsBtn.textContent = '✔️ Clear Alerts'; Object.assign(clearAlertsBtn.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); clearAlertsBtn.onmouseenter = () => clearAlertsBtn.style.background = 'rgba(255,255,255,0.2)'; clearAlertsBtn.onmouseleave = () => clearAlertsBtn.style.background = 'rgba(255,255,255,0.1)'; clearAlertsBtn.onclick = () => { alertShownProfit = false; alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); alertMessageDiv.style.display = 'none'; // Hide current alert immediately refreshAll(); // Re-evaluate if any *other* alert should be shown (e.g. if conditions are still met) }; settingsPanel.appendChild(clearAlertsBtn); // END THRESHOLD ALERTS FEATURE // NEW FEATURE: Mini-Bar Display Count Setting const miniBarCountSettingDiv = document.createElement('div'); Object.assign(miniBarCountSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const miniBarCountLabel = document.createElement('label'); miniBarCountLabel.textContent = 'Mini-Bar Count:'; miniBarCountLabel.htmlFor = 'mini-bar-count-input'; Object.assign(miniBarCountLabel.style, { flexShrink: '0' }); const miniBarCountInput = document.createElement('input'); miniBarCountInput.type = 'number'; miniBarCountInput.id = 'mini-bar-count-input'; miniBarCountInput.min = '1'; miniBarCountInput.max = '50'; // Let's cap it at 50 for reasonable display miniBarCountInput.value = miniBarCount; Object.assign(miniBarCountInput.style, { width: '60px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); miniBarCountInput.onchange = () => { let newValue = parseInt(miniBarCountInput.value, 10); if (isNaN(newValue) || newValue < 1) newValue = 1; if (newValue > 50) newValue = 50; // Cap at 50 miniBarCount = newValue; miniBarCountInput.value = miniBarCount; localStorage.setItem(MINI_BAR_COUNT_KEY, miniBarCount.toString()); refreshAll(); // Refresh to update the mini-bar display }; miniBarCountSettingDiv.append(miniBarCountLabel, miniBarCountInput); settingsPanel.appendChild(miniBarCountSettingDiv); // END MINI-BAR COUNT FEATURE const resetBtn = document.createElement('button'); resetBtn.textContent = '🔄 Reset Data'; Object.assign(resetBtn.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer' }); resetBtn.onmouseenter = () => resetBtn.style.background = 'rgba(255,255,255,0.2)'; resetBtn.onmouseleave = () => resetBtn.style.background = 'rgba(255,255,255,0.1)'; resetBtn.onclick = () => { if (confirm('Clear all results and reset profit?')) { results = []; totalProfit = 0; saveResults(); saveTotalProfit(); lastPot = 0; roundActive = true; hasTracked = false; // Also clear alerts on full reset alertShownProfit = false; alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); refreshAll(); } }; settingsPanel.appendChild(resetBtn); const autoHideBtn = document.createElement('button'); autoHideBtn.textContent = autoHide ? 'Auto-Hide: On' : 'Auto-Hide: Off'; Object.assign(autoHideBtn.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); autoHideBtn.onmouseenter = () => autoHideBtn.style.background = 'rgba(255,255,255,0.2)'; autoHideBtn.onmouseleave = () => autoHideBtn.style.background = 'rgba(255,255,255,0.1)'; autoHideBtn.onclick = () => { autoHide = !autoHide; localStorage.setItem(AUTOHIDE_KEY, JSON.stringify(autoHide)); autoHideBtn.textContent = autoHide ? 'Auto-Hide: On' : 'Auto-Hide: Off'; refreshAll(); }; settingsPanel.appendChild(autoHideBtn); // --- Settings Button for Main Panel --- const settingsButton = document.createElement('button'); settingsButton.textContent = '⚙️ Settings'; Object.assign(settingsButton.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); settingsButton.onmouseenter = () => settingsButton.style.background = 'rgba(255,255,255,0.2)'; settingsButton.onmouseleave = () => settingsButton.style.background = 'rgba(255,255,255,0.1)'; settingsButton.onclick = () => { showSettings = true; refreshAll(); }; panel.appendChild(settingsButton); const saveResults = () => localStorage.setItem(STORAGE, JSON.stringify(results)); const saveTotalProfit = () => localStorage.setItem(PROFIT_STORAGE, totalProfit.toString()); const saveCollapsed = () => localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed)); // MODIFIED: makeCircle now accepts result, bet, and index for clickable pop-up const makeCircle = (result, bet, index) => { const container = document.createElement('span'); Object.assign(container.style, { display: 'inline-block', width: '14px', // Slightly larger for better click target/tooltip height: '14px', borderRadius: '50%', backgroundColor: result === 'win' ? '#4CAF50' : '#E53935', marginRight: '2px', cursor: 'pointer', // Indicate it's clickable position: 'relative', // For tooltip positioning }); // Simple tooltip for details on hover container.title = `${result.toUpperCase()}: $${bet.toLocaleString()}`; // Add click listener for temporary pop-up container.addEventListener('click', (e) => { e.stopPropagation(); // Prevent potential parent clicks // Remove any existing temporary popups from this container Array.from(container.children).forEach(child => { if (child.classList.contains('rr-temp-popup')) { container.removeChild(child); } }); const tempPopup = document.createElement('div'); tempPopup.classList.add('rr-temp-popup'); // Add a class for easier identification tempPopup.textContent = `${result.toUpperCase()}: $${bet.toLocaleString()}`; Object.assign(tempPopup.style, { position: 'absolute', background: 'rgba(0,0,0,0.9)', // Slightly darker for better contrast border: '1px solid #555', color: 'white', padding: '4px 8px', borderRadius: '4px', fontSize: '12px', whiteSpace: 'nowrap', zIndex: '10000000', top: '-28px', // Position above the circle left: '50%', transform: 'translateX(-50%)', pointerEvents: 'none', // Prevent pop-up itself from blocking clicks opacity: '0', // Start invisible transition: 'opacity 0.2s ease-in-out', // Smooth fade in/out }); container.appendChild(tempPopup); // Fade in setTimeout(() => tempPopup.style.opacity = '1', 10); // Small delay to allow render // Fade out and remove setTimeout(() => { tempPopup.style.opacity = '0'; setTimeout(() => { if (container.contains(tempPopup)) { container.removeChild(tempPopup); } }, 200); // Allow transition to finish before removing }, 1500); // Display for 1.5 seconds }); return container; }; function updateStatus() { statusDiv.textContent = collapsed ? '▪' : (roundActive ? '►' : '▸'); } function updatePanelVisibility() { if (!autoHide) { panel.style.display = 'flex'; return; } const onMenu = document.body.innerText.includes('Password'); panel.style.display = onMenu ? 'flex' : 'none'; } function refreshAll() { // NEW: Alert Logic - Hide by default before checking alertMessageDiv.style.display = 'none'; // Check for Profit Target if (profitTarget > 0 && totalProfit >= profitTarget && !alertShownProfit) { alertMessageDiv.textContent = `🎯 PROFIT TARGET REACHED! +$${profitTarget.toLocaleString()}`; Object.assign(alertMessageDiv.style, { background: 'rgba(76, 175, 80, 0.8)', // Green borderColor: '#4CAF50', display: 'block' }); alertShownProfit = true; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'true'); } // Check for Loss Limit else if (lossLimit > 0 && totalProfit <= -lossLimit && !alertShownLoss) { alertMessageDiv.textContent = `🚨 LOSS LIMIT REACHED! -$${lossLimit.toLocaleString()}`; Object.assign(alertMessageDiv.style, { background: 'rgba(229, 57, 53, 0.8)', // Red borderColor: '#E53935', display: 'block' }); alertShownLoss = true; localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'true'); } const sign = totalProfit >= 0 ? '+' : '–'; profitMini.textContent = `${sign}${Math.abs(totalProfit).toLocaleString()}`; profitMini.style.color = totalProfit >= 0 ? '#4CAF50' : '#E53935'; profitDiv.textContent = `💰 Profit: ${sign}$${Math.abs(totalProfit).toLocaleString()}`; profitDiv.style.color = totalProfit >= 0 ? '#4CAF50' : '#E53935'; const wins = results.filter(r => r.result==='win').length; const tot = results.length; winrateDiv.textContent = `🎯 Win Rate: ${tot?((wins/tot)*100).toFixed(1):'0.0'}% (${wins}/${tot})`; let w=0,l=0; for (const r of results) { if (r.result==='win') { if(l) break; w++; } else { if(w) break; l++; } } streakDiv.textContent = w?`🔥 Streak: ${w}`: l?`💀 Streak: ${l}`:'⏸️ No streak'; resultsContainer.innerHTML = ''; // Display only up to maxDisplayMatches in expanded view results.slice(0, maxDisplayMatches).forEach((r,i) => { const row = document.createElement('div'); row.append( makeCircle(r.result, r.bet, i), // Pass data for clickable circle document.createTextNode(`${i+1}. ${r.result.toUpperCase()} — $${r.bet.toLocaleString()}`) ); resultsContainer.appendChild(row); }); miniBar.innerHTML = ''; // MODIFIED: Mini-bar now uses miniBarCount for compact view results.slice(0, miniBarCount).forEach((r, i) => miniBar.append(makeCircle(r.result, r.bet, i))); // --- Visibility Logic for Main Panel vs. Settings Panel --- if (showSettings) { statsGroup.style.display = 'none'; settingsButton.style.display = 'none'; settingsPanel.style.display = 'flex'; miniBar.style.display = 'none'; profitMini.style.display = 'none'; alertMessageDiv.style.display = 'none'; // Hide alert when in settings } else if (collapsed) { miniBar.style.display = 'flex'; profitMini.style.display = 'block'; statsGroup.style.display = 'none'; settingsButton.style.display = 'none'; settingsPanel.style.display = 'none'; // Alert can remain visible in collapsed state if active } else { miniBar.style.display = 'none'; profitMini.style.display = 'none'; statsGroup.style.display = 'flex'; settingsButton.style.display = 'inline-block'; settingsPanel.style.display = 'none'; // Alert can remain visible in expanded state if active } updateStatus(); updatePanelVisibility(); } function addResult(type) { if (!roundActive) return; if (type === 'win') { totalProfit += lastPot; } else { totalProfit -= lastPot; } saveTotalProfit(); results.unshift({ result: type, bet: lastPot }); // Now truncate based on maxDisplayMatches if (results.length > maxDisplayMatches) { results.pop(); } saveResults(); roundActive = false; hasTracked = true; // NEW: Reset alert shown flags if profit moves away from threshold if (profitTarget > 0 && alertShownProfit && totalProfit < profitTarget) { alertShownProfit = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); } if (lossLimit > 0 && alertShownLoss && totalProfit > -lossLimit) { alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); } refreshAll(); } function scanPot() { document.querySelectorAll('body *').forEach(el => { const txt = el.innerText?.trim(); if (txt?.includes('POT MONEY:$')) { const m = txt.match(/POT MONEY:\$\s*([\d,]+)/); if (m) lastPot = Math.floor(parseInt(m[1].replace(/,/g,''),10)/2); } }); } function scanResult() { if (!roundActive) return; document.querySelectorAll('body *').forEach(el => { const txt = el.innerText?.trim(); if (txt?.includes('You take your winnings')) addResult('win'); if (txt?.includes('BANG! You fall down')) addResult('lose'); }); } function scanStart() { if (hasTracked && document.body.innerText.includes('Waiting:')) { roundActive = true; hasTracked = false; updateStatus(); } } (function() { let mx, my; const savePos = () => localStorage.setItem(POS_KEY, JSON.stringify({ top: panel.style.top, left: panel.style.left })); dragHandle.addEventListener('mousedown', e => { e.preventDefault(); mx = e.clientX; my = e.clientY; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); function onMove(ev) { const dx = ev.clientX - mx, dy = ev.clientY - my; mx = ev.clientX; my = ev.clientY; panel.style.top = panel.offsetTop + dy + 'px'; panel.style.left = panel.offsetLeft + dx + 'px'; } function onUp() { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); savePos(); } }); dragHandle.addEventListener('touchstart', e => { mx = e.touches[0].clientX; my = e.touches[0].clientY; document.addEventListener('touchmove', onTmove); document.addEventListener('touchend', onTend); function onTmove(ev) { const dx = ev.touches[0].clientX - mx, dy = ev.touches[0].clientY - my; mx = ev.touches[0].clientX; my = ev.touches[0].clientY; panel.style.top = panel.offsetTop + dy + 'px'; panel.style.left = panel.offsetLeft + dx + 'px'; } function onTend() { document.removeEventListener('touchmove', onTmove); document.removeEventListener('touchend', onTend); savePos(); } }); })(); statusDiv.addEventListener('click', () => { collapsed = !collapsed; if (showSettings && collapsed) { // If settings are open and collapsing, close settings showSettings = false; } localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed)); refreshAll(); }); // Dismiss alert by clicking on the alert message itself alertMessageDiv.addEventListener('click', () => { alertMessageDiv.style.display = 'none'; }); refreshAll(); setInterval(() => { updatePanelVisibility(); scanStart(); scanPot(); scanResult(); }, 500); })();