Safe Fold

Requires a double click to fold when checking is available. Prevents accidental folds.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);

})();