Torn Background Theme Editor

Change Torn's background with predefined themes and custom colors

Från och med 2025-03-19. Se den senaste versionen.

// ==UserScript==
// @name         Torn Background Theme Editor
// @namespace    http://tampermonkey.net/
// @version      1
// @description  Change Torn's background with predefined themes and custom colors
// @author       TR0LL [2561502]
// @match        https://www.torn.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      420
// ==/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"),
    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;
    }
  }

  // ===== 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 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-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"><a href="https://greasyfork.org/en/users/1431907-theeeunknown" target="_blank">Created by TR0LL [2561502]</a></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 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);
      }
    });

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

    // 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;
      }

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

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