Heavy balance blur, session P&L tracker, bet multipliers. Cosmetic/QoL only.
// ==UserScript==
// @name Torn Blackjack Helper
// @namespace https://www.torn.com/
// @version 2.0.0
// @description Heavy balance blur, session P&L tracker, bet multipliers. Cosmetic/QoL only.
// @author Muckduck
// @match https://www.torn.com/loader.php?sid=blackjack*
// @match https://www.torn.com/page.php?sid=blackjack*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ─── Persisted prefs ───────────────────────────────────────────────────────
const KEY_HIDE_BAL = 'bj_hideBalance';
const KEY_SHOW_PNL = 'bj_showPnl';
let hideBalance = GM_getValue(KEY_HIDE_BAL, false);
let showPnl = GM_getValue(KEY_SHOW_PNL, true);
// ─── Session P&L state (in-memory only, resets on page load) ──────────────
let sessionPnl = 0;
let lastMoney = null;
// ─── Balance element ───────────────────────────────────────────────────────
function getMoneyEl() {
return document.getElementById('user-money');
}
function getRawMoney() {
const el = getMoneyEl();
if (!el) return null;
const raw = parseInt(el.getAttribute('data-money'), 10);
return isNaN(raw) ? null : raw;
}
// ─── Blur / unblur ─────────────────────────────────────────────────────────
function applyBalanceVisibility() {
const el = getMoneyEl();
if (!el) return;
if (hideBalance) {
el.style.filter = 'blur(12px) brightness(0.4)';
el.style.userSelect = 'none';
el.style.transition = 'filter 0.1s ease';
} else {
el.style.filter = '';
el.style.userSelect = '';
el.style.transition = '';
}
}
// ─── Session P&L ───────────────────────────────────────────────────────────
function initSession() {
const money = getRawMoney();
if (money === null) return;
lastMoney = money;
sessionPnl = 0;
updatePnlDisplay();
}
function checkMoneyChange() {
const money = getRawMoney();
if (money === null || lastMoney === null) return;
if (money !== lastMoney) {
sessionPnl += (money - lastMoney);
lastMoney = money;
updatePnlDisplay();
}
}
function formatMoney(n) {
const abs = Math.abs(n);
let str;
if (abs >= 1_000_000_000) str = (abs / 1_000_000_000).toFixed(2) + 'B';
else if (abs >= 1_000_000) str = (abs / 1_000_000).toFixed(2) + 'M';
else if (abs >= 1_000) str = (abs / 1_000).toFixed(1) + 'K';
else str = abs.toLocaleString();
return (n >= 0 ? '+$' : '-$') + str;
}
function updatePnlDisplay() {
const el = document.getElementById('bj-pnl-value');
if (!el) return;
el.textContent = formatMoney(sessionPnl);
el.style.color = sessionPnl > 0 ? '#2ecc71' : sessionPnl < 0 ? '#e74c3c' : '#aaaaaa';
}
// ─── Bet helpers ───────────────────────────────────────────────────────────
function getCurrentBet() {
const input = document.querySelector(
'input.bet, input[name="bet"], input[class*="bet-input"], input[class*="betInput"], input[placeholder*="bet" i]'
);
if (!input) return null;
const val = parseFloat(input.value.replace(/[^0-9.]/g, ''));
return isNaN(val) ? null : { input, val };
}
function setBet(newVal) {
const result = getCurrentBet();
if (!result) { showToast('Bet input not found.'); return; }
const rounded = Math.floor(newVal);
// Use React's internal setter if available (handles framework-controlled inputs)
const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value');
if (nativeSetter && nativeSetter.set) {
nativeSetter.set.call(result.input, rounded);
} else {
result.input.value = rounded;
}
// Fire both input and change — React needs InputEvent, legacy code needs Event
result.input.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
result.input.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
result.input.focus();
}
// ─── Toast ─────────────────────────────────────────────────────────────────
function showToast(msg) {
const t = document.createElement('div');
t.textContent = msg;
Object.assign(t.style, {
position: 'fixed', bottom: '80px', right: '20px', zIndex: 99999,
background: '#1a1a2e', color: '#e0c97f', padding: '8px 14px',
borderRadius: '6px', fontSize: '13px', fontFamily: 'monospace',
boxShadow: '0 2px 12px rgba(0,0,0,0.6)', opacity: '1',
transition: 'opacity 0.4s ease', pointerEvents: 'none'
});
document.body.appendChild(t);
setTimeout(() => { t.style.opacity = '0'; }, 2000);
setTimeout(() => t.remove(), 2500);
}
// ─── UI helpers ────────────────────────────────────────────────────────────
function styleBtn(el, bg) {
Object.assign(el.style, {
background: bg, color: '#f0e6c0',
border: '1px solid rgba(255,255,255,0.12)',
borderRadius: '6px', padding: '6px 12px',
fontSize: '13px', fontFamily: 'monospace', fontWeight: '600',
cursor: 'pointer', boxShadow: '0 2px 6px rgba(0,0,0,0.4)',
transition: 'filter 0.15s ease', whiteSpace: 'nowrap',
});
el.onmouseenter = () => el.style.filter = 'brightness(1.25)';
el.onmouseleave = () => el.style.filter = '';
}
// ─── P&L widget ────────────────────────────────────────────────────────────
function buildPnlWidget() {
const wrap = document.createElement('div');
wrap.id = 'bj-pnl-widget';
Object.assign(wrap.style, {
background: 'rgba(8,8,18,0.92)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '10px',
padding: '10px 16px',
display: showPnl ? 'flex' : 'none',
flexDirection: 'column',
alignItems: 'flex-end',
gap: '3px',
backdropFilter: 'blur(6px)',
minWidth: '110px',
});
const title = document.createElement('div');
title.textContent = 'THIS SESSION';
Object.assign(title.style, {
fontSize: '9px', color: '#555', fontFamily: 'monospace',
letterSpacing: '2px', fontWeight: '700', textTransform: 'uppercase'
});
const val = document.createElement('div');
val.id = 'bj-pnl-value';
Object.assign(val.style, {
fontSize: '20px', fontFamily: 'monospace', fontWeight: '700',
color: '#aaaaaa', letterSpacing: '0.5px', lineHeight: '1.2'
});
val.textContent = '+$0';
const resetBtn = document.createElement('button');
resetBtn.textContent = '↺ reset';
Object.assign(resetBtn.style, {
background: 'none', border: 'none', color: '#444',
fontSize: '10px', fontFamily: 'monospace', cursor: 'pointer',
padding: '0', marginTop: '4px', letterSpacing: '0.5px'
});
resetBtn.onmouseenter = () => resetBtn.style.color = '#888';
resetBtn.onmouseleave = () => resetBtn.style.color = '#444';
resetBtn.addEventListener('click', () => {
sessionPnl = 0;
lastMoney = getRawMoney();
updatePnlDisplay();
});
wrap.appendChild(title);
wrap.appendChild(val);
wrap.appendChild(resetBtn);
return wrap;
}
// ─── Main panel ────────────────────────────────────────────────────────────
function injectUI() {
if (document.getElementById('bj-helper-panel')) return;
const panel = document.createElement('div');
panel.id = 'bj-helper-panel';
Object.assign(panel.style, {
position: 'fixed', bottom: '20px', right: '20px',
zIndex: '99998', display: 'flex', flexDirection: 'column',
gap: '8px', alignItems: 'flex-end',
});
// P&L widget (built first so togglePnlBtn can reference it)
const pnlWidget = buildPnlWidget();
// Toggle balance blur
const toggleBalBtn = document.createElement('button');
styleBtn(toggleBalBtn, hideBalance ? '#6b1111' : '#0f4a22');
toggleBalBtn.textContent = hideBalance ? '💰 Show Balance' : '🙈 Hide Balance';
toggleBalBtn.addEventListener('click', () => {
hideBalance = !hideBalance;
GM_setValue(KEY_HIDE_BAL, hideBalance);
styleBtn(toggleBalBtn, hideBalance ? '#6b1111' : '#0f4a22');
toggleBalBtn.textContent = hideBalance ? '💰 Show Balance' : '🙈 Hide Balance';
applyBalanceVisibility();
});
// Toggle P&L display
const togglePnlBtn = document.createElement('button');
styleBtn(togglePnlBtn, showPnl ? '#4a3800' : '#222222');
togglePnlBtn.textContent = showPnl ? '📊 Hide P&L' : '📊 Show P&L';
togglePnlBtn.addEventListener('click', () => {
showPnl = !showPnl;
GM_setValue(KEY_SHOW_PNL, showPnl);
pnlWidget.style.display = showPnl ? 'flex' : 'none';
styleBtn(togglePnlBtn, showPnl ? '#4a3800' : '#222222');
togglePnlBtn.textContent = showPnl ? '📊 Hide P&L' : '📊 Show P&L';
});
// Bet multiplier buttons
const betRow = document.createElement('div');
Object.assign(betRow.style, { display: 'flex', gap: '6px' });
[['× ½', 0.5], ['× 1.5', 1.5], ['× 2', 2]].forEach(([lbl, mult]) => {
const btn = document.createElement('button');
btn.textContent = lbl;
btn.title = `Multiply bet by ${mult}`;
styleBtn(btn, '#152050');
btn.addEventListener('click', () => {
const r = getCurrentBet();
if (r) setBet(Math.max(1, r.val * mult));
else showToast('Bet input not found.');
});
betRow.appendChild(btn);
});
// Label
const label = document.createElement('div');
label.textContent = '🃏 BJ Helper v2';
Object.assign(label.style, {
fontSize: '10px', color: '#333', textAlign: 'right',
fontFamily: 'monospace', letterSpacing: '0.5px'
});
panel.appendChild(pnlWidget);
panel.appendChild(toggleBalBtn);
panel.appendChild(togglePnlBtn);
panel.appendChild(betRow);
panel.appendChild(label);
document.body.appendChild(panel);
applyBalanceVisibility();
initSession();
}
// ─── MutationObserver ──────────────────────────────────────────────────────
const observer = new MutationObserver(() => {
if (hideBalance) applyBalanceVisibility();
checkMoneyChange();
});
// ─── Init ──────────────────────────────────────────────────────────────────
function init() {
injectUI();
observer.observe(document.body, {
childList: true,
subtree: true,
attributeFilter: ['data-money']
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 1200);
}
})();