Visited Links Enhanced - Size Optimized

Size-optimized userscript for visited links. Reduced file size with full functionality.

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

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Visited Links Enhanced - Size Optimized
// @namespace    com.userscript.visited-links-enhanced
// @version      0.6.9
// @description  Size-optimized userscript for visited links. Reduced file size with full functionality.
// @author       Enhanced by AI Assistant ft. Hongmd
// @license      MIT
// @homepageURL  https://github.com/hongmd/userscript-improved
// @supportURL   https://github.com/hongmd/userscript-improved/issues
// @match        http://*/*
// @match        https://*/*
// @noframes
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @copyright    2025, Enhanced by AI Assistant ft. Hongmd
// ==/UserScript==

(function () {
  "use strict";

  const MEM_OPT = {
    REGEX: {
      COLOR_HEX: /^#([0-9a-f]{3}){1,2}$/i,
      COLOR_RGB: /^rgb\(\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*\)$/i,
      COLOR_RGBA: /^rgba\(\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\s*,\s*(?:1(?:\.0*)?|0(?:\.\d*)?)\s*\)$/i,
      COLOR_NAMED: /^(red|blue|green|yellow|black|white|gray|orange|purple|pink|brown)$/i,
      URL_DOMAIN: /^https?:\/\/([^\/\?#]+)/i,
    },
    COLORS: [
      ["#93c5fd", "Pastel Blue"], ["#fca5a5", "Pastel Red"], ["#86efac", "Pastel Green"], ["#fed7aa", "Pastel Orange"],
      ["#f97316", "Vibrant Orange"], ["#c4b5fd", "Pastel Purple"], ["#f9a8d4", "Pastel Pink"], ["#7dd3fc", "Pastel Sky Blue"],
      ["#bef264", "Pastel Lime"], ["#fde047", "Pastel Yellow"], ["#fb7185", "Pastel Rose"], ["#a78bfa", "Pastel Violet"],
      ["#34d399", "Pastel Emerald"], ["#dc2626", "Bold Red"], ["#2563eb", "Bold Blue"], ["#059669", "Bold Green"],
      ["#7c3aed", "Bold Purple"], ["#db2777", "Bold Pink"], ["#ea580c", "Bold Orange"], ["#0891b2", "Bold Cyan"],
      ["#65a30d", "Bold Lime"], ["#ca8a04", "Bold Yellow"], ["#be123c", "Bold Rose"], ["#000000", "Black"],
      ["#ffffff", "White"], ["#6b7280", "Gray"], ["#ef4444", "Pure Red"], ["#3b82f6", "Pure Blue"],
      ["#10b981", "Pure Green"], ["#8b5cf6", "Pure Purple"], ["#f59e0b", "Pure Orange"], ["#eab308", "Pure Yellow"]
    ],
    CFG: {
      KEYS: { C: "visited_color", N: "script_enabled" },
      DEF: { C: "#f97316", N: true },
      ID: "visited-lite-enhanced-style",
      CSS: "a:visited,a:visited *{color:%C%!important}",
    }
  };

  

  const U = {
    V: (c) => {
      if (!c || typeof c !== 'string') return false;
      const t = c.trim();
      if (t.length < 3) return false;
      return MEM_OPT.REGEX.COLOR_HEX.test(t) ||
        MEM_OPT.REGEX.COLOR_RGB.test(t) ||
        MEM_OPT.REGEX.COLOR_RGBA.test(t) ||
        MEM_OPT.REGEX.COLOR_NAMED.test(t);
    },
    G: (u) => {
      try {
        return new URL(u).hostname;
      } catch {
        const m = u.match(MEM_OPT.REGEX.URL_DOMAIN);
        return m ? m[1] : "";
      }
    }
  };

  const CM = {
    _c: new Map(),
    _p: "visited_links_enhanced_",
    G: function (k) {
      if (this._c.has(k)) return this._c.get(k);
      const sk = MEM_OPT.CFG.KEYS[k];
      const dv = MEM_OPT.CFG.DEF[k];
      let v = dv;
      try {
        if (typeof GM_getValue !== 'undefined') {
          v = GM_getValue(sk, dv);
        } else {
          const s = localStorage.getItem(this._p + sk);
          v = s ? JSON.parse(s) : dv;
        }
      } catch (e) {
        try {
          const s = localStorage.getItem(this._p + sk);
          v = s ? JSON.parse(s) : dv;
        } catch { }
      }
      this._c.set(k, v);
      return v;
    },
    S: function (k, v) {
      this._c.set(k, v);
      const sk = MEM_OPT.CFG.KEYS[k];
      try {
        if (typeof GM_setValue !== 'undefined') {
          GM_setValue(sk, v);
          return true;
        }
      } catch (e) {
      }
      try {
        localStorage.setItem(this._p + sk, JSON.stringify(v));
        return true;
      } catch { }
      return false;
    },
    // removed CM.I (unused) and CM.C (no external cache)
  };

  const SM = {
    _e: null,
    _l: "",
    I: function () { this.E(); },
    E: function () {
      const e = document.getElementById(MEM_OPT.CFG.ID);
      if (e) e.remove();

      this._e = Object.assign(document.createElement("style"), {
        id: MEM_OPT.CFG.ID,
        type: "text/css"
      });

      

      (document.head ?? document.documentElement)?.appendChild?.(this._e);
      return this._e;
    },
    U: function () {
      const c = CM.G("C");
      if (!U.V(c)) return;
      const css = MEM_OPT.CFG.CSS.replace("%C%", c);
      if (this._l === css) return;
      if (!this._e?.isConnected) this.E();
      const animations = `
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
      `;
      this._e.textContent = animations + "\n" + css;
      this._l = css;
    },
    R: function () {
      if (this._e && this._l) {
        this._e.textContent = "";
        this._l = "";
      }
    }
  };

  const MM = {
    _co: null,
    I: function () {
      if (typeof GM_registerMenuCommand === 'undefined') return;
      try {
        GM_registerMenuCommand("🎨 Choose Color", this.CC.bind(this));
        GM_registerMenuCommand("⚙️ Toggle Script", this.TS.bind(this));
      } catch { }
    },
    TS: function () {
      const ns = !CM.G("N");
      CM.S("N", ns);
      ns ? SM.U() : SM.R();
      const msg = `Visited Links Enhanced: ${ns ? 'Enabled' : 'Disabled'}`;
      this.showNotification(msg);
    },
    
    CC: function () {
      // Create visual color picker interface
      const colorGrid = this.createColorGrid();
      const modal = this.createColorModal(colorGrid);

      // Display modal
      document.body.appendChild(modal);

      // Focus on modal
      modal.style.display = 'flex';
      modal.focus();
    },

    createColorGrid: function () {
      const container = document.createElement('div');
      container.style.cssText = `
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(75px, 1fr));
        gap: 12px;
        max-width: 650px;
        margin: 25px auto;
        padding: 0 10px;
      `;

      MEM_OPT.COLORS.forEach((color, index) => {
        const colorBox = document.createElement('div');
        colorBox.style.cssText = `
          width: 75px;
          height: 75px;
          background-color: ${color[0]};
          border: 3px solid #e0e0e0;
          border-radius: 12px;
          cursor: pointer;
          display: flex;
          align-items: center;
          justify-content: center;
          font-size: 11px;
          font-weight: 600;
          color: ${this.getContrastColor(color[0])};
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
          position: relative;
          box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        `;

        // Hover effects
        colorBox.onmouseover = () => {
          colorBox.style.transform = 'scale(1.08) rotate(2deg)';
          colorBox.style.boxShadow = '0 8px 25px rgba(0,0,0,0.25)';
          colorBox.style.borderColor = '#007bff';
          colorBox.style.zIndex = '10';
        };
        colorBox.onmouseout = () => {
          colorBox.style.transform = 'scale(1) rotate(0deg)';
          colorBox.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
          colorBox.style.borderColor = '#e0e0e0';
          colorBox.style.zIndex = '1';
        };

        // Click handler
        colorBox.onclick = () => {
          CM.S("C", color[0]);
          SM.U();
          this.showNotification(`Selected: ${color[1]} (${color[0]})`);
          const modal = document.querySelector('.color-modal');
          if (modal && modal.parentNode) {
            modal.parentNode.removeChild(modal);
          }
        };

        // Tooltip
        colorBox.title = `${color[1]} - Click to select`;

        container.appendChild(colorBox);
      });

      return container;
    },

    createColorModal: function (colorGrid) {
      const modal = document.createElement('div');
      modal.className = 'color-modal';
      modal.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background: rgba(0, 0, 0, 0.75);
        backdrop-filter: blur(8px);
        display: none;
        z-index: 10000;
        align-items: center;
        justify-content: center;
        padding: 20px;
        box-sizing: border-box;
      `;

      const content = document.createElement('div');
      content.style.cssText = `
        background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
        padding: 30px;
        border-radius: 20px;
        box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        max-width: 700px;
        max-height: 85vh;
        overflow-y: auto;
        position: relative;
        border: 1px solid rgba(255,255,255,0.2);
      `;

      const title = document.createElement('h3');
      title.textContent = '🎨 Choose Color for Visited Links';
      title.style.cssText = `
        margin: 0 0 25px 0;
        color: #2c3e50;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: 24px;
        font-weight: 600;
        text-align: center;
        text-shadow: 0 1px 2px rgba(0,0,0,0.1);
      `;

      // Default color button
      const defaultWrap = document.createElement('div');
      defaultWrap.style.cssText = `
        display: flex; justify-content: center; margin: 0 0 15px 0;
      `;
      const defaultBtn = document.createElement('button');
      defaultBtn.textContent = 'Use Default Color';
      defaultBtn.style.cssText = `
        padding: 10px 16px;
        background: #f1f3f5;
        color: #212529;
        border: 1px solid #dee2e6;
        border-radius: 8px;
        cursor: pointer;
        font-size: 14px;
        font-weight: 600;
      `;
      defaultBtn.onclick = () => {
        CM.S("C", MEM_OPT.CFG.DEF.C);
        SM.U();
        this.showNotification(`Default color applied: ${MEM_OPT.CFG.DEF.C}`);
        if (modal && modal.parentNode) modal.parentNode.removeChild(modal);
      };
      defaultWrap.appendChild(defaultBtn);

      const closeBtn = document.createElement('button');
      closeBtn.innerHTML = '×';
      closeBtn.style.cssText = `
        position: absolute;
        top: 15px;
        right: 20px;
        background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
        color: white;
        border: none;
        border-radius: 50%;
        width: 35px;
        height: 35px;
        cursor: pointer;
        font-size: 20px;
        font-weight: bold;
        line-height: 1;
        transition: all 0.3s ease;
        box-shadow: 0 4px 12px rgba(238, 90, 82, 0.3);
      `;
      closeBtn.onmouseover = () => {
        closeBtn.style.transform = 'scale(1.1)';
        closeBtn.style.boxShadow = '0 6px 20px rgba(238, 90, 82, 0.4)';
      };
      closeBtn.onmouseout = () => {
        closeBtn.style.transform = 'scale(1)';
        closeBtn.style.boxShadow = '0 4px 12px rgba(238, 90, 82, 0.3)';
      };
      closeBtn.onclick = () => {
        if (modal && modal.parentNode) {
          modal.parentNode.removeChild(modal);
        }
      };

      const customColorDiv = document.createElement('div');
      customColorDiv.style.cssText = `
        margin-top: 25px;
        padding-top: 20px;
        border-top: 2px solid #e9ecef;
        text-align: center;
        background: rgba(248, 249, 250, 0.5);
        border-radius: 12px;
        padding: 20px;
      `;

      const customLabel = document.createElement('div');
      customLabel.textContent = 'Or enter a custom color code:';
      customLabel.style.cssText = `
        margin-bottom: 15px;
        color: #495057;
        font-size: 16px;
        font-weight: 500;
      `;

      const inputContainer = document.createElement('div');
      inputContainer.style.cssText = `
        display: flex;
        gap: 10px;
        justify-content: center;
        align-items: center;
        flex-wrap: wrap;
      `;

      const customInput = document.createElement('input');
      customInput.type = 'text';
      customInput.placeholder = '#ff0000 or red';
      customInput.style.cssText = `
        padding: 12px 16px;
        border: 2px solid #dee2e6;
        border-radius: 8px;
        width: 220px;
        font-size: 16px;
        transition: all 0.3s ease;
        outline: none;
      `;
      customInput.onfocus = () => {
        customInput.style.borderColor = '#007bff';
        customInput.style.boxShadow = '0 0 0 3px rgba(0, 123, 255, 0.1)';
      };
      customInput.onblur = () => {
        customInput.style.borderColor = '#dee2e6';
        customInput.style.boxShadow = 'none';
      };

      const customBtn = document.createElement('button');
      customBtn.textContent = 'Apply';
      customBtn.style.cssText = `
        padding: 12px 24px;
        background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
        color: white;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        font-size: 16px;
        font-weight: 600;
        transition: all 0.3s ease;
        box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
      `;
      customBtn.onmouseover = () => {
        customBtn.style.transform = 'translateY(-2px)';
        customBtn.style.boxShadow = '0 6px 20px rgba(40, 167, 69, 0.4)';
      };
      customBtn.onmouseout = () => {
        customBtn.style.transform = 'translateY(0)';
        customBtn.style.boxShadow = '0 4px 12px rgba(40, 167, 69, 0.3)';
      };
      customBtn.onclick = () => {
        const customColor = customInput.value.trim();
        if (customColor && U.V(customColor)) {
          CM.S("C", customColor);
          SM.U();
          this.showNotification(`Custom color applied: ${customColor}`);
          if (modal && modal.parentNode) {
            modal.parentNode.removeChild(modal);
          }
        } else {
          this.showError('Invalid color code! Please try again.');
        }
      };

      inputContainer.appendChild(customInput);
      inputContainer.appendChild(customBtn);

      customColorDiv.appendChild(customLabel);
      customColorDiv.appendChild(inputContainer);

      content.appendChild(closeBtn);
      content.appendChild(title);
      content.appendChild(defaultWrap);
      content.appendChild(colorGrid);
      content.appendChild(customColorDiv);

      modal.appendChild(content);

      // Enhanced close handlers
      modal.onclick = (e) => {
        if (e.target === modal && modal.parentNode) {
          modal.parentNode.removeChild(modal);
        }
      };

      modal.onkeydown = (e) => {
        if (e.key === 'Escape' && modal.parentNode) {
          modal.parentNode.removeChild(modal);
        }
      };

      return modal;
    },

    getContrastColor: function (hexColor) {
      const r = parseInt(hexColor.slice(1, 3), 16);
      const g = parseInt(hexColor.slice(3, 5), 16);
      const b = parseInt(hexColor.slice(5, 7), 16);
      const brightness = (r * 299 + g * 587 + b * 114) / 1000;
      return brightness > 128 ? '#000000' : '#ffffff';
    },

    showNotification: function (message) {
      const notification = document.createElement('div');
      notification.textContent = '✅ ' + message;
      notification.style.cssText = `
        position: fixed;
        top: 20px;
        right: 20px;
        background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
        color: white;
        padding: 15px 20px;
        border-radius: 10px;
        box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: 14px;
        font-weight: 500;
        z-index: 10001;
        animation: slideIn 0.3s ease-out;
      `;

      document.body.appendChild(notification);

      setTimeout(() => {
        notification.style.animation = 'slideOut 0.3s ease-in';
        setTimeout(() => {
          if (notification.parentNode) {
            notification.parentNode.removeChild(notification);
          }
        }, 300);
      }, 3000);
    },

    showError: function (message) {
      const error = document.createElement('div');
      error.textContent = '❌ ' + message;
      error.style.cssText = `
        position: fixed;
        top: 20px;
        right: 20px;
        background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
        color: white;
        padding: 15px 20px;
        border-radius: 10px;
        box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: 14px;
        font-weight: 500;
        z-index: 10001;
        animation: slideIn 0.3s ease-out;
      `;

      document.body.appendChild(error);

      setTimeout(() => {
        error.style.animation = 'slideOut 0.3s ease-in';
        setTimeout(() => {
          if (error.parentNode) {
            error.parentNode.removeChild(error);
          }
        }, 300);
      }, 3000);
    }
  };

  const A = {
    I: function () {
      SM.I();
      MM.I();
      this.CAS();
    },
    CAS: function () {
      const e = CM.G("N");
      if (e) {
        SM.U();
      } else {
        SM.R();
      }
    },
    
  };

  function I() {
    if (document.documentElement) {
      A.I();
    } else {
      setTimeout(I, 50);
    }
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", I);
  } else {
    I();
  }

  

})();