Shows a compact draggable banner near the top of Torn pages indicating whether your bazaar is open or closed.
// ==UserScript==
// @name Torn Bazaar Open Warning
// @namespace http://tampermonkey.net/
// @version 1.12
// @author Crognell [3208324]
// @license MIT
// @description Shows a compact draggable banner near the top of Torn pages indicating whether your bazaar is open or closed.
// @match https://www.torn.com/*
// @grant GM_addStyle
// ==/UserScript==
(function () {
'use strict';
var SCRIPT_VERSION = '1.12';
var BANNER_ID = 'tm-bazaar-open-warning';
var HANDLE_CLASS = 'tm-bazaar-open-warning-handle';
var LABEL_CLASS = 'tm-bazaar-open-warning-label';
var STYLE_ID = 'tm-bazaar-open-warning-style';
var POSITION_KEY = 'tm_bazaar_open_warning_position';
var DEFAULT_TOP = 78;
var EDGE_PADDING = 8;
var POLL_MS = 1500;
var queuedRender = null;
function getFallbackBazaarUrl() {
return 'https://www.torn.com/bazaar.php#/';
}
function addStyles() {
if (document.getElementById(STYLE_ID)) return;
var style = GM_addStyle(
'#' + BANNER_ID + ' {' +
'position: fixed !important;' +
'z-index: 2147483647 !important;' +
'display: inline-flex !important;' +
'align-items: center !important;' +
'gap: 7px !important;' +
'max-width: calc(100vw - 16px) !important;' +
'padding: 6px 10px !important;' +
'border-radius: 10px !important;' +
'font-size: 13px !important;' +
'font-weight: 700 !important;' +
'line-height: 1.1 !important;' +
'text-align: center !important;' +
'text-decoration: none !important;' +
'white-space: nowrap !important;' +
'box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18) !important;' +
'cursor: pointer !important;' +
'user-select: none !important;' +
'}' +
'#' + BANNER_ID + ':hover {' +
'filter: brightness(0.99) !important;' +
'box-shadow: 0 10px 22px rgba(0, 0, 0, 0.22) !important;' +
'}' +
'#' + BANNER_ID + '.is-open {' +
'border: 2px solid #b96b00 !important;' +
'background: linear-gradient(180deg, #fff2d6 0%, #ffd18a 100%) !important;' +
'color: #5a2c00 !important;' +
'}' +
'#' + BANNER_ID + '.is-closed {' +
'border: 2px solid #355f88 !important;' +
'background: linear-gradient(180deg, #e8f2ff 0%, #bfd8f5 100%) !important;' +
'color: #17344f !important;' +
'}' +
'#' + BANNER_ID + ' .' + HANDLE_CLASS + ' {' +
'display: inline-flex !important;' +
'align-items: center !important;' +
'justify-content: center !important;' +
'width: 18px !important;' +
'height: 18px !important;' +
'border-radius: 6px !important;' +
'background: rgba(255, 255, 255, 0.35) !important;' +
'border: 1px solid rgba(0, 0, 0, 0.12) !important;' +
'font-size: 10px !important;' +
'font-weight: 800 !important;' +
'letter-spacing: 0.08em !important;' +
'cursor: move !important;' +
'flex: 0 0 auto !important;' +
'}' +
'#' + BANNER_ID + ' .' + LABEL_CLASS + ' {' +
'display: inline-block !important;' +
'min-width: 0 !important;' +
'max-width: calc(100vw - 56px) !important;' +
'overflow: hidden !important;' +
'text-overflow: ellipsis !important;' +
'}'
);
if (style && style.id !== STYLE_ID) {
style.id = STYLE_ID;
}
}
function getBanner() {
return document.getElementById(BANNER_ID);
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function readStoredPosition() {
try {
var raw = window.localStorage.getItem(POSITION_KEY);
if (!raw) return null;
var parsed = JSON.parse(raw);
if (!parsed) return null;
if (typeof parsed.left !== 'number' || typeof parsed.top !== 'number') return null;
return {
left: parsed.left,
top: parsed.top
};
} catch (e) {
return null;
}
}
function writeStoredPosition(left, top) {
try {
window.localStorage.setItem(POSITION_KEY, JSON.stringify({
left: left,
top: top
}));
} catch (e) {}
}
function setBannerPosition(banner, left, top) {
banner.style.left = left + 'px';
banner.style.top = top + 'px';
banner.style.transform = 'none';
}
function getDefaultPosition(banner) {
var width = banner.offsetWidth || 180;
var maxLeft = Math.max(EDGE_PADDING, window.innerWidth - width - EDGE_PADDING);
var centeredLeft = Math.round((window.innerWidth - width) / 2);
return {
left: clamp(centeredLeft, EDGE_PADDING, maxLeft),
top: DEFAULT_TOP
};
}
function applySavedPosition(banner) {
var stored = readStoredPosition();
var width = banner.offsetWidth || 180;
var height = banner.offsetHeight || 32;
var maxLeft = Math.max(EDGE_PADDING, window.innerWidth - width - EDGE_PADDING);
var maxTop = Math.max(EDGE_PADDING, window.innerHeight - height - EDGE_PADDING);
var pos = stored || getDefaultPosition(banner);
setBannerPosition(
banner,
clamp(Math.round(pos.left), EDGE_PADDING, maxLeft),
clamp(Math.round(pos.top), EDGE_PADDING, maxTop)
);
}
function bindDragBehavior(banner, handle) {
var dragState = null;
function getPoint(event) {
if (event.touches && event.touches.length) {
return {
x: event.touches[0].clientX,
y: event.touches[0].clientY
};
}
return {
x: event.clientX,
y: event.clientY
};
}
function onMove(event) {
if (!dragState) return;
var point = getPoint(event);
var maxLeft = Math.max(EDGE_PADDING, window.innerWidth - banner.offsetWidth - EDGE_PADDING);
var maxTop = Math.max(EDGE_PADDING, window.innerHeight - banner.offsetHeight - EDGE_PADDING);
var nextLeft = clamp(dragState.startLeft + (point.x - dragState.startX), EDGE_PADDING, maxLeft);
var nextTop = clamp(dragState.startTop + (point.y - dragState.startY), EDGE_PADDING, maxTop);
setBannerPosition(banner, Math.round(nextLeft), Math.round(nextTop));
if (event.cancelable) event.preventDefault();
}
function stopDrag() {
if (!dragState) return;
writeStoredPosition(
parseInt(banner.style.left, 10) || EDGE_PADDING,
parseInt(banner.style.top, 10) || EDGE_PADDING
);
dragState = null;
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', stopDrag);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', stopDrag);
}
function startDrag(event) {
var point = getPoint(event);
dragState = {
startX: point.x,
startY: point.y,
startLeft: parseInt(banner.style.left, 10) || 0,
startTop: parseInt(banner.style.top, 10) || 0
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', stopDrag);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', stopDrag);
if (event.cancelable) event.preventDefault();
}
handle.addEventListener('mousedown', startDrag);
handle.addEventListener('touchstart', startDrag, { passive: false });
handle.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
});
}
function getOrCreateBanner() {
var banner = getBanner();
if (banner) return banner;
if (!document.body) return null;
banner = document.createElement('a');
banner.id = BANNER_ID;
banner.href = getFallbackBazaarUrl();
var handle = document.createElement('span');
handle.className = HANDLE_CLASS;
handle.textContent = '::';
handle.title = 'Drag banner';
var label = document.createElement('span');
label.className = LABEL_CLASS;
banner.appendChild(handle);
banner.appendChild(label);
document.body.appendChild(banner);
bindDragBehavior(banner, handle);
applySavedPosition(banner);
return banner;
}
function isVisible(el) {
if (!el || !document.body || !document.body.contains(el)) return false;
var style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') return false;
var rect = el.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function findOpenBazaarIcon() {
var selectors = [
'#icon35',
'[id="icon35"]',
'[class*="icon35"]',
'[class*="icon35_"]'
];
for (var i = 0; i < selectors.length; i++) {
var nodes = document.querySelectorAll(selectors[i]);
for (var j = 0; j < nodes.length; j++) {
if (isVisible(nodes[j])) return nodes[j];
}
}
return null;
}
function getBazaarUrl(icon) {
if (!icon) return getFallbackBazaarUrl();
var link = icon.closest('a');
if (link && link.href) return link.href;
if (icon.href) return icon.href;
return getFallbackBazaarUrl();
}
function setBannerState(banner, text, href, title, className) {
var label = banner.querySelector('.' + LABEL_CLASS);
if (!label) return;
label.textContent = text;
banner.href = href;
banner.title = title;
banner.className = className;
applySavedPosition(banner);
}
function render() {
addStyles();
var icon = findOpenBazaarIcon();
var banner = getOrCreateBanner();
if (!banner) return;
if (icon) {
setBannerState(banner, 'Bazaar open', getBazaarUrl(icon), 'Open bazaar', 'is-open');
return;
}
setBannerState(banner, 'Bazaar closed', getFallbackBazaarUrl(), 'Go to bazaar', 'is-closed');
}
function queueRender(delay) {
if (queuedRender) clearTimeout(queuedRender);
queuedRender = window.setTimeout(function () {
queuedRender = null;
render();
}, typeof delay === 'number' ? delay : 100);
}
var observer = new MutationObserver(function () {
queueRender(100);
});
function startObserver() {
observer.disconnect();
if (!document.body) return;
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function init() {
addStyles();
startObserver();
queueRender(0);
}
window.addEventListener('load', init);
window.addEventListener('hashchange', function () {
queueRender(0);
});
window.addEventListener('resize', function () {
queueRender(0);
});
setTimeout(init, 250);
setTimeout(function () {
queueRender(0);
}, 1000);
setTimeout(function () {
queueRender(0);
}, 2500);
setInterval(function () {
queueRender(0);
}, POLL_MS);
console.log('[Torn Bazaar Open Warning v' + SCRIPT_VERSION + '] Loaded');
})();