Torn.com Background Theme Editor

Change Torn's background with predefined themes and custom colors, with dark mode toggle

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();