// ==UserScript==
// @name Torn Trade • Floating Accept/Cancel (PDA/Mobile)
// @namespace https://greasyfork.org/en/scripts/553137-torn-trade-floating-accept-cancel-buttons
// @version 1.1.0
// @description Compact floating Accept/Cancel for Torn trade page with touch dragging (made for Torn PDA/mobile). Buttons stay side-by-side and mirror the native ones.
// @author KillerCleat [2842410]
// @license MIT
// @homepageURL https://greasyfork.org/en/scripts/553137-torn-trade-floating-accept-cancel-buttons
// @source https://greasyfork.org/en/scripts/553137-torn-trade-floating-accept-cancel-buttons
// @supportURL https://greasyfork.org/en/scripts/553137-torn-trade-floating-accept-cancel-buttons/feedback
// @match https://www.torn.com/trade.php*
// @match https://www.torn.com/page.php?sid=Trade*
// @include https://www.torn.com/trade.php*
// @include https://www.torn.com/page.php?sid=Trade*
// @icon https://www.torn.com/favicon.ico
// @run-at document-idle
// @noframes
// @grant none
// ==/UserScript==
(function () {
"use strict";
/***** SETTINGS *****/
// Default mobile-friendly position (sit above PDA bottom bar; adjust if you like)
const DEFAULT_POSITION = { bottom: "84px", right: "12px" };
const START_LOCKED = false; // start unlocked so you can drag immediately on PDA
const COMPACT = true; // smaller paddings/fonts for PDA
/********************/
const $ = (s, r=document) => r.querySelector(s);
const SELECTORS = {
cancelBtn: "div.cancel-btn-wrap button.torn-btn.orange",
acceptBtn: "div.cancel-btn-wrap a.torn-btn.green.accept",
container: "div.cancel-btn-wrap"
};
function createBar() {
const bar = document.createElement("div");
bar.id = "kc-pda-trade-bar";
bar.innerHTML = `
<div class="kc-row" role="toolbar" aria-label="Trade actions">
<button class="torn-btn orange kc-cancel" type="button">CANCEL</button>
<a class="torn-btn green kc-accept" role="button">ACCEPT</a>
<button class="kc-pin" title="Pin/Unpin" aria-label="Pin/Unpin">📌</button>
</div>
`;
// container styling
Object.assign(bar.style, {
position: "fixed",
zIndex: "2147483647", // above PDA chrome
left: "auto",
top: "auto",
background: "rgba(20,20,20,0.92)",
borderRadius: "12px",
boxShadow: "0 8px 24px rgba(0,0,0,.35)",
backdropFilter: "blur(3px)",
WebkitBackdropFilter: "blur(3px)",
padding: COMPACT ? "6px" : "8px",
...DEFAULT_POSITION,
});
// inner CSS
const style = document.createElement("style");
style.textContent = `
#kc-pda-trade-bar { touch-action: none; }
#kc-pda-trade-bar .kc-row {
display:flex; align-items:center; gap:${COMPACT ? "8px" : "10px"};
}
#kc-pda-trade-bar .torn-btn{
text-decoration:none; text-transform:uppercase; font-weight:800;
border-radius:10px; display:inline-flex; align-items:center; justify-content:center;
padding:${COMPACT ? "8px 12px" : "10px 14px"};
min-width:${COMPACT ? "96px" : "110px"};
font-size:${COMPACT ? "14px" : "15px"};
}
#kc-pda-trade-bar .kc-pin{
border:0; background:transparent; color:#fff; opacity:.8;
font-size:${COMPACT ? "18px" : "20px"}; padding:${COMPACT ? "2px 6px" : "2px 8px"};
border-radius:10px; margin-left:${COMPACT ? "2px" : "4px"};
}
#kc-pda-trade-bar .kc-pin:hover{ opacity:1; background:rgba(255,255,255,.08) }
#kc-pda-trade-bar.kc-disabled a.kc-accept{ pointer-events:none; opacity:.5 }
#kc-pda-trade-bar.kc-disabled button.kc-cancel{ opacity:.5 }
`;
document.head.appendChild(style);
document.body.appendChild(bar);
return bar;
}
function linkButtons(bar) {
const liveCancel = $(SELECTORS.cancelBtn);
const liveAccept = $(SELECTORS.acceptBtn);
const cloneCancel = $(".kc-cancel", bar);
const cloneAccept = $(".kc-accept", bar);
if (!liveCancel || !liveAccept) {
bar.classList.add("kc-disabled");
cloneCancel.disabled = true;
cloneAccept.removeAttribute("href");
return;
}
const sync = () => {
// mirror href
const href = liveAccept.getAttribute("href");
if (href) {
cloneAccept.setAttribute("href", href);
bar.classList.remove("kc-disabled");
cloneCancel.disabled = !!(liveCancel.disabled || liveCancel.classList.contains("disabled"));
} else {
cloneAccept.removeAttribute("href");
bar.classList.add("kc-disabled");
}
if (liveAccept.classList.contains("disabled")) {
cloneAccept.removeAttribute("href");
bar.classList.add("kc-disabled");
}
};
cloneCancel.onclick = (e) => { e.preventDefault(); liveCancel.click(); };
cloneAccept.onclick = (e) => {
// if no href, click native
if (!cloneAccept.getAttribute("href")) { e.preventDefault(); liveAccept.click(); }
};
sync();
const opts = { attributes:true, attributeFilter:["class","href","disabled"] };
const mo1 = new MutationObserver(sync); mo1.observe(liveAccept, opts);
const mo2 = new MutationObserver(sync); mo2.observe(liveCancel, opts);
bar._mo = [mo1, mo2];
}
// touch & mouse dragging (works in PDA webview)
function enableDrag(bar) {
let locked = START_LOCKED;
let dragging = false;
let startX = 0, startY = 0;
let startLeft = 0, startTop = 0;
const pinBtn = $(".kc-pin", bar);
const setPinIcon = () => pinBtn.textContent = locked ? "📌" : "📍";
setPinIcon();
pinBtn.addEventListener("click", (e) => {
e.stopPropagation();
locked = !locked;
setPinIcon();
});
const start = (clientX, clientY) => {
if (locked) return;
dragging = true;
const rect = bar.getBoundingClientRect();
startX = clientX;
startY = clientY;
startLeft = rect.left + window.scrollX;
startTop = rect.top + window.scrollY;
// switch to top/left while dragging
bar.style.left = startLeft + "px";
bar.style.top = startTop + "px";
bar.style.right = "";
bar.style.bottom = "";
};
const move = (clientX, clientY) => {
if (!dragging) return;
const dx = clientX - startX;
const dy = clientY - startY;
bar.style.left = (startLeft + dx) + "px";
bar.style.top = (startTop + dy) + "px";
};
const end = () => { dragging = false; };
// Mouse
bar.addEventListener("mousedown", (e) => {
// ignore direct clicks on links/buttons that aren’t the pin
if (e.target.closest("a, button:not(.kc-pin)")) return;
start(e.clientX, e.clientY);
e.preventDefault();
}, { passive:false });
window.addEventListener("mousemove", (e) => move(e.clientX, e.clientY));
window.addEventListener("mouseup", end);
// Touch
bar.addEventListener("touchstart", (e) => {
if (e.target.closest("a, button:not(.kc-pin)")) return;
const t = e.changedTouches[0];
start(t.clientX, t.clientY);
e.preventDefault();
}, { passive:false });
window.addEventListener("touchmove", (e) => {
if (!dragging) return;
const t = e.changedTouches[0];
move(t.clientX, t.clientY);
}, { passive:false });
window.addEventListener("touchend", end, { passive:true });
window.addEventListener("touchcancel", end, { passive:true });
}
function observeForButtons(bar) {
const relink = () => linkButtons(bar);
relink();
const mo = new MutationObserver(() => {
if ($(SELECTORS.container)) relink();
});
mo.observe(document.body, { childList:true, subtree:true });
bar._rootObserver = mo;
}
function init() {
const bar = createBar();
enableDrag(bar);
observeForButtons(bar);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();