Torn.com Background Theme Editor

Change Torn's background with predefined themes and custom colors

  1. // ==UserScript==
  2. // @name Torn.com Background Theme Editor
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Change Torn's background with predefined themes and custom colors
  6. // @author TR0LL [2561502]
  7. // @match https://www.torn.com/*
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_deleteValue
  11. // @license Proprietary
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. "use strict";
  16.  
  17. const CONFIG = {
  18. defaultBgColor: "#191919",
  19. darkModeBgColor: "#242424",
  20. selectorContentContainer: ".content.responsive-sidebar-container.logged-in",
  21. themes: {
  22. pureBlack: { name: "Pure Black", color: "#000000" },
  23. midnightBlue: { name: "Midnight Blue", color: "#1a2a42" },
  24. princess: { name: "Princess", color: "#c80e71" },
  25. custom: { name: "Custom", color: null }
  26. }
  27. };
  28.  
  29. const state = {
  30. bgColor: GM_getValue("bgColor", "#191919"),
  31. currentTheme: GM_getValue("currentTheme", null),
  32. isObserving: false,
  33. isPanelVisible: false
  34. };
  35.  
  36. const domCache = { contentContainer: null, bodyElement: null };
  37.  
  38. function getContentContainer() {
  39. return domCache.contentContainer || (domCache.contentContainer = document.querySelector(CONFIG.selectorContentContainer));
  40. }
  41.  
  42. function getBodyElement() {
  43. return domCache.bodyElement || (domCache.bodyElement = document.getElementById('body') || document.body);
  44. }
  45.  
  46. function throttle(func, delay) {
  47. let lastCall = 0;
  48. return function(...args) {
  49. const now = Date.now();
  50. if (now - lastCall >= delay) {
  51. lastCall = now;
  52. func.apply(this, args);
  53. }
  54. };
  55. }
  56.  
  57. function isDarkModeActive() {
  58. const bodyElement = getBodyElement();
  59. return bodyElement && bodyElement.classList.contains('dark-mode');
  60. }
  61.  
  62. function getAutoDetectedColor() {
  63. return isDarkModeActive() ? CONFIG.darkModeBgColor : CONFIG.defaultBgColor;
  64. }
  65.  
  66. function applyBackgroundColor(color) {
  67. const contentContainer = getContentContainer();
  68. if (contentContainer) {
  69. contentContainer.style.backgroundColor = color;
  70. return true;
  71. }
  72. return false;
  73. }
  74.  
  75. function saveBackgroundColor(color, themeName = null) {
  76. if (!/^#[0-9A-F]{6}$/i.test(color)) return false;
  77. state.bgColor = color;
  78. GM_setValue("bgColor", color);
  79. if (themeName !== undefined) {
  80. state.currentTheme = themeName;
  81. GM_setValue("currentTheme", themeName);
  82. }
  83. return applyBackgroundColor(color);
  84. }
  85.  
  86. function getCurrentThemeColor() {
  87. if (state.currentTheme === "custom") {
  88. return state.bgColor;
  89. } else if (state.currentTheme && CONFIG.themes[state.currentTheme]) {
  90. return CONFIG.themes[state.currentTheme].color;
  91. } else {
  92. return getAutoDetectedColor();
  93. }
  94. }
  95.  
  96. const observer = new MutationObserver((mutations) => {
  97. const shouldReapply = mutations.some(mutation =>
  98. (mutation.type === 'childList' && mutation.addedNodes.length > 0) ||
  99. (mutation.type === 'attributes' &&
  100. (mutation.attributeName === 'style' ||
  101. mutation.attributeName === 'class'))
  102. );
  103.  
  104. if (shouldReapply && state.currentTheme) {
  105. applyBackgroundColor(getCurrentThemeColor());
  106. }
  107. });
  108.  
  109. function startObserving() {
  110. if (state.isObserving) return;
  111.  
  112. const contentContainer = getContentContainer();
  113. if (contentContainer) {
  114. observer.observe(contentContainer, {
  115. attributes: true,
  116. attributeFilter: ['style', 'class'],
  117. childList: true,
  118. subtree: false
  119. });
  120.  
  121. const bodyElement = getBodyElement();
  122. if (bodyElement) {
  123. observer.observe(bodyElement, {
  124. attributes: true,
  125. attributeFilter: ['class'],
  126. subtree: false
  127. });
  128. }
  129.  
  130. state.isObserving = true;
  131. }
  132. }
  133.  
  134. function createUI() {
  135. addStyles();
  136.  
  137. const toggleButton = document.createElement("div");
  138. toggleButton.className = "bg-theme-toggle";
  139. 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>`;
  140. document.body.appendChild(toggleButton);
  141.  
  142. const panel = document.createElement("div");
  143. panel.id = "bg-theme-panel";
  144. panel.className = "bg-theme-panel";
  145.  
  146. let panelHTML = `
  147. <div class="bg-theme-header">
  148. <span>Background Theme</span>
  149. <span class="bg-theme-close">×</span>
  150. </div>
  151. <div class="bg-theme-content">
  152. <div class="bg-theme-group">
  153. <label for="bg-theme-select">Theme:</label>
  154. <select id="bg-theme-select" class="bg-theme-select">`;
  155.  
  156. Object.keys(CONFIG.themes).forEach(themeKey => {
  157. const selected = themeKey === state.currentTheme ? 'selected' : '';
  158. panelHTML += `<option value="${themeKey}" ${selected}>${CONFIG.themes[themeKey].name}</option>`;
  159. });
  160.  
  161. panelHTML += `
  162. </select>
  163. </div>
  164.  
  165. <div id="custom-color-group" class="bg-theme-group" style="display: ${state.currentTheme === 'custom' ? 'block' : 'none'}">
  166. <label for="bg-theme-color">Custom Color:</label>
  167. <div class="bg-theme-color-preview" id="color-preview"></div>
  168. <div class="bg-theme-color-inputs">
  169. <input type="color" id="bg-theme-color-picker" value="${state.bgColor}">
  170. <input type="text" id="bg-theme-hex" value="${state.bgColor}" placeholder="#RRGGBB">
  171. </div>
  172. </div>
  173.  
  174. <div class="bg-theme-buttons">
  175. <button id="bg-theme-reset" class="bg-theme-button">Reset to Default</button>
  176. <button id="bg-theme-save" class="bg-theme-button bg-theme-save">Save</button>
  177. </div>
  178.  
  179. <div id="bg-theme-save-indicator" class="bg-theme-save-indicator">Saved!</div>
  180. <div class="bg-theme-credit">TR0LL [2561502]</div>
  181. </div>
  182. `;
  183.  
  184. panel.innerHTML = panelHTML;
  185. document.body.appendChild(panel);
  186.  
  187. const themeSelect = document.getElementById('bg-theme-select');
  188. const colorGroup = document.getElementById('custom-color-group');
  189. const colorPreview = document.getElementById('color-preview');
  190. const colorPicker = document.getElementById('bg-theme-color-picker');
  191. const hexInput = document.getElementById('bg-theme-hex');
  192. const resetButton = document.getElementById('bg-theme-reset');
  193. const saveButton = document.getElementById('bg-theme-save');
  194. const saveIndicator = document.getElementById('bg-theme-save-indicator');
  195.  
  196. colorPreview.style.backgroundColor = state.bgColor;
  197.  
  198. toggleButton.addEventListener('click', () => {
  199. state.isPanelVisible = !state.isPanelVisible;
  200. panel.classList.toggle('visible', state.isPanelVisible);
  201. });
  202.  
  203. panel.querySelector('.bg-theme-close').addEventListener('click', () => {
  204. state.isPanelVisible = false;
  205. panel.classList.remove('visible');
  206. });
  207.  
  208. themeSelect.addEventListener('change', function() {
  209. const selectedTheme = this.value;
  210. state.currentTheme = selectedTheme;
  211. colorGroup.style.display = selectedTheme === 'custom' ? 'block' : 'none';
  212.  
  213. if (selectedTheme === 'custom') {
  214. colorPicker.value = state.bgColor;
  215. hexInput.value = state.bgColor;
  216. colorPreview.style.backgroundColor = state.bgColor;
  217. applyBackgroundColor(state.bgColor);
  218. } else if (selectedTheme && CONFIG.themes[selectedTheme]) {
  219. const themeColor = CONFIG.themes[selectedTheme].color;
  220. colorPicker.value = themeColor;
  221. hexInput.value = themeColor;
  222. colorPreview.style.backgroundColor = themeColor;
  223. applyBackgroundColor(themeColor);
  224. }
  225. });
  226.  
  227. const throttledColorApply = throttle((newColor) => {
  228. state.bgColor = newColor;
  229. hexInput.value = newColor;
  230. colorPreview.style.backgroundColor = newColor;
  231. applyBackgroundColor(newColor);
  232. }, 50);
  233.  
  234. colorPicker.addEventListener('input', function() {
  235. throttledColorApply(this.value);
  236. });
  237.  
  238. colorPicker.addEventListener('change', function() {
  239. const newColor = this.value;
  240. state.bgColor = newColor;
  241. hexInput.value = newColor;
  242. colorPreview.style.backgroundColor = newColor;
  243. applyBackgroundColor(newColor);
  244. });
  245.  
  246. hexInput.addEventListener('input', function() {
  247. let value = this.value;
  248. if (!value.startsWith('#') && value.length > 0) {
  249. value = '#' + value;
  250. this.value = value;
  251. }
  252. if (/^#[0-9A-Fa-f]{6}$/i.test(value)) {
  253. colorPicker.value = value;
  254. colorPreview.style.backgroundColor = value;
  255. state.bgColor = value;
  256. applyBackgroundColor(value);
  257. }
  258. });
  259.  
  260. resetButton.addEventListener('click', () => {
  261. applyBackgroundColor('');
  262. state.currentTheme = null;
  263. GM_setValue("currentTheme", null);
  264.  
  265. const themeSelect = document.getElementById('bg-theme-select');
  266. if (themeSelect) {
  267. themeSelect.selectedIndex = -1;
  268. }
  269.  
  270. const colorGroup = document.getElementById('custom-color-group');
  271. if (colorGroup) {
  272. colorGroup.style.display = 'none';
  273. }
  274.  
  275. const saveIndicator = document.getElementById('bg-theme-save-indicator');
  276. if (saveIndicator) {
  277. saveIndicator.textContent = "Reset! Using default background.";
  278. saveIndicator.classList.add('visible');
  279. setTimeout(() => {
  280. saveIndicator.textContent = "Saved!";
  281. saveIndicator.classList.remove('visible');
  282. }, 2000);
  283. }
  284. });
  285.  
  286. saveButton.addEventListener('click', () => {
  287. if (state.currentTheme === 'custom') {
  288. saveBackgroundColor(colorPicker.value, 'custom');
  289. } else if (state.currentTheme && CONFIG.themes[state.currentTheme]) {
  290. saveBackgroundColor(CONFIG.themes[state.currentTheme].color, state.currentTheme);
  291. }
  292.  
  293. saveIndicator.classList.add('visible');
  294. setTimeout(() => {
  295. saveIndicator.classList.remove('visible');
  296. }, 2000);
  297.  
  298. state.isPanelVisible = false;
  299. panel.classList.remove('visible');
  300. });
  301.  
  302. return panel;
  303. }
  304.  
  305. function addStyles() {
  306. const styleElement = document.createElement('style');
  307. styleElement.textContent = `.bg-theme-toggle{position:fixed;right:10px;top:100px;width:32px;height:32px;background-color:#333;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:9998;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:transform .2s}.bg-theme-toggle:hover{transform:scale(1.1)}.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,.3);color:#eee;font-family:Arial,sans-serif;font-size:13px;transition:right .3s ease}.bg-theme-panel.visible{right:10px}.bg-theme-header{display:flex;justify-content:space-between;align-items:center;padding:10px 15px;border-bottom:1px solid #444;font-weight:700}.bg-theme-close{cursor:pointer;font-size:20px;width:20px;height:20px;line-height:20px;text-align:center}.bg-theme-close:hover{color:#f44}.bg-theme-content{padding:15px}.bg-theme-group{margin-bottom:15px}.bg-theme-group label{display:block;margin-bottom:5px;font-weight:700;color:#ccc}.bg-theme-select{width:100%;padding:7px;background-color:#333;border:1px solid #444;color:#eee;border-radius:3px}.bg-theme-color-preview{height:25px;width:100%;margin-bottom:8px;border:1px solid #444;border-radius:3px}.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}.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:700;cursor:pointer;transition:background-color .2s}.bg-theme-button:hover{background-color:#555}.bg-theme-save{background-color:#4caf50}.bg-theme-save:hover{background-color:#3e8e41}.bg-theme-save-indicator{text-align:center;margin-top:10px;color:#4caf50;opacity:0;transition:opacity .3s;font-weight:700}.bg-theme-save-indicator.visible{opacity:1}.bg-theme-credit{margin-top:10px;text-align:center;font-size:11px;color:#777}@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}}`;
  308. document.head.appendChild(styleElement);
  309. }
  310.  
  311. function init() {
  312. if (state.currentTheme) {
  313. const currentColor = getCurrentThemeColor();
  314.  
  315. if (!applyBackgroundColor(currentColor)) {
  316. window.addEventListener("DOMContentLoaded", () => {
  317. applyBackgroundColor(getCurrentThemeColor());
  318. startObserving();
  319. });
  320. } else {
  321. startObserving();
  322. }
  323. } else {
  324. startObserving();
  325. }
  326.  
  327. const currentUrl = window.location.href;
  328. if (currentUrl.includes("/preferences.php") || currentUrl.includes("/profiles.php?XID=")) {
  329. if (document.readyState === "loading") {
  330. document.addEventListener("DOMContentLoaded", createUI);
  331. } else {
  332. createUI();
  333. }
  334. }
  335. }
  336.  
  337. init();
  338. })();