Torn Background Theme Editor

Change Torn's background with predefined themes and custom colors

当前为 2025-03-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Torn Background Theme Editor
  3. // @namespace http://tampermonkey.net/
  4. // @version 1
  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. // @license 420
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. "use strict";
  15.  
  16. // ===== Configuration =====
  17. const CONFIG = {
  18. defaultBgColor: "#000000",
  19. selectorContentContainer: ".content.responsive-sidebar-container.logged-in",
  20. themes: {
  21. pureBlack: {
  22. name: "Pure Black",
  23. color: "#000000"
  24. },
  25. darkGray: {
  26. name: "Dark Gray",
  27. color: "#121212"
  28. },
  29. midnightBlue: {
  30. name: "Midnight Blue",
  31. color: "#0A1929"
  32. },
  33. princess: {
  34. name: "Princess",
  35. color: "#c80e71"
  36. },
  37. custom: {
  38. name: "Custom",
  39. color: null // Will use user's custom color
  40. }
  41. }
  42. };
  43.  
  44. // ===== State Management =====
  45. const state = {
  46. bgColor: GM_getValue("bgColor", CONFIG.defaultBgColor),
  47. currentTheme: GM_getValue("currentTheme", "pureBlack"),
  48. isObserving: false,
  49. isPanelVisible: false
  50. };
  51.  
  52. // ===== DOM Manipulation =====
  53. function applyBackgroundColor(color) {
  54. const contentContainer = document.querySelector(CONFIG.selectorContentContainer);
  55. if (contentContainer) {
  56. contentContainer.style.backgroundColor = color;
  57. return true;
  58. }
  59. return false;
  60. }
  61.  
  62. function saveBackgroundColor(color, themeName = null) {
  63. // Validate color format
  64. if (!/^#[0-9A-F]{6}$/i.test(color)) {
  65. console.warn("Invalid color format:", color);
  66. return false;
  67. }
  68.  
  69. state.bgColor = color;
  70. GM_setValue("bgColor", color);
  71.  
  72. // Save theme if provided
  73. if (themeName) {
  74. state.currentTheme = themeName;
  75. GM_setValue("currentTheme", themeName);
  76. }
  77.  
  78. return applyBackgroundColor(color);
  79. }
  80.  
  81. // Get current background color based on theme
  82. function getCurrentThemeColor() {
  83. if (state.currentTheme === "custom") {
  84. return state.bgColor;
  85. } else if (CONFIG.themes[state.currentTheme]) {
  86. return CONFIG.themes[state.currentTheme].color;
  87. } else {
  88. return CONFIG.defaultBgColor;
  89. }
  90. }
  91.  
  92. // ===== Observer for Dynamic Content =====
  93. const observer = new MutationObserver((mutations) => {
  94. // Only reapply if we found actual DOM changes that might affect our target
  95. const shouldReapply = mutations.some(mutation =>
  96. mutation.type === 'childList' ||
  97. (mutation.type === 'attributes' &&
  98. (mutation.attributeName === 'style' ||
  99. mutation.attributeName === 'class'))
  100. );
  101.  
  102. if (shouldReapply) {
  103. applyBackgroundColor(getCurrentThemeColor());
  104. }
  105. });
  106.  
  107. function startObserving() {
  108. if (state.isObserving) return;
  109.  
  110. const contentContainer = document.querySelector(CONFIG.selectorContentContainer);
  111. if (contentContainer) {
  112. observer.observe(contentContainer, {
  113. attributes: true,
  114. childList: true,
  115. subtree: false // Only observe direct children
  116. });
  117. state.isObserving = true;
  118. }
  119. }
  120.  
  121. function stopObserving() {
  122. observer.disconnect();
  123. state.isObserving = false;
  124. }
  125.  
  126. // ===== UI Creation =====
  127. function createUI() {
  128. // Add stylesheet
  129. addStyles();
  130.  
  131. // Create the toggle button (small, with moon icon)
  132. const toggleButton = document.createElement("div");
  133. toggleButton.className = "bg-theme-toggle";
  134. 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>`;
  135. document.body.appendChild(toggleButton);
  136.  
  137. // Create the panel
  138. const panel = document.createElement("div");
  139. panel.id = "bg-theme-panel";
  140. panel.className = "bg-theme-panel";
  141.  
  142. // Add header
  143. let panelHTML = `
  144. <div class="bg-theme-header">
  145. <span>Background Theme</span>
  146. <span class="bg-theme-close">×</span>
  147. </div>
  148. <div class="bg-theme-content">
  149. <div class="bg-theme-group">
  150. <label for="bg-theme-select">Theme:</label>
  151. <select id="bg-theme-select" class="bg-theme-select">`;
  152.  
  153. // Add theme options
  154. Object.keys(CONFIG.themes).forEach(themeKey => {
  155. const selected = themeKey === state.currentTheme ? 'selected' : '';
  156. panelHTML += `<option value="${themeKey}" ${selected}>${CONFIG.themes[themeKey].name}</option>`;
  157. });
  158.  
  159. // Add custom color section and buttons
  160. panelHTML += `
  161. </select>
  162. </div>
  163.  
  164. <div id="custom-color-group" class="bg-theme-group" style="display: ${state.currentTheme === 'custom' ? 'block' : 'none'}">
  165. <label for="bg-theme-color">Custom Color:</label>
  166. <div class="bg-theme-color-preview" id="color-preview"></div>
  167. <div class="bg-theme-color-inputs">
  168. <input type="color" id="bg-theme-color-picker" value="${state.bgColor}">
  169. <input type="text" id="bg-theme-hex" value="${state.bgColor}" placeholder="#RRGGBB">
  170. </div>
  171. </div>
  172.  
  173. <div class="bg-theme-buttons">
  174. <button id="bg-theme-reset" class="bg-theme-button">Reset</button>
  175. <button id="bg-theme-save" class="bg-theme-button bg-theme-save">Save</button>
  176. </div>
  177.  
  178. <div id="bg-theme-save-indicator" class="bg-theme-save-indicator">Saved!</div>
  179. <div class="bg-theme-credit"><a href="https://greasyfork.org/en/users/1431907-theeeunknown" target="_blank">Created by TR0LL [2561502]</a></div> `;
  180.  
  181. panel.innerHTML = panelHTML;
  182. document.body.appendChild(panel);
  183.  
  184. // Get references to elements
  185. const themeSelect = document.getElementById('bg-theme-select');
  186. const colorGroup = document.getElementById('custom-color-group');
  187. const colorPreview = document.getElementById('color-preview');
  188. const colorPicker = document.getElementById('bg-theme-color-picker');
  189. const hexInput = document.getElementById('bg-theme-hex');
  190. const resetButton = document.getElementById('bg-theme-reset');
  191. const saveButton = document.getElementById('bg-theme-save');
  192. const saveIndicator = document.getElementById('bg-theme-save-indicator');
  193.  
  194. // Set initial color preview
  195. colorPreview.style.backgroundColor = state.bgColor;
  196.  
  197. // Event Listeners
  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. // Theme select change
  209. themeSelect.addEventListener('change', function() {
  210. const selectedTheme = this.value;
  211. state.currentTheme = selectedTheme;
  212.  
  213. // Show/hide custom color controls
  214. colorGroup.style.display = selectedTheme === 'custom' ? 'block' : 'none';
  215.  
  216. // Apply theme color immediately
  217. if (selectedTheme === 'custom') {
  218. applyBackgroundColor(state.bgColor);
  219. } else {
  220. const themeColor = CONFIG.themes[selectedTheme].color;
  221. colorPicker.value = themeColor;
  222. hexInput.value = themeColor;
  223. colorPreview.style.backgroundColor = themeColor;
  224. applyBackgroundColor(themeColor);
  225. }
  226. });
  227.  
  228. // Color picker change
  229. colorPicker.addEventListener('input', function() {
  230. const newColor = this.value;
  231. state.bgColor = newColor;
  232. hexInput.value = newColor;
  233. colorPreview.style.backgroundColor = newColor;
  234. applyBackgroundColor(newColor);
  235. });
  236.  
  237. // Hex input change
  238. hexInput.addEventListener('input', function() {
  239. let value = this.value;
  240.  
  241. // Auto-add hash if missing
  242. if (!value.startsWith('#') && value.length > 0) {
  243. value = '#' + value;
  244. this.value = value;
  245. }
  246.  
  247. // For complete valid hex codes, update immediately
  248. if (/^#[0-9A-Fa-f]{6}$/i.test(value)) {
  249. colorPicker.value = value;
  250. colorPreview.style.backgroundColor = value;
  251. state.bgColor = value;
  252. applyBackgroundColor(value);
  253. }
  254. });
  255.  
  256. // Reset button
  257. resetButton.addEventListener('click', () => {
  258. // Reset to Pure Black theme
  259. themeSelect.value = 'pureBlack';
  260. state.currentTheme = 'pureBlack';
  261. colorGroup.style.display = 'none';
  262.  
  263. const pureBlackColor = CONFIG.themes.pureBlack.color;
  264. colorPicker.value = pureBlackColor;
  265. hexInput.value = pureBlackColor;
  266. colorPreview.style.backgroundColor = pureBlackColor;
  267. applyBackgroundColor(pureBlackColor);
  268. });
  269.  
  270. // Save button
  271. saveButton.addEventListener('click', () => {
  272. if (state.currentTheme === 'custom') {
  273. saveBackgroundColor(colorPicker.value, 'custom');
  274. } else {
  275. saveBackgroundColor(CONFIG.themes[state.currentTheme].color, state.currentTheme);
  276. }
  277.  
  278. // Show save confirmation
  279. saveIndicator.classList.add('visible');
  280. setTimeout(() => {
  281. saveIndicator.classList.remove('visible');
  282. }, 2000);
  283.  
  284. // Close panel
  285. state.isPanelVisible = false;
  286. panel.classList.remove('visible');
  287. });
  288.  
  289. return panel;
  290. }
  291.  
  292. // Show save indicator
  293. function showSaveIndicator() {
  294. const indicator = document.getElementById('bg-theme-save-indicator');
  295. if (indicator) {
  296. indicator.classList.add('visible');
  297. setTimeout(() => {
  298. indicator.classList.remove('visible');
  299. }, 2000);
  300. }
  301. }
  302.  
  303. // Add CSS styles
  304. function addStyles() {
  305. const styleElement = document.createElement('style');
  306. styleElement.textContent = `
  307. /* Toggle Button */
  308. .bg-theme-toggle {
  309. position: fixed;
  310. right: 10px;
  311. top: 100px;
  312. width: 32px;
  313. height: 32px;
  314. background-color: #333;
  315. border-radius: 50%;
  316. color: white;
  317. display: flex;
  318. align-items: center;
  319. justify-content: center;
  320. cursor: pointer;
  321. z-index: 9998;
  322. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
  323. transition: transform 0.2s;
  324. }
  325.  
  326. .bg-theme-toggle:hover {
  327. transform: scale(1.1);
  328. }
  329.  
  330. /* Panel */
  331. .bg-theme-panel {
  332. position: fixed;
  333. right: -250px;
  334. top: 100px;
  335. width: 220px;
  336. background-color: #222;
  337. border-radius: 5px;
  338. z-index: 9999;
  339. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
  340. color: #eee;
  341. font-family: Arial, sans-serif;
  342. font-size: 13px;
  343. transition: right 0.3s ease;
  344. }
  345.  
  346. .bg-theme-panel.visible {
  347. right: 10px;
  348. }
  349.  
  350. /* Header */
  351. .bg-theme-header {
  352. display: flex;
  353. justify-content: space-between;
  354. align-items: center;
  355. padding: 10px 15px;
  356. border-bottom: 1px solid #444;
  357. font-weight: bold;
  358. }
  359.  
  360. .bg-theme-close {
  361. cursor: pointer;
  362. font-size: 20px;
  363. width: 20px;
  364. height: 20px;
  365. line-height: 20px;
  366. text-align: center;
  367. }
  368.  
  369. .bg-theme-close:hover {
  370. color: #ff4444;
  371. }
  372.  
  373. /* Content */
  374. .bg-theme-content {
  375. padding: 15px;
  376. }
  377.  
  378. .bg-theme-group {
  379. margin-bottom: 15px;
  380. }
  381.  
  382. .bg-theme-group label {
  383. display: block;
  384. margin-bottom: 5px;
  385. font-weight: bold;
  386. color: #ccc;
  387. }
  388.  
  389. .bg-theme-select {
  390. width: 100%;
  391. padding: 7px;
  392. background-color: #333;
  393. border: 1px solid #444;
  394. color: #eee;
  395. border-radius: 3px;
  396. }
  397.  
  398. /* Color Preview */
  399. .bg-theme-color-preview {
  400. height: 25px;
  401. width: 100%;
  402. margin-bottom: 8px;
  403. border: 1px solid #444;
  404. border-radius: 3px;
  405. }
  406.  
  407. /* Color Inputs */
  408. .bg-theme-color-inputs {
  409. display: flex;
  410. gap: 8px;
  411. }
  412.  
  413. #bg-theme-color-picker {
  414. width: 40px;
  415. height: 30px;
  416. padding: 0;
  417. border: 1px solid #444;
  418. cursor: pointer;
  419. }
  420.  
  421. #bg-theme-hex {
  422. flex: 1;
  423. padding: 6px 8px;
  424. background-color: #333;
  425. border: 1px solid #444;
  426. color: #eee;
  427. border-radius: 3px;
  428. font-family: monospace;
  429. }
  430.  
  431. /* Buttons */
  432. .bg-theme-buttons {
  433. display: flex;
  434. gap: 10px;
  435. margin-top: 15px;
  436. }
  437.  
  438. .bg-theme-button {
  439. flex: 1;
  440. padding: 8px 0;
  441. background-color: #444;
  442. border: none;
  443. border-radius: 3px;
  444. color: #eee;
  445. font-weight: bold;
  446. cursor: pointer;
  447. transition: background-color 0.2s;
  448. }
  449.  
  450. .bg-theme-button:hover {
  451. background-color: #555;
  452. }
  453.  
  454. .bg-theme-save {
  455. background-color: #4CAF50;
  456. }
  457.  
  458. .bg-theme-save:hover {
  459. background-color: #3e8e41;
  460. }
  461.  
  462. /* Save Indicator */
  463. .bg-theme-save-indicator {
  464. text-align: center;
  465. margin-top: 10px;
  466. color: #4CAF50;
  467. opacity: 0;
  468. transition: opacity 0.3s;
  469. font-weight: bold;
  470. }
  471.  
  472. .bg-theme-save-indicator.visible {
  473. opacity: 1;
  474. }
  475.  
  476. /* Credit */
  477. .bg-theme-credit {
  478. margin-top: 10px;
  479. text-align: center;
  480. font-size: 11px;
  481. color: #777;
  482. }
  483.  
  484. /* Mobile Responsiveness */
  485. @media (max-width: 768px) {
  486. .bg-theme-toggle {
  487. width: 28px;
  488. height: 28px;
  489. right: 5px;
  490. top: 70px;
  491. }
  492.  
  493. .bg-theme-panel {
  494. width: 190px;
  495. }
  496.  
  497. .bg-theme-panel.visible {
  498. right: 5px;
  499. }
  500. }
  501. `;
  502. document.head.appendChild(styleElement);
  503. }
  504.  
  505. // ===== Initialization =====
  506. function init() {
  507. // Set initial color based on saved theme
  508. const currentColor = getCurrentThemeColor();
  509.  
  510. // Apply saved background color to all pages
  511. if (!applyBackgroundColor(currentColor)) {
  512. // If element not found immediately, wait for DOM to be ready
  513. window.addEventListener("DOMContentLoaded", () => {
  514. applyBackgroundColor(currentColor);
  515. startObserving();
  516. });
  517. } else {
  518. startObserving();
  519. }
  520.  
  521. // Only create UI on preferences page
  522. const currentUrl = window.location.href;
  523. if (currentUrl.includes("/preferences.php")) {
  524. // Wait for DOM to be ready
  525. if (document.readyState === "loading") {
  526. document.addEventListener("DOMContentLoaded", createUI);
  527. } else {
  528. createUI();
  529. }
  530. }
  531. }
  532.  
  533. // Start the script
  534. init();
  535. })();