// ==UserScript==
// @name Torn.com Background Theme Editor
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Change Torn's background with predefined themes and custom colors, with dark mode toggle
// @author TR0LL [2561502]
// @match https://www.torn.com/*
// @grant GM_getValue
// @grant GM_setValue
// @license Proprietary
// ==/UserScript==
(function() {
"use strict";
// ===== Configuration =====
const CONFIG = {
defaultBgColor: "#000000",
selectorContentContainer: ".content.responsive-sidebar-container.logged-in",
themes: {
pureBlack: {
name: "Pure Black",
color: "#000000"
},
darkGray: {
name: "Dark Gray",
color: "#121212"
},
midnightBlue: {
name: "Midnight Blue",
color: "#0A1929"
},
princess: {
name: "Princess",
color: "#c80e71"
},
custom: {
name: "Custom",
color: null // Will use user's custom color
}
}
};
// ===== State Management =====
const state = {
bgColor: GM_getValue("bgColor", CONFIG.defaultBgColor),
currentTheme: GM_getValue("currentTheme", "pureBlack"),
isDarkMode: GM_getValue("isDarkMode", false),
isObserving: false,
isPanelVisible: false
};
// ===== DOM Manipulation =====
function applyBackgroundColor(color) {
const contentContainer = document.querySelector(CONFIG.selectorContentContainer);
if (contentContainer) {
contentContainer.style.backgroundColor = color;
return true;
}
return false;
}
function saveBackgroundColor(color, themeName = null) {
// Validate color format
if (!/^#[0-9A-F]{6}$/i.test(color)) {
console.warn("Invalid color format:", color);
return false;
}
state.bgColor = color;
GM_setValue("bgColor", color);
// Save theme if provided
if (themeName) {
state.currentTheme = themeName;
GM_setValue("currentTheme", themeName);
}
return applyBackgroundColor(color);
}
// Get current background color based on theme
function getCurrentThemeColor() {
if (state.currentTheme === "custom") {
return state.bgColor;
} else if (CONFIG.themes[state.currentTheme]) {
return CONFIG.themes[state.currentTheme].color;
} else {
return CONFIG.defaultBgColor;
}
}
// ===== Dark Mode Handling =====
function toggleDarkMode(setDarkMode = null) {
// If a specific state is provided, use it, otherwise toggle
const targetDarkMode = setDarkMode !== null ? setDarkMode : !state.isDarkMode;
// Direct method: Toggle the dark-mode class on the body
const bodyElement = document.getElementById('body') || document.body;
if (bodyElement) {
if (targetDarkMode) {
bodyElement.classList.add('dark-mode');
} else {
bodyElement.classList.remove('dark-mode');
}
// Also try to update the checkbox state to match (for consistency)
const darkModeCheckbox = document.querySelector("#dark-mode-state");
if (darkModeCheckbox) {
darkModeCheckbox.checked = targetDarkMode;
}
// Update our state
state.isDarkMode = targetDarkMode;
GM_setValue("isDarkMode", targetDarkMode);
}
}
// ===== Observer for Dynamic Content =====
const observer = new MutationObserver((mutations) => {
// Only reapply if we found actual DOM changes that might affect our target
const shouldReapply = mutations.some(mutation =>
mutation.type === 'childList' ||
(mutation.type === 'attributes' &&
(mutation.attributeName === 'style' ||
mutation.attributeName === 'class'))
);
if (shouldReapply) {
applyBackgroundColor(getCurrentThemeColor());
}
});
function startObserving() {
if (state.isObserving) return;
const contentContainer = document.querySelector(CONFIG.selectorContentContainer);
if (contentContainer) {
observer.observe(contentContainer, {
attributes: true,
childList: true,
subtree: false // Only observe direct children
});
state.isObserving = true;
}
}
function stopObserving() {
observer.disconnect();
state.isObserving = false;
}
// ===== UI Creation =====
function createUI() {
// Add stylesheet
addStyles();
// Create the toggle button (small, with moon icon)
const toggleButton = document.createElement("div");
toggleButton.className = "bg-theme-toggle";
toggleButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>`;
document.body.appendChild(toggleButton);
// Create the panel
const panel = document.createElement("div");
panel.id = "bg-theme-panel";
panel.className = "bg-theme-panel";
// Add header
let panelHTML = `
<div class="bg-theme-header">
<span>Background Theme</span>
<span class="bg-theme-close">×</span>
</div>
<div class="bg-theme-content">
<div class="bg-theme-group">
<label for="bg-theme-select">Theme:</label>
<select id="bg-theme-select" class="bg-theme-select">`;
// Add theme options
Object.keys(CONFIG.themes).forEach(themeKey => {
const selected = themeKey === state.currentTheme ? 'selected' : '';
panelHTML += `<option value="${themeKey}" ${selected}>${CONFIG.themes[themeKey].name}</option>`;
});
// Add custom color section, dark mode toggle, and buttons
panelHTML += `
</select>
</div>
<div id="custom-color-group" class="bg-theme-group" style="display: ${state.currentTheme === 'custom' ? 'block' : 'none'}">
<label for="bg-theme-color">Custom Color:</label>
<div class="bg-theme-color-preview" id="color-preview"></div>
<div class="bg-theme-color-inputs">
<input type="color" id="bg-theme-color-picker" value="${state.bgColor}">
<input type="text" id="bg-theme-hex" value="${state.bgColor}" placeholder="#RRGGBB">
</div>
</div>
<div class="bg-theme-group">
<div class="bg-theme-dark-mode">
<label for="bg-theme-dark-mode-toggle">Force Dark Mode:</label>
<label class="bg-theme-switch">
<input type="checkbox" id="bg-theme-dark-mode-toggle" ${state.isDarkMode ? 'checked' : ''}>
<span class="bg-theme-slider"></span>
</label>
</div>
</div>
<div class="bg-theme-buttons">
<button id="bg-theme-reset" class="bg-theme-button">Reset</button>
<button id="bg-theme-save" class="bg-theme-button bg-theme-save">Save</button>
</div>
<div id="bg-theme-save-indicator" class="bg-theme-save-indicator">Saved!</div>
<div class="bg-theme-credit">TR0LL [2561502]</div>
</div>
`;
panel.innerHTML = panelHTML;
document.body.appendChild(panel);
// Get references to elements
const themeSelect = document.getElementById('bg-theme-select');
const colorGroup = document.getElementById('custom-color-group');
const colorPreview = document.getElementById('color-preview');
const colorPicker = document.getElementById('bg-theme-color-picker');
const hexInput = document.getElementById('bg-theme-hex');
const darkModeToggle = document.getElementById('bg-theme-dark-mode-toggle');
const resetButton = document.getElementById('bg-theme-reset');
const saveButton = document.getElementById('bg-theme-save');
const saveIndicator = document.getElementById('bg-theme-save-indicator');
// Set initial color preview
colorPreview.style.backgroundColor = state.bgColor;
// Event Listeners
toggleButton.addEventListener('click', () => {
state.isPanelVisible = !state.isPanelVisible;
panel.classList.toggle('visible', state.isPanelVisible);
});
panel.querySelector('.bg-theme-close').addEventListener('click', () => {
state.isPanelVisible = false;
panel.classList.remove('visible');
});
// Theme select change
themeSelect.addEventListener('change', function() {
const selectedTheme = this.value;
state.currentTheme = selectedTheme;
// Show/hide custom color controls
colorGroup.style.display = selectedTheme === 'custom' ? 'block' : 'none';
// Apply theme color immediately
if (selectedTheme === 'custom') {
applyBackgroundColor(state.bgColor);
} else {
const themeColor = CONFIG.themes[selectedTheme].color;
colorPicker.value = themeColor;
hexInput.value = themeColor;
colorPreview.style.backgroundColor = themeColor;
applyBackgroundColor(themeColor);
}
});
// Color picker change
colorPicker.addEventListener('input', function() {
const newColor = this.value;
state.bgColor = newColor;
hexInput.value = newColor;
colorPreview.style.backgroundColor = newColor;
applyBackgroundColor(newColor);
});
// Hex input change
hexInput.addEventListener('input', function() {
let value = this.value;
// Auto-add hash if missing
if (!value.startsWith('#') && value.length > 0) {
value = '#' + value;
this.value = value;
}
// For complete valid hex codes, update immediately
if (/^#[0-9A-Fa-f]{6}$/i.test(value)) {
colorPicker.value = value;
colorPreview.style.backgroundColor = value;
state.bgColor = value;
applyBackgroundColor(value);
}
});
// Dark mode toggle
darkModeToggle.addEventListener('change', function() {
toggleDarkMode(this.checked);
});
// Reset button
resetButton.addEventListener('click', () => {
// Reset to Pure Black theme
themeSelect.value = 'pureBlack';
state.currentTheme = 'pureBlack';
colorGroup.style.display = 'none';
const pureBlackColor = CONFIG.themes.pureBlack.color;
colorPicker.value = pureBlackColor;
hexInput.value = pureBlackColor;
colorPreview.style.backgroundColor = pureBlackColor;
applyBackgroundColor(pureBlackColor);
// Reset dark mode
darkModeToggle.checked = false;
toggleDarkMode(false);
});
// Save button
saveButton.addEventListener('click', () => {
if (state.currentTheme === 'custom') {
saveBackgroundColor(colorPicker.value, 'custom');
} else {
saveBackgroundColor(CONFIG.themes[state.currentTheme].color, state.currentTheme);
}
// Show save confirmation
saveIndicator.classList.add('visible');
setTimeout(() => {
saveIndicator.classList.remove('visible');
}, 2000);
// Close panel
state.isPanelVisible = false;
panel.classList.remove('visible');
});
return panel;
}
// Show save indicator
function showSaveIndicator() {
const indicator = document.getElementById('bg-theme-save-indicator');
if (indicator) {
indicator.classList.add('visible');
setTimeout(() => {
indicator.classList.remove('visible');
}, 2000);
}
}
// Add CSS styles
function addStyles() {
const styleElement = document.createElement('style');
styleElement.textContent = `
/* Toggle Button */
.bg-theme-toggle {
position: fixed;
right: 10px;
top: 100px;
width: 32px;
height: 32px;
background-color: #333;
border-radius: 50%;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 9998;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
transition: transform 0.2s;
}
.bg-theme-toggle:hover {
transform: scale(1.1);
}
/* Panel */
.bg-theme-panel {
position: fixed;
right: -250px;
top: 100px;
width: 220px;
background-color: #222;
border-radius: 5px;
z-index: 9999;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
color: #eee;
font-family: Arial, sans-serif;
font-size: 13px;
transition: right 0.3s ease;
}
.bg-theme-panel.visible {
right: 10px;
}
/* Header */
.bg-theme-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
border-bottom: 1px solid #444;
font-weight: bold;
}
.bg-theme-close {
cursor: pointer;
font-size: 20px;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
}
.bg-theme-close:hover {
color: #ff4444;
}
/* Content */
.bg-theme-content {
padding: 15px;
}
.bg-theme-group {
margin-bottom: 15px;
}
.bg-theme-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #ccc;
}
.bg-theme-select {
width: 100%;
padding: 7px;
background-color: #333;
border: 1px solid #444;
color: #eee;
border-radius: 3px;
}
/* Color Preview */
.bg-theme-color-preview {
height: 25px;
width: 100%;
margin-bottom: 8px;
border: 1px solid #444;
border-radius: 3px;
}
/* Color Inputs */
.bg-theme-color-inputs {
display: flex;
gap: 8px;
}
#bg-theme-color-picker {
width: 40px;
height: 30px;
padding: 0;
border: 1px solid #444;
cursor: pointer;
}
#bg-theme-hex {
flex: 1;
padding: 6px 8px;
background-color: #333;
border: 1px solid #444;
color: #eee;
border-radius: 3px;
font-family: monospace;
}
/* Dark Mode Switch */
.bg-theme-dark-mode {
display: flex;
justify-content: space-between;
align-items: center;
}
.bg-theme-switch {
position: relative;
display: inline-block;
width: 44px;
height: 22px;
}
.bg-theme-switch input {
opacity: 0;
width: 0;
height: 0;
}
.bg-theme-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #555;
transition: .4s;
border-radius: 22px;
}
.bg-theme-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .bg-theme-slider {
background-color: #4CAF50;
}
input:checked + .bg-theme-slider:before {
transform: translateX(22px);
}
/* Buttons */
.bg-theme-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
.bg-theme-button {
flex: 1;
padding: 8px 0;
background-color: #444;
border: none;
border-radius: 3px;
color: #eee;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.bg-theme-button:hover {
background-color: #555;
}
.bg-theme-save {
background-color: #4CAF50;
}
.bg-theme-save:hover {
background-color: #3e8e41;
}
/* Save Indicator */
.bg-theme-save-indicator {
text-align: center;
margin-top: 10px;
color: #4CAF50;
opacity: 0;
transition: opacity 0.3s;
font-weight: bold;
}
.bg-theme-save-indicator.visible {
opacity: 1;
}
/* Credit */
.bg-theme-credit {
margin-top: 10px;
text-align: center;
font-size: 11px;
color: #777;
}
/* Mobile Responsiveness */
@media (max-width: 768px) {
.bg-theme-toggle {
width: 28px;
height: 28px;
right: 5px;
top: 70px;
}
.bg-theme-panel {
width: 190px;
}
.bg-theme-panel.visible {
right: 5px;
}
}
`;
document.head.appendChild(styleElement);
}
// ===== Initialization =====
function init() {
// Set initial color based on saved theme
const currentColor = getCurrentThemeColor();
// Apply saved background color to all pages
if (!applyBackgroundColor(currentColor)) {
// If element not found immediately, wait for DOM to be ready
window.addEventListener("DOMContentLoaded", () => {
applyBackgroundColor(currentColor);
startObserving();
});
} else {
startObserving();
}
// Set dark mode state based on saved preference
if (state.isDarkMode) {
toggleDarkMode(true);
}
// Only create UI on preferences page
const currentUrl = window.location.href;
if (currentUrl.includes("/preferences.php")) {
// Wait for DOM to be ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", createUI);
} else {
createUI();
}
}
}
// Start the script
init();
})();