Greasy Fork is available in English.
Requires a double click to fold when checking is available. Prevents accidental folds.
// ==UserScript==
// @name Safe Fold
// @namespace torn.poker.safefold
// @version 1.1
// @description Requires a double click to fold when checking is available. Prevents accidental folds.
// @author HopesG
// @match https://www.torn.com/*
// @grant none
// @run-at document-end
// @license All rights reserved
// ==/UserScript==
(function () {
'use strict';
/* ---------- page check ---------- */
function isPokerPage() {
return document.body && document.body.dataset.page && document.body.dataset.page.includes('holdem');
}
/* ---------- helpers ---------- */
function getText(el) {
return (el.innerText || el.textContent || '').replace(/\s+/g, ' ').trim().toLowerCase();
}
function isVisible(el) {
return !!(el && el.offsetParent !== null);
}
function canCheck() {
// Look for any visible button that is check or check/fold
return Array.from(document.querySelectorAll('button')).some(btn => {
if (!isVisible(btn)) return false;
const text = getText(btn);
return text === 'check' || text === 'check / fold' || text === 'call any / check';
});
}
function findFoldButtons() {
return Array.from(document.querySelectorAll('button')).filter(btn => {
return isVisible(btn) && getText(btn) === 'fold';
});
}
/* ---------- style ---------- */
const STYLE_ID = 'tp-safefold-style';
function injectStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.tp-fold-armed {
background: linear-gradient(180deg, #ffb347 0%, #cc6600 100%) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.20),
0 0 0 2px rgba(255,160,50,.50),
0 0 14px rgba(255,140,0,.45) !important;
animation: tpFoldPulse .4s ease-in-out infinite alternate !important;
transition: none !important;
}
.tp-fold-armed::after {
content: "Click again to fold";
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: rgba(20, 10, 0, 0.92);
color: #ffcc88;
font-size: 12px;
font-weight: 600;
padding: 5px 10px;
border-radius: 8px;
white-space: nowrap;
pointer-events: none;
box-shadow: 0 2px 8px rgba(0,0,0,.5);
border: 1px solid rgba(255,160,50,.35);
z-index: 99999;
}
.tp-fold-armed::before {
/* override the button shine from Colorful Poker if present */
opacity: 0 !important;
}
@keyframes tpFoldPulse {
from { filter: brightness(1.00); }
to { filter: brightness(1.15); }
}
`;
document.head.appendChild(style);
}
/* ---------- core logic ---------- */
const armed = new WeakMap(); // btn → timeout id
const intercepted = new WeakSet();
function armButton(btn) {
// Already armed — this is the confirmation click, let it through
if (armed.has(btn)) {
clearTimeout(armed.get(btn));
armed.delete(btn);
btn.classList.remove('tp-fold-armed');
// Don't block — the real click fires naturally
return true;
}
// First click — arm it
btn.classList.add('tp-fold-armed');
const timer = setTimeout(() => {
btn.classList.remove('tp-fold-armed');
armed.delete(btn);
}, 2000);
armed.set(btn, timer);
return false; // block this click
}
function interceptFoldButton(btn) {
if (intercepted.has(btn)) return;
intercepted.add(btn);
btn.addEventListener('click', (e) => {
// Only intercept when check is available
if (!canCheck()) return;
// If already armed, let the click through (confirmation)
if (armed.has(btn)) {
clearTimeout(armed.get(btn));
armed.delete(btn);
btn.classList.remove('tp-fold-armed');
return; // allow
}
// First click — block and arm
e.preventDefault();
e.stopImmediatePropagation();
armButton(btn);
}, true); // capture phase so we beat Torn's handler
}
/* ---------- main loop ---------- */
function run() {
if (!isPokerPage()) return;
injectStyle();
findFoldButtons().forEach(interceptFoldButton);
}
setInterval(run, 700);
})();