// ==UserScript==
// @name RR Tracker v8.0 (smart bets introduced)
// @namespace https://greasyfork.org/users/1493252
// @version 8.0 // Enhanced Martingale UI, Dedicated Edit Panel, Cash Pull Fixed, 11th Bet Added, Persistent Bets Fixed
// @description RR tracker with cool features, now with integrated Martingale bet auto-start!
//###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.
//###* Shift + Drag: Hold Shift and drag with your mouse from anywhere on the panel to move it.
//###* 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.
//###* Edit Bets: Customize your Martingale bet amounts in a dedicated panel.
//###Bets (💰 icon) - NEW!
//###* Martingale Bet Auto-Start: Click a button to automatically set the bet and start a match.
// @match https://www.torn.com/page.php?sid=russianRoulette*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function waitUntilReady() {
'use strict';
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';
const OPACITY_KEY = 'rr_panelOpacity';
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'; // <-- FIXED TYPO HERE
const MINI_BAR_COUNT_KEY = 'rr_miniBarCount';
// NEW: Martingale Bet Constants & Variables
const MARTINGALE_BETS_STORAGE_KEY = 'RRBets';
// Default Martingale sequence for 11 bets (approximately 2x multiplier)
// CORRECTED 5th bet (index 4) from 16000 to 160000 for proper x2 progression
const DEFAULT_MARTINGALE_BETS = [10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000, 10240000];
const EXPECTED_BET_COUNT = DEFAULT_MARTINGALE_BETS.length; // Now dynamically set to 11
let currentMartingaleBets = []; // This will hold the bet sequence
let showBetsPanel = false; // New state to control Bets panel visibility
let showEditBetsPanel = false; // New state for dedicated Edit Bets panel
// Dragging Variables
let isDragging = false;
let dragMouseX = 0;
let dragMouseY = 0;
// Two-Finger Dragging Variables
let isTwoFingerDragging = false;
let initialTouchMidX = 0;
let initialTouchMidY = 0;
let initialPanelX = 0;
let initialPanelY = 0;
// Universal way to set input value and trigger events
const nativeSet = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;
if (document.getElementById(PANEL_ID)) return;
// Check for specific text that indicates we are on the RR page
// Added more robust checks for common elements on the RR page
if (!document.body.innerText.includes('Password') &&
!document.body.innerText.includes('POT MONEY') &&
!document.body.innerText.includes('Shoot') &&
!document.body.innerText.includes('Players') &&
!document.querySelector('.create-game-section') && // Check for "Create Game" container
!document.querySelector('.russian-roulette-container')) { // General RR container
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) || 'false');
let showSettings = false;
let maxDisplayMatches = parseInt(localStorage.getItem(MAX_DISPLAY_KEY) || '100', 10);
if (isNaN(maxDisplayMatches) || maxDisplayMatches < 1) {
maxDisplayMatches = 100;
localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString());
}
let currentOpacity = parseFloat(localStorage.getItem(OPACITY_KEY) || '0.6');
if (isNaN(currentOpacity) || currentOpacity < 0.1 || currentOpacity > 1.0) {
currentOpacity = 0.6;
localStorage.setItem(OPACITY_KEY, currentOpacity.toString());
}
let profitTarget = parseFloat(localStorage.getItem(PROFIT_TARGET_KEY) || '0');
let lossLimit = parseFloat(localStorage.getItem(LOSS_LIMIT_KEY) || '0');
let alertShownProfit = JSON.parse(localStorage.getItem(ALERT_SHOWN_PROFIT_KEY) || 'false');
let alertShownLoss = JSON.parse(localStorage.getItem(ALERT_SHOWN_LOSS_KEY) || 'false'); // <-- FIXED TYPO HERE
let miniBarCount = parseInt(localStorage.getItem(MINI_BAR_COUNT_KEY) || '10', 10);
if (isNaN(miniBarCount) || miniBarCount < 1 || miniBarCount > 50) {
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})`,
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 {}
const alertMessageDiv = document.createElement('div');
alertMessageDiv.id = 'rr-alert-message';
Object.assign(alertMessageDiv.style, {
display: 'none',
padding: '8px',
marginBottom: '8px',
borderRadius: '6px',
textAlign: 'center',
fontWeight: 'bold',
fontSize: '16px',
color: 'white',
cursor: 'pointer',
border: '1px solid transparent',
transition: 'background-color 0.3s, border-color 0.3s'
});
panel.appendChild(alertMessageDiv);
const miniBar = document.createElement('div');
Object.assign(miniBar.style, {
display: 'none', // Managed by refreshAll()
flexDirection: 'column', // Now a column to stack circles then bets
gap: '2px',
padding: '4px 0'
});
panel.appendChild(miniBar);
const profitMini = document.createElement('div');
Object.assign(profitMini.style, {
display: 'none', // Managed by refreshAll()
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' // Prevent default touch actions on this handle
});
panel.appendChild(dragHandle);
const statsGroup = document.createElement('div');
Object.assign(statsGroup.style, {
display: 'flex', // Managed by refreshAll()
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);
const settingsPanel = document.createElement('div');
Object.assign(settingsPanel.style, {
display: 'none', // Managed by refreshAll()
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 backButtonSettings = document.createElement('button');
backButtonSettings.textContent = '← Back';
Object.assign(backButtonSettings.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginBottom: '8px'
});
backButtonSettings.onmouseenter = () => backButtonSettings.style.background = 'rgba(255,255,255,0.2)';
backButtonSettings.onmouseleave = () => backButtonSettings.style.background = 'rgba(255,255,255,0.1)';
backButtonSettings.onclick = () => {
showSettings = false;
showBetsPanel = false;
showEditBetsPanel = false; // Ensure other panels are closed
refreshAll();
};
settingsPanel.appendChild(backButtonSettings);
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';
maxMatchesInput.max = '500';
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;
}
if (newValue > 500) newValue = 500;
maxDisplayMatches = newValue;
maxMatchesInput.value = maxDisplayMatches;
localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString());
while (results.length > maxDisplayMatches) {
results.pop();
}
saveResults();
refreshAll();
};
maxMatchesSettingDiv.append(maxMatchesLabel, maxMatchesInput);
settingsPanel.appendChild(maxMatchesSettingDiv);
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';
transparencySlider.max = '1.0';
transparencySlider.step = '0.05';
transparencySlider.value = currentOpacity;
Object.assign(transparencySlider.style, {
width: '100px',
padding: '4px',
border: 'none',
background: 'transparent',
cursor: 'pointer'
});
transparencySlider.oninput = () => {
currentOpacity = parseFloat(transparencySlider.value);
panel.style.background = `rgba(0,0,0,${currentOpacity})`;
localStorage.setItem(OPACITY_KEY, currentOpacity.toString());
};
transparencySettingDiv.append(transparencyLabel, transparencySlider);
settingsPanel.appendChild(transparencySettingDiv);
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';
profitTargetInput.value = profitTarget;
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;
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';
lossLimitInput.value = lossLimit;
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;
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';
refreshAll();
};
settingsPanel.appendChild(clearAlertsBtn);
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';
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;
miniBarCount = newValue;
miniBarCountInput.value = miniBarCount;
localStorage.setItem(MINI_BAR_COUNT_KEY, miniBarCount.toString());
refreshAll();
};
miniBarCountSettingDiv.append(miniBarCountLabel, miniBarCountInput);
settingsPanel.appendChild(miniBarCountSettingDiv);
// NEW: Button to open Edit Bets Panel
const editBetsButton = document.createElement('button');
editBetsButton.textContent = '✏️ Edit Bets';
Object.assign(editBetsButton.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginTop: '4px'
});
editBetsButton.onmouseenter = () => editBetsButton.style.background = 'rgba(255,255,255,0.2)';
editBetsButton.onmouseleave = () => editBetsButton.style.background = 'rgba(255,255,255,0.1)';
editBetsButton.onclick = () => {
showSettings = false; // Close settings panel
showBetsPanel = false; // Close bets panel
showEditBetsPanel = true; // Open edit bets panel
refreshAll();
};
settingsPanel.appendChild(editBetsButton);
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',
marginTop: '4px'
});
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;
alertShownProfit = false;
alertShownLoss = false;
localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false');
localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false');
// Also reset Martingale bets to default when resetting all data
currentMartingaleBets = [...DEFAULT_MARTINGALE_BETS];
saveMartingaleBets(currentMartingaleBets);
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 (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 = !showSettings; // Toggle settings visibility
showBetsPanel = false; // Close bets panel if settings is opened
showEditBetsPanel = false; // Close edit bets panel
refreshAll();
};
panel.appendChild(settingsButton);
// NEW: Bets Panel elements
const betsPanel = document.createElement('div');
Object.assign(betsPanel.style, {
display: 'none', // Managed by refreshAll()
flexDirection: 'column',
gap: '8px',
padding: '12px 0'
});
panel.appendChild(betsPanel);
const betsTitle = document.createElement('div');
betsTitle.textContent = 'Martingale Auto-Start';
Object.assign(betsTitle.style, {
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '4px'
});
betsPanel.appendChild(betsTitle);
const backButtonBets = document.createElement('button');
backButtonBets.textContent = '← Back';
Object.assign(backButtonBets.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginBottom: '8px'
});
backButtonBets.onmouseenter = () => backButtonBets.style.background = 'rgba(255,255,255,0.2)';
backButtonBets.onmouseleave = () => backButtonBets.style.background = 'rgba(255,255,255,0.1)';
backButtonBets.onclick = () => {
showBetsPanel = false;
showSettings = false; // Ensure settings panel is also closed
showEditBetsPanel = false; // Ensure edit bets panel is also closed
refreshAll();
};
betsPanel.appendChild(backButtonBets);
const betButtonsContainer = document.createElement('div');
Object.assign(betButtonsContainer.style, {
display: 'flex',
flexDirection: 'column', // Make rows stack vertically
gap: '6px',
marginTop: '6px'
});
betsPanel.appendChild(betButtonsContainer);
// NEW: Edit Bets Panel elements
const editBetsPanel = document.createElement('div');
Object.assign(editBetsPanel.style, {
display: 'none', // Managed by refreshAll()
flexDirection: 'column',
gap: '8px',
padding: '12px 0'
});
panel.appendChild(editBetsPanel);
const editBetsTitle = document.createElement('div');
editBetsTitle.textContent = 'Edit Martingale Bets';
Object.assign(editBetsTitle.style, {
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '4px'
});
editBetsPanel.appendChild(editBetsTitle);
const backButtonEditBets = document.createElement('button');
backButtonEditBets.textContent = '← Back to Settings';
Object.assign(backButtonEditBets.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginBottom: '8px'
});
backButtonEditBets.onmouseenter = () => backButtonEditBets.style.background = 'rgba(255,255,255,0.2)';
backButtonEditBets.onmouseleave = () => backButtonEditBets.style.background = 'rgba(255,255,255,0.1)';
backButtonEditBets.onclick = () => {
showEditBetsPanel = false;
showSettings = true; // Go back to settings panel
refreshAll();
};
editBetsPanel.appendChild(backButtonEditBets);
const martingaleEditGrid = document.createElement('div');
Object.assign(martingaleEditGrid.style, {
display: 'grid',
gridTemplateColumns: 'auto 1fr', // Label then input
gap: '6px 10px'
});
editBetsPanel.appendChild(martingaleEditGrid);
// Dynamically create 11 input fields for editing Martingale bets
let martingaleEditInputs = [];
for (let i = 0; i < EXPECTED_BET_COUNT; i++) {
const label = document.createElement('label');
label.textContent = `Bet ${i + 1}:`;
Object.assign(label.style, {
fontSize: '13px',
paddingTop: '4px'
});
martingaleEditGrid.appendChild(label);
const input = document.createElement('input');
input.type = 'text'; // Use text to allow K/M/B suffixes
input.placeholder = `e.g., ${i === 0 ? '10k' : (i === 1 ? '30k' : '')}`; // Adjusted for 10k base
Object.assign(input.style, {
width: '80px',
padding: '4px',
border: '1px solid #555',
borderRadius: '4px',
background: 'rgba(255,255,255,0.1)',
color: '#fff'
});
input.onchange = (e) => {
const parsed = parseBetInput(e.target.value);
if (parsed !== null) {
currentMartingaleBets[i] = parsed;
saveMartingaleBets(currentMartingaleBets);
e.target.value = formatNumberToKMB(parsed); // Format back to KMB for display
refreshAll(); // Update buttons in Bets panel
} else if (e.target.value !== '') { // If not empty but invalid
alert('Invalid input. Please enter a positive number or use K/M/B suffixes (e.g., 25k, 2.5m).');
e.target.value = formatNumberToKMB(currentMartingaleBets[i]); // Revert to old value
}
};
martingaleEditGrid.appendChild(input);
martingaleEditInputs.push(input);
}
// NEW: Bets Button (main panel)
const betsButton = document.createElement('button');
betsButton.textContent = '💰 Bets';
Object.assign(betsButton.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginTop: '4px'
});
betsButton.onmouseenter = () => betsButton.style.background = 'rgba(255,255,255,0.2)';
betsButton.onmouseleave = () => betsButton.style.background = 'rgba(255,255,255,0.1)';
betsButton.onclick = () => {
showBetsPanel = !showBetsPanel; // Toggle bets panel visibility
showSettings = false; // Close settings panel if bets is opened
showEditBetsPanel = false; // Close edit bets panel
refreshAll();
};
panel.appendChild(betsButton);
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));
// --- NEW: Martingale Bet Helper Functions ---
function parseBetInput(str) {
str = str.toLowerCase().replace(/,/g,'').trim();
let m = 1;
if (str.endsWith('k')) { m=1e3; str=str.slice(0,-1); }
else if (str.endsWith('m')) { m=1e6; str=str.slice(0,-1); }
else if (str.endsWith('b')) { m=1e9; str=str.slice(0,-1); }
let v = parseFloat(str);
return (isNaN(v)||v<=0) ? null : Math.floor(v*m);
}
function formatNumberToKMB(num) {
if (num === null || isNaN(num)) return ''; // Handle cases where num might be invalid
if (num >= 1e9) {
return (num / 1e9).toFixed(2).replace(/\.00$/, '') + 'b';
}
if (num >= 1e6) {
return (num / 1e6).toFixed(2).replace(/\.00$/, '') + 'm';
}
if (num >= 1e3) {
return (num / 1e3).toFixed(2).replace(/\.00$/, '') + 'k';
}
return num.toLocaleString(); // Fallback for numbers less than 1000
}
function loadMartingaleBets() {
let storedBetsArray = [];
try {
const storedJson = localStorage.getItem(MARTINGALE_BETS_STORAGE_KEY);
if (storedJson) {
const parsed = JSON.parse(storedJson);
if (Array.isArray(parsed)) {
storedBetsArray = parsed;
}
}
} catch (e) {
console.error("Error parsing stored martingale bets:", e);
// If parsing fails, storedBetsArray remains empty, leading to default values.
}
// Start with a copy of the default values to ensure correct length and initial values
let finalBets = [...DEFAULT_MARTINGALE_BETS];
// Overlay valid stored bets onto the default array
storedBetsArray.forEach((bet, index) => {
if (index < EXPECTED_BET_COUNT) { // Only process up to the expected count
const num = parseInt(bet, 10);
// Use the stored value if it's a valid positive number
if (!isNaN(num) && num > 0) {
finalBets[index] = num;
}
// If it's not valid, the default value at that index is retained from 'finalBets' initialization
}
});
return finalBets;
}
function saveMartingaleBets(a){ localStorage.setItem(MARTINGALE_BETS_STORAGE_KEY, JSON.stringify(a)); }
// MODIFIED startMatch to correctly compare to the input box value *after* filling it.
function startMatch(originalButtonBetValue) {
const inputBox = document.querySelector('input[aria-label="Money value"]');
if (!inputBox) {
console.warn("RR Tracker: Bet input box not found. Cannot start match.");
return;
}
// First, set the input box value based on the button clicked
nativeSet.call(inputBox, originalButtonBetValue.toLocaleString('en-US'));
inputBox.dispatchEvent(new Event('input',{bubbles:true}));
inputBox.dispatchEvent(new Event('change',{bubbles:true}));
// NOW, after the input box has been filled, read its value for comparison
const currentInputBoxBet = parseBetInput(inputBox.value);
// Provide immediate feedback if input box value is manipulated by Torn itself to be less
if (currentInputBoxBet === null || currentInputBoxBet <= 0) {
alert("Invalid bet amount currently in the input box. Please ensure it's a valid number.");
return;
}
// This check might seem redundant *after* setting the value, but it's crucial if the input
// box has any external listeners or restrictions that might alter the value after `nativeSet.call`.
// Keeping it for exact replication of the second script's logic flow.
if (currentInputBoxBet < originalButtonBetValue) {
alert(`The bet amount in the box ($${currentInputBoxBet.toLocaleString()}) ` +
`is less than the button's intended value ($${originalButtonBetValue.toLocaleString()}).\n` +
`Match not started.`);
return;
}
let start = [...document.querySelectorAll('button')]
.find(b=>b.textContent.trim().toLowerCase()==='start');
if (!start) {
console.warn("RR Tracker: 'Start' button not found. Perhaps already in a match or not on create screen.");
return;
}
start.click();
//setTimeout(()=>{
//let yes = [...document.querySelectorAll('button')]
//.find(b=>b.textContent.trim().toLowerCase()==='yes');
//if (yes) yes.click();
//}, 400); // Small delay for confirmation pop-up
}
// --- New Function to Build/Rebuild Martingale Bet Buttons ---
function buildBetButtons() {
currentMartingaleBets = loadMartingaleBets(); // Load latest bets
// Populate main Bets panel
betButtonsContainer.innerHTML = ''; // Clear existing buttons
// Row for the first bet (Base Bet)
const firstBetRow = document.createElement('div');
Object.assign(firstBetRow.style, {
display: 'flex',
justifyContent: 'center', // Center the single button
width: '100%'
});
let b0 = document.createElement('button');
b0.textContent = formatNumberToKMB(currentMartingaleBets[0]);
b0.title = `$${currentMartingaleBets[0].toLocaleString()}`;
Object.assign(b0.style, {
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 10px',
cursor: 'pointer',
fontSize: '11px',
minWidth: '80px' // Ensure a consistent width
});
b0.onmouseenter = () => b0.style.background = 'rgba(255,255,255,0.2)';
b0.onmouseleave = () => b0.style.background = 'rgba(255,255,255,0.1)';
b0.onclick = () => startMatch(currentMartingaleBets[0]);
firstBetRow.appendChild(b0);
betButtonsContainer.appendChild(firstBetRow);
// Rows for the remaining 10 bets (2 buttons per row) - Changed from 9 to 10
for (let i = 1; i < EXPECTED_BET_COUNT; i += 2) {
const row = document.createElement('div');
Object.assign(row.style, {
display: 'flex',
justifyContent: 'space-around', // Space out the two buttons
gap: '6px',
width: '100%'
});
// Button 1
let b1 = document.createElement('button');
b1.textContent = formatNumberToKMB(currentMartingaleBets[i]);
b1.title = `$${currentMartingaleBets[i].toLocaleString()}`;
Object.assign(b1.style, {
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 10px',
cursor: 'pointer',
fontSize: '11px',
flex: '1', // Allow them to grow and fill space
minWidth: '70px'
});
b1.onmouseenter = () => b1.style.background = 'rgba(255,255,255,0.2)';
b1.onmouseleave = () => b1.style.background = 'rgba(255,255,255,0.1)';
b1.onclick = () => startMatch(currentMartingaleBets[i]);
row.appendChild(b1);
// Button 2 (if exists)
if (i + 1 < EXPECTED_BET_COUNT) {
let b2 = document.createElement('button');
b2.textContent = formatNumberToKMB(currentMartingaleBets[i+1]);
b2.title = `$${currentMartingaleBets[i+1].toLocaleString()}`;
Object.assign(b2.style, {
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 10px',
cursor: 'pointer',
fontSize: '11px',
flex: '1',
minWidth: '70px'
});
b2.onmouseenter = () => b2.style.background = 'rgba(255,255,255,0.2)';
b2.onmouseleave = () => b2.style.background = 'rgba(255,255,255,0.1)';
b2.onclick = () => startMatch(currentMartingaleBets[i+1]);
row.appendChild(b2);
}
betButtonsContainer.appendChild(row);
}
// Populate Martingale editing inputs in dedicated Edit Bets panel
martingaleEditInputs.forEach((input, i) => {
// Ensure that currentMartingaleBets[i] exists before formatting.
input.value = formatNumberToKMB(currentMartingaleBets[i]);
});
// NEW: Populate Mini-Bar with Win/Lose Circles and Martingale Buttons
miniBar.innerHTML = ''; // Clear existing mini-bar content
const miniCirclesContainer = document.createElement('div');
Object.assign(miniCirclesContainer.style, {
display: 'flex',
flexWrap: 'wrap',
gap: '2px',
justifyContent: 'center',
// No margin-bottom here, as it's handled by the parent miniBar gap
});
results.slice(0, miniBarCount).forEach((r, idx) => miniCirclesContainer.append(makeCircle(r.result, r.bet, idx)));
miniBar.appendChild(miniCirclesContainer);
// Add betting buttons to mini-bar only when collapsed
if (collapsed && !showBetsPanel && !showSettings && !showEditBetsPanel) {
const miniBetContainerTopRow = document.createElement('div');
Object.assign(miniBetContainerTopRow.style, {
display: 'flex',
flexWrap: 'nowrap', // Ensure they stay in one row
gap: '2px',
justifyContent: 'center',
marginBottom: '2px' // Small gap between the two rows of bets
});
const miniBetContainerBottomRow = document.createElement('div');
Object.assign(miniBetContainerBottomRow.style, {
display: 'flex',
flexWrap: 'nowrap', // Ensure they stay in one row
gap: '2px',
justifyContent: 'center'
});
// Loop through all 11 bets
currentMartingaleBets.forEach((bet, i) => {
let miniB = document.createElement('button');
miniB.textContent = formatNumberToKMB(bet);
miniB.title = `$${bet.toLocaleString()}`;
Object.assign(miniB.style, {
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '3px 6px', // Smaller padding for mini-bar
cursor: 'pointer',
fontSize: '9px', // Smaller font size for mini-bar
width: 'auto' // Allow buttons to size naturally
});
miniB.onmouseenter = () => miniB.style.background = 'rgba(255,255,255,0.2)';
miniB.onmouseleave = () => miniB.style.background = 'rgba(255,255,255,0.1)';
miniB.onclick = () => startMatch(bet);
// Distribute into two rows: first row has 6 (Base + 5), second has 5
if (i < 6) { // First 6 buttons (index 0-5) go to top row
miniBetContainerTopRow.appendChild(miniB);
} else { // Remaining 5 (index 6-10) go to bottom row
miniBetContainerBottomRow.appendChild(miniB);
}
});
miniBar.appendChild(miniBetContainerTopRow);
miniBar.appendChild(miniBetContainerBottomRow);
}
}
const makeCircle = (result, bet, index) => {
const container = document.createElement('span');
Object.assign(container.style, {
display: 'inline-block',
width: '14px',
height: '14px',
borderRadius: '50%',
backgroundColor: result === 'win' ? '#4CAF50' : '#E53935',
marginRight: '2px',
cursor: 'pointer',
position: 'relative',
});
container.title = `${result.toUpperCase()}: $${bet.toLocaleString()}`;
container.addEventListener('click', (e) => {
e.stopPropagation();
if (isDragging || isTwoFingerDragging) return;
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');
tempPopup.textContent = `${result.toUpperCase()}: $${bet.toLocaleString()}`;
Object.assign(tempPopup.style, {
position: 'absolute',
background: 'rgba(0,0,0,0.9)',
border: '1px solid #555',
color: 'white',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
whiteSpace: 'nowrap',
zIndex: '10000000',
top: '-28px',
left: '50%',
transform: 'translateX(-50%)',
pointerEvents: 'none',
opacity: '0',
transition: 'opacity 0.2s ease-in-out',
});
container.appendChild(tempPopup);
setTimeout(() => tempPopup.style.opacity = '1', 10);
setTimeout(() => {
tempPopup.style.opacity = '0';
setTimeout(() => {
if (container.contains(tempPopup)) {
container.removeChild(tempPopup);
}
}, 200);
}, 1500);
});
return container;
};
function updateStatus() {
statusDiv.textContent = collapsed ? '▪' : (roundActive ? '►' : '▸');
}
function updatePanelVisibility() {
// Determine if we're on an "RR page"
const onRRPage = document.body.innerText.includes('POT MONEY') ||
document.body.innerText.includes('Waiting:') ||
document.body.innerText.includes('You take your winnings') ||
document.body.innerText.includes('BANG! You fall down');
// Condition 1: If autoHide is explicitly false, the panel always shows.
if (!autoHide) {
panel.style.display = 'flex';
return; // Exit the function, no further checks needed.
}
// Condition 2: If autoHide is true (we only reach here if !autoHide was false),
// show the panel IF we are NOT on an RR page. Otherwise, hide it.
panel.style.display = !onRRPage ? 'flex' : 'none';
}
function refreshAll() {
alertMessageDiv.style.display = 'none';
let showAlert = false;
let alertText = '';
let alertBackgroundColor = '';
let alertBorderColor = '';
if (lossLimit > 0 && totalProfit <= -lossLimit) {
if (!alertShownLoss) {
alertShownLoss = true;
localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'true');
}
showAlert = true;
alertText = `🚨 LOSS LIMIT REACHED! -$${lossLimit.toLocaleString()}`;
alertBackgroundColor = 'rgba(229, 57, 53, 0.8)';
alertBorderColor = '#E53935';
}
else if (profitTarget > 0 && totalProfit >= profitTarget) {
if (!alertShownProfit) {
alertShownProfit = true;
localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'true');
}
showAlert = true;
alertText = `🎯 PROFIT TARGET REACHED! +$${profitTarget.toLocaleString()}`;
alertBackgroundColor = 'rgba(76, 175, 80, 0.8)';
alertBorderColor = '#4CAF50';
}
if (showAlert) {
alertMessageDiv.textContent = alertText;
Object.assign(alertMessageDiv.style, {
background: alertBackgroundColor,
borderColor: alertBorderColor,
display: 'block'
});
}
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 = '';
results.slice(0, maxDisplayMatches).forEach((r,i) => {
const row = document.createElement('div');
row.append(
makeCircle(r.result, r.bet, i),
document.createTextNode(`${i+1}. ${r.result.toUpperCase()} — $${r.bet.toLocaleString()}`)
);
resultsContainer.appendChild(row);
});
// Handle panel visibility based on current state
statsGroup.style.display = 'none';
settingsPanel.style.display = 'none';
betsPanel.style.display = 'none';
editBetsPanel.style.display = 'none';
miniBar.style.display = 'none';
profitMini.style.display = 'none';
// *** MODIFICATION START ***
// Always hide settings and bets buttons initially
settingsButton.style.display = 'none';
betsButton.style.display = 'none';
if (showSettings) {
settingsPanel.style.display = 'flex';
} else if (showBetsPanel) {
betsPanel.style.display = 'flex';
} else if (showEditBetsPanel) {
editBetsPanel.style.display = 'flex';
} else if (collapsed) {
miniBar.style.display = 'flex';
profitMini.style.display = 'block';
// When collapsed, settings/bets buttons are still hidden.
// They only appear in the expanded main view.
} else {
// This is the expanded main view
statsGroup.style.display = 'flex';
settingsButton.style.display = 'inline-block'; // Show settings button
betsButton.style.display = 'inline-block'; // Show bets button
}
// *** MODIFICATION END ***
// Call buildBetButtons to ensure both main and mini-bar bet buttons are updated
buildBetButtons();
updateStatus();
updatePanelVisibility();
}
function addResult(type) {
if (!roundActive) return;
if (type === 'win') {
totalProfit += lastPot;
} else {
totalProfit -= lastPot;
}
saveTotalProfit();
results.unshift({ result: type, bet: lastPot });
if (results.length > maxDisplayMatches) {
results.pop();
}
saveResults();
roundActive = false;
hasTracked = true;
// Reset alertShown flags if profit/loss goes back within limits
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() {
// Check for "Waiting:", or "Create Game" if a new game can be started
// Added check for button.start-game which might be present on some create game screens
if (hasTracked && (document.body.innerText.includes('Waiting:') || document.body.innerText.includes('Create Game') || document.querySelector('button.start-game'))) {
roundActive = true;
hasTracked = false;
updateStatus();
}
}
// --- Unified Drag and Move Logic ---
const savePos = () => localStorage.setItem(POS_KEY, JSON.stringify({
top: panel.style.top, left: panel.style.left
}));
function onDragMove(e) {
if (!isDragging) return;
e.preventDefault();
const moveEvent = e.touches ? e.touches[0] : e;
if (typeof moveEvent.clientX === 'undefined') return;
const dx = moveEvent.clientX - dragMouseX;
const dy = moveEvent.clientY - dragMouseY;
dragMouseX = moveEvent.clientX;
dragMouseY = moveEvent.clientY;
panel.style.left = (panel.offsetLeft + dx) + 'px';
panel.style.top = (panel.offsetTop + dy) + 'px';
}
function onDragEnd() {
if (!isDragging) return;
isDragging = false;
document.removeEventListener('mousemove', onDragMove);
document.removeEventListener('mouseup', onDragEnd);
document.removeEventListener('touchmove', onDragMove);
document.removeEventListener('touchend', onDragEnd);
savePos();
panel.style.cursor = '';
document.body.style.cursor = '';
}
function startDrag(e) {
if (isTwoFingerDragging) return;
e.preventDefault();
const startEvent = e.touches ? e.touches[0] : e;
if (typeof startEvent.clientX === 'undefined') return;
isDragging = true;
dragMouseX = startEvent.clientX;
dragMouseY = startEvent.clientY;
panel.style.cursor = 'grabbing';
document.body.style.cursor = 'grabbing';
document.addEventListener('mousemove', onDragMove);
document.addEventListener('mouseup', onDragEnd);
document.addEventListener('touchmove', onDragMove, { passive: false });
document.addEventListener('touchend', onDragEnd);
}
dragHandle.addEventListener('mousedown', startDrag);
dragHandle.addEventListener('touchstart', e => {
if (e.touches.length === 1) {
startDrag(e);
}
});
panel.addEventListener('mousedown', e => {
if (e.shiftKey && e.target !== dragHandle) {
startDrag(e);
}
});
// --- Two-Finger Drag Implementation for the entire panel ---
panel.addEventListener('touchstart', e => {
if (e.touches.length === 2) {
e.preventDefault();
isTwoFingerDragging = true;
isDragging = false; // Ensure single-drag is off
initialTouchMidX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
initialTouchMidY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
initialPanelX = panel.offsetLeft;
initialPanelY = panel.offsetTop;
panel.style.cursor = 'grabbing';
document.body.style.cursor = 'grabbing';
document.addEventListener('touchmove', onTwoFingerMove, { passive: false });
document.addEventListener('touchend', onTwoFingerEnd);
}
});
function onTwoFingerMove(e) {
if (!isTwoFingerDragging || e.touches.length !== 2) {
onTwoFingerEnd(e);
return;
}
e.preventDefault();
const currentTouchMidX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const currentTouchMidY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
const dx = currentTouchMidX - initialTouchMidX;
const dy = currentTouchMidY - initialTouchMidY;
panel.style.left = (initialPanelX + dx) + 'px';
panel.style.top = (initialPanelY + dy) + 'px';
}
function onTwoFingerEnd(e) {
if (!isTwoFingerDragging) return;
if (e.touches.length < 2) {
document.removeEventListener('touchmove', onTwoFingerMove);
document.removeEventListener('touchend', onTwoFingerEnd);
savePos();
isTwoFingerDragging = false;
panel.style.cursor = '';
document.body.style.cursor = '';
}
}
statusDiv.addEventListener('click', () => {
if (isDragging || isTwoFingerDragging) return;
collapsed = !collapsed;
// When collapsing, ensure settings/bets/edit-bets panels are hidden
if (collapsed) {
showSettings = false;
showBetsPanel = false;
showEditBetsPanel = false;
}
localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed));
refreshAll();
});
alertMessageDiv.addEventListener('click', () => {
alertMessageDiv.style.display = 'none';
});
refreshAll(); // Initial render
setInterval(() => {
updatePanelVisibility();
scanStart();
scanPot();
scanResult();
}, 500);
})();