Greasy Fork 还支持 简体中文。

Torn.com Background Theme Editor

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

目前為 2025-03-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();