F95Zone Ultimate Enhancer

All-in-one powerhouse for F95Zone: Advanced thread highlighting & overlays, customizable tags/colors, wide layouts, auto latest refresh + notifications, seamless masked link skipping (direct on-click zap to hosts), image retry fixes, and more!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         F95Zone Ultimate Enhancer
// @version      4.2.0
// @icon         https://external-content.duckduckgo.com/iu/?u=https://f95zone.to/data/avatars/l/1963/1963870.jpg?1744969685
// @namespace    https://f95zone.to/threads/f95zone-latest.250836/
// @homepage     https://f95zone.to/threads/f95zone-latest.250836/
// @homepageURL  https://f95zone.to/threads/f95zone-latest.250836/
// @supportURL   https://f95zone.to/threads/forum-latest.250836/
// @author       X Death (creator and maintainer)
// @author       Edexal (enhancements)
// @match        https://f95zone.to/sam/latest_alpha/*
// @match        https://f95zone.to/threads/*
// @match        https://f95zone.to/masked/*
// @grant        GM.setValue
// @grant        GM.getValues
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @run-at       document-idle
// @license      GPL-3.0-or-later
// @description  All-in-one powerhouse for F95Zone: Advanced thread highlighting & overlays, customizable tags/colors, wide layouts, auto latest refresh + notifications, seamless masked link skipping (direct on-click zap to hosts), image retry fixes, and more!
// ==/UserScript==
// ------------------------------------------------------------
// Built on 2026-01-07 11:21:35 UTC — AUTO-GENERATED, edit from /src and rebuild
// ------------------------------------------------------------

(() => {
  // src/constants.js
  var debug = false;
  var state = {
    modalInjected: false,
    tagsUpdated: false,
    globalSettingsRendered: false,
    colorRendered: false,
    overlayRendered: false,
    threadSettingsRendered: false,
    isThread: false,
    isLatest: false,
    isImgRetryInjected: false,
    firstLoad: true,
    isMaskedLink: false,
    isMaskedLinkApplied: false,
    isProcessingTiles: false,
    isCrossTabSyncInitialized: false
  };
  var defaultColors = {
    completed: "#388e3c",
    onhold: "#1976d2",
    abandoned: "#c9a300",
    highVersion: "#2e7d32",
    invalidVersion: "#a38400",
    tileInfo: "#9398a0",
    tileHeader: "#d9d9d9",
    preferred: "#7b1fa2",
    preferredText: "#ffffff",
    excluded: "#b71c1c",
    excludedText: "#ffffff",
    neutral: "#37383a",
    neutralText: "#9398a0"
  };
  var defaultOverlaySettings = {
    completed: true,
    onhold: true,
    abandoned: true,
    highVersion: true,
    invalidVersion: true,
    preferred: true,
    excluded: true,
    overlayText: true
  };
  var defaultThreadSetting = {
    neutral: true,
    preferred: true,
    preferredShadow: true,
    excluded: true,
    excludedShadow: true,
    isWide: false,
    imgRetry: false,
    skipMaskedLink: true,
    collapseSignature: false,
    threadOverlayToggle: true
  };
  var defaultLatestSettings = {
    autoRefresh: false,
    webNotif: false,
    minVersion: 0.5,
    wideLatest: false,
    denseLatestGrid: false,
    latestOverlayToggle: true,
    // ── new horny addition ──
    goldenFreshGlow: true,
    // main toggle
    goldenMaxViews: 5e3,
    // threshold
    goldenMaxAgeMinutes: 60,
    // strict <1h or relax to 120/180 later
    goldenMaxBleedPx: 6,
    // keep it tight
    goldenMinOpacity: 0.15
    // so it never fully disappears
  };
  var metrics = {
    retried: 0,
    succeeded: 0,
    failed: 0,
    avgCache: 0,
    highest: 0,
    lowest: Infinity,
    mean: 0
  };
  var defaultGlobalSettings = {
    configVisibility: true,
    enableCrossTabSync: false
  };
  var config = {
    tags: [],
    preferredTags: [],
    excludedTags: [],
    color: [],
    overlaySettings: [],
    threadSettings: [],
    globalSettings: [],
    configVisibility: true,
    minVersion: 0.5,
    latestSettings: [],
    metrics
  };
  var STATUS = Object.freeze({
    PREFERRED: "preferred",
    EXCLUDED: "excluded",
    NEUTRAL: "neutral",
    PREFFERED_SHADOW: "preffered-shadow",
    EXCLUDED_SHADOW: "excluded-shadow"
  });
  var crossTabKeys = {
    color: true,
    overlaySettings: true,
    threadSettings: true,
    latestSettings: true
  };

  // src/storage/save.js
  async function saveConfigKeys(data) {
    const promises = Object.entries(data).map(([key, value]) => GM.setValue(key, value));
    await Promise.all(promises);
    if (debug) console.log("Config saved (keys)", data);
  }
  async function loadData() {
    let parsed = {};
    try {
      parsed = await GM.getValues(Object.keys(config)) ?? {};
    } catch (e) {
      debug && console.warn("loadData error:", e);
      parsed = {};
    }
    const mergeWithDefault = (saved, defaultObj) => {
      if (!saved || typeof saved !== "object") return { ...defaultObj };
      const result2 = { ...defaultObj };
      Object.keys(saved).forEach((key) => {
        if (key in result2) {
          result2[key] = saved[key];
        }
      });
      return result2;
    };
    const result = {
      tags: Array.isArray(parsed.tags) ? parsed.tags : [],
      preferredTags: Array.isArray(parsed.preferredTags) ? parsed.preferredTags : [],
      excludedTags: Array.isArray(parsed.excludedTags) ? parsed.excludedTags : [],
      color: mergeWithDefault(parsed.color, defaultColors),
      overlaySettings: mergeWithDefault(parsed.overlaySettings, defaultOverlaySettings),
      threadSettings: mergeWithDefault(parsed.threadSettings, defaultThreadSetting),
      latestSettings: mergeWithDefault(parsed.latestSettings, defaultLatestSettings),
      globalSettings: mergeWithDefault(parsed.globalSettings, defaultGlobalSettings),
      // Backward compat: old flat minVersion → migrate to latestSettings
      // (safe even if latestSettings already exists)
      ...typeof parsed.minVersion === "number" && !parsed.latestSettings?.minVersion && {
        latestSettings: {
          ...parsed.latestSettings || defaultLatestSettings,
          minVersion: parsed.minVersion
        }
      },
      metrics: mergeWithDefault(parsed.metrics, metrics)
    };
    if (!result.latestSettings.minVersion && result.latestSettings.minVersion !== 0) {
      result.latestSettings.minVersion = 0.5;
    }
    debug && console.log("loadData result:", result);
    return result;
  }

  // src/storage/migrate.js
  function migrateLatestSettings() {
    let migrated = false;
    if (typeof config.minVersion !== "undefined" && !config.latestSettings) {
      config.latestSettings = {
        autoRefresh: false,
        webNotif: false,
        minVersion: Number(config.minVersion) || 0.5
      };
      delete config.minVersion;
      migrated = true;
    }
    if (config.latestSettings && typeof config.latestSettings.minVersion === "undefined") {
      config.latestSettings.minVersion = 0.5;
      migrated = true;
    }
    const defaults = {
      autoRefresh: false,
      webNotif: false,
      minVersion: 0.5
    };
    let needsSave = false;
    Object.keys(defaults).forEach((key) => {
      if (!(key in config.latestSettings)) {
        config.latestSettings[key] = defaults[key];
        needsSave = true;
      }
    });
    if (migrated || needsSave) {
      saveConfigKeys({ latestSettings: config.latestSettings });
      debug && console.log("Latest settings migrated successfully");
    }
  }

  // src/template/ui.html?raw
  var ui_default = '<div id="toast-container"></div>\r\n\r\n<div class="modal-content">\r\n  <h2 style="text-align: center">CONFIG</h2>\r\n\r\n  <!-- General -->\r\n  <div class="modal-settings-spacing">\r\n    <details class="config-list-details">\r\n      <summary>General</summary>\r\n      <div class="settings-wrapper">\r\n        <div id="global-settings-container"></div>\r\n      </div>\r\n    </details>\r\n  </div>\r\n  <hr class="thick-line" />\r\n  <!-- Latest page settings -->\r\n  <div class="modal-settings-spacing">\r\n    <details class="config-list-details">\r\n      <summary>Latest page settings</summary>\r\n      <div class="settings-wrapper">\r\n        <div id="latest-settings-warning"></div>\r\n        <div id="latest-settings-container"></div>\r\n        <div id="overlay-settings-container"></div>\r\n      </div>\r\n    </details>\r\n  </div>\r\n  <hr class="thick-line" />\r\n  <!-- Thread settings -->\r\n  <div class="modal-settings-spacing">\r\n    <details class="config-list-details">\r\n      <summary>Thread settings</summary>\r\n      <div class="settings-wrapper">\r\n        <div id="thread-settings-container"></div>\r\n        <div id="thread-overlay-settings-container"></div>\r\n      </div>\r\n    </details>\r\n  </div>\r\n  <hr class="thick-line" />\r\n  <!-- TAGS -->\r\n  <div class="modal-settings-spacing">\r\n    <details class="config-list-details">\r\n      <summary>Tags</summary>\r\n\r\n      <div class="settings-wrapper">\r\n        <div id="tag-error-notif" class="modal-error-notif"></div>\r\n        <div id="tags-container">\r\n          <div id="search-container">\r\n            <input type="text" id="tags-search" placeholder="Search tags..." autocomplete="off" />\r\n            <ul id="search-results"></ul>\r\n            <div id="preffered-tags-list"></div>\r\n            <div id="excluded-tags-list"></div>\r\n          </div>\r\n        </div>\r\n      </div>\r\n    </details>\r\n  </div>\r\n  <hr class="thick-line" />\r\n  <!-- COLORS -->\r\n  <div class="modal-settings-spacing">\r\n    <details class="config-list-details">\r\n      <summary>Color</summary>\r\n      <div class="settings-wrapper">\r\n        <div id="color-error-notif" class="modal-error-notif"></div>\r\n\r\n        <div id="color-container"></div>\r\n      </div>\r\n      <div class="centered-item">\r\n        <button id="reset-color" class="modal-btn">Reset color</button>\r\n      </div>\r\n    </details>\r\n  </div>\r\n  <hr class="thick-line" />\r\n\r\n  <!-- Close -->\r\n  <div class="centered-item">\r\n    <button id="close-modal" class="modal-btn">\u{1F5D9} Close</button>\r\n  </div>\r\n</div>\r\n';

  // src/template/css.css?raw
  var css_default = ':root {\r\n  --completed-color: #388e3c;\r\n  --onhold-color: #1976d2;\r\n  --abandoned-color: #c9a300;\r\n  --highVersion-color: #2e7d32;\r\n  --invalidVersion-color: #a38400;\r\n  --tileInfo-color: #9398a0;\r\n  --tileHeader-color: #d9d9d9;\r\n  --preferred-color: #7b1fa2;\r\n  --preferred-text-color: #ffffff;\r\n  --excluded-color: #b71c1c;\r\n  --excluded-text-color: #ffffff;\r\n  --neutral-color: #37383a;\r\n  --neutral-text-color: #9398a0;\r\n\r\n  \r\n  --preferred-shadow: 0 0 2px 1px white;\r\n  --excluded-shadow: 0 0 2px 1px white;\r\n}\r\n.modal-error-notif {\r\n  display: none; \r\n  background-color: #ffe5e5; \r\n  color: #b00020; \r\n  border: 1px solid #b00020;\r\n  padding: 12px 16px;\r\n  border-radius: 6px;\r\n  margin-bottom: 12px;\r\n  font-size: 14px;\r\n  font-weight: 500;\r\n}\r\n.preferred {\r\n  background-color: var(--preferred-color);\r\n  font-weight: bold;\r\n  color: var(--preferred-text-color);\r\n}\r\n.preffered-shadow {\r\n  box-shadow: var(--preferred-shadow);\r\n}\r\n.excluded {\r\n  background-color: var(--excluded-color);\r\n  font-weight: bold;\r\n  color: var(--excluded-text-color);\r\n}\r\n.excluded-shadow {\r\n  box-shadow: var(--excluded-shadow);\r\n}\r\n.neutral {\r\n  background-color: var(--neutral-color);\r\n  font-weight: bold;\r\n  color: var(--neutral-text-color);\r\n}\r\n.custom-overlay-reason {\r\n  position: absolute;\r\n  top: 4px;\r\n  left: 4px;\r\n  background: rgba(0, 0, 0, 0.7);\r\n  color: white;\r\n  padding: 2px 6px;\r\n  font-size: 12px;\r\n  border-radius: 4px;\r\n  z-index: 2;\r\n  pointer-events: none;\r\n}\r\n.centered-item {\r\n  display: flex;\r\n  justify-content: center;\r\n  align-items: center;\r\n  padding: 10px;\r\n}\r\n.settings-wrapper {\r\n  padding: 10px;\r\n  color: #ccc;\r\n  font-size: 14px;\r\n  line-height: 1.6;\r\n}\r\ndiv#latest-page_items-wrap_inner\r\n  div.resource-tile\r\n  a.resource-tile_link\r\n  div.resource-tile_info\r\n  div.resource-tile_info-meta {\r\n  color: var(--tileInfo-color);\r\n  font-weight: 600;\r\n}\r\n\r\ndiv#latest-page_items-wrap_inner div.resource-tile a.resource-tile_link {\r\n  color: var(--tileHeader-color);\r\n}\r\n.tag-btn {\r\n  border: none;\r\n  padding: 5px;\r\n  margin: 0 2px;\r\n  cursor: pointer;\r\n  font-size: 14px;\r\n  color: white;\r\n  font-weight: bold;\r\n  transition: background-color 0.2s ease;\r\n}\r\n\r\n.tag-btn.excluded {\r\n  background-color: var(--excluded-color);\r\n  color: var(--excludedText-color);\r\n}\r\n\r\n.tag-btn.preferred {\r\n  background-color: var(--preferred-color);\r\n  color: var(--preferredText-color);\r\n}\r\n\r\n.tag-btn:hover {\r\n  filter: brightness(1.1);\r\n}\r\n#toast-container {\r\n  position: fixed;\r\n  top: 20px;\r\n  left: 50%;\r\n  transform: translateX(-50%);\r\n  z-index: 10000; \r\n  display: flex;\r\n  flex-direction: column;\r\n  gap: 8px;\r\n  pointer-events: none;\r\n}\r\n\r\n.toast {\r\n  padding: 10px;\r\n  background-color: #333;\r\n  color: #fff;\r\n  border-radius: 8px;\r\n  opacity: 0;\r\n  transform: translateY(-10px);\r\n  transition:\r\n    opacity 0.3s ease,\r\n    transform 0.3s ease;\r\n}\r\n\r\n.toast.show {\r\n  opacity: 1;\r\n  transform: translateY(0);\r\n}\r\n\r\n#tag-config-modal {\r\n  display: none;\r\n  position: fixed;\r\n  z-index: 9999;\r\n  top: 0;\r\n  left: 0;\r\n  width: 100%;\r\n  height: 100%;\r\n  background-color: rgba(0, 0, 0, 0.5);\r\n}\r\n\r\n#preffered-tags-list {\r\n  display: flex;\r\n  flex-wrap: wrap;\r\n  gap: 6px;\r\n  margin-top: 8px;\r\n}\r\n\r\n\r\n.preferred-tag-item {\r\n  display: inline-flex;\r\n  align-items: center;\r\n  background-color: var(--preferred-color);\r\n  color: var(--preferredText-color);\r\n  border-radius: 4px;\r\n  font-size: 14px;\r\n  font-weight: bold;\r\n}\r\n\r\n.preferred-tag-item span {\r\n  margin-right: 6px;\r\n  margin-left: 6px;\r\n}\r\n\r\n.preferred-tag-remove {\r\n  background-color: #c15858;\r\n  color: #fff;\r\n  border: none;\r\n  border-top-right-radius: 4px;\r\n  border-bottom-right-radius: 4px;\r\n\r\n  padding: 10px;\r\n  cursor: pointer;\r\n  font-weight: bold;\r\n  font-size: 12px;\r\n}\r\n\r\n\r\n.tag-actions {\r\n  display: flex;\r\n  gap: 5px;\r\n}\r\n#excluded-tags-list {\r\n  display: flex;\r\n  flex-wrap: wrap;\r\n  gap: 6px;\r\n  margin-top: 8px;\r\n}\r\n#search-container {\r\n  position: relative;\r\n  display: inline-block;\r\n  min-height: 250px;\r\n  width: 100%;\r\n}\r\n\r\n.excluded-tag-item {\r\n  display: inline-flex;\r\n  align-items: center;\r\n  background-color: var(--excluded-color);\r\n  color: var(--excludedText-color);\r\n  border-radius: 4px;\r\n  font-size: 14px;\r\n  font-weight: bold;\r\n}\r\n\r\n.excluded-tag-item span {\r\n  margin-right: 6px;\r\n}\r\n\r\n.excluded-tag-remove {\r\n  background-color: #c15858;\r\n  color: #fff;\r\n  border: none;\r\n  padding: 10px;\r\n  cursor: pointer;\r\n  border-top-right-radius: 4px;\r\n  border-bottom-right-radius: 4px;\r\n  font-size: 12px;\r\n  font-weight: bold;\r\n}\r\n\r\n\r\n#search-results li {\r\n  padding: 6px 8px;\r\n  cursor: pointer;\r\n  color: #fff;\r\n  background-color: #222;\r\n\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n}\r\n\r\n#search-results li:hover {\r\n  background-color: #333; \r\n}\r\n#tags-search {\r\n  background-color: #222;\r\n  color: #fff;\r\n  border: 1px solid #555;\r\n  border-radius: 4px;\r\n  padding: 6px 8px;\r\n  width: 100%;\r\n}\r\n\r\n#tags-search:focus {\r\n  outline: none;\r\n  border: 1px solid #c15858;\r\n}\r\n#search-results {\r\n  position: absolute;\r\n  left: 0;\r\n  right: 0;\r\n  max-height: 200px;\r\n  overflow-y: auto;\r\n  background-color: #222; \r\n  border: 1px solid #555; \r\n  border-radius: 4px;\r\n  margin: 2px 0 0 0; \r\n  padding: 0;\r\n  list-style: none;\r\n  display: none;\r\n  z-index: 1000;\r\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); \r\n}\r\n\r\n#tag-config-modal input,\r\n#tag-config-modal textarea,\r\n#tag-config-modal select {\r\n  background-color: #222;\r\n  color: #fff;\r\n  border: 1px solid #555;\r\n  border-radius: 4px;\r\n}\r\n#tag-config-modal input:focus,\r\n#tag-config-modal textarea:focus,\r\n#tag-config-modal select:focus {\r\n  outline: none;\r\n  border: 1px solid #c15858;\r\n}\r\n\r\n\r\n#tag-config-modal input[type="checkbox"],\r\n#tag-config-modal input[type="radio"] {\r\n  accent-color: #c15858;\r\n  background-color: #222;\r\n  border: 1px solid #555;\r\n}\r\n#tag-config-modal .config-color-input {\r\n  border: 2px solid #3f4043;\r\n  border-radius: 5px;\r\n  padding: 2px;\r\n  width: 40px;\r\n  height: 28px;\r\n  cursor: pointer;\r\n  background-color: #181a1d;\r\n}\r\n\r\n#tag-config-modal .config-color-input::-webkit-color-swatch-wrapper {\r\n  padding: 0;\r\n}\r\n\r\n#tag-config-modal .config-color-input::-webkit-color-swatch {\r\n  border-radius: 4px;\r\n  border: none;\r\n}\r\n\r\n.modal-btn {\r\n  background-color: #893839;\r\n  color: white;\r\n  border: 2px solid #893839;\r\n  border-radius: 6px;\r\n  padding: 8px 16px;\r\n  font-weight: 600;\r\n  font-size: 14px;\r\n  cursor: pointer;\r\n  transition:\r\n    background-color 0.3s ease,\r\n    border-color 0.3s ease;\r\n  box-shadow: 0 4px 8px rgba(137, 56, 56, 0.5);\r\n}\r\n\r\n.modal-btn:hover {\r\n  background-color: #b94f4f;\r\n  border-color: #b94f4f;\r\n}\r\n\r\n.modal-btn:active {\r\n  background-color: #6e2b2b;\r\n  border-color: #6e2b2b;\r\n  box-shadow: none;\r\n}\r\n.config-row {\r\n  display: flex;\r\n  align-items: center; \r\n  gap: 12px;\r\n  margin: 8px 0;\r\n  line-height: 1.4;\r\n  user-select: none;\r\n}\r\n\r\n\r\n.config-row label {\r\n  flex: 0 0 180px; \r\n  text-align: left;\r\n  font-weight: 500;\r\n  cursor: pointer;\r\n  white-space: nowrap; \r\n  overflow: hidden;\r\n  text-overflow: ellipsis;\r\n}\r\n\r\n\r\n.config-row input[type="checkbox"] {\r\n  flex-shrink: 0;\r\n  width: 18px;\r\n  height: 18px;\r\n  margin: 0;\r\n  cursor: pointer;\r\n  accent-color: #6e42d6; \r\n}\r\n\r\n.config-row:hover {\r\n  background: rgba(110, 66, 214, 0.05);\r\n}\r\n\r\n#tag-config-button {\r\n  position: fixed;\r\n  bottom: 20px;\r\n  right: 20px;\r\n  left: 20px;\r\n  padding: 8px 12px;\r\n  font-size: 20px;\r\n  z-index: 7;\r\n  cursor: pointer;\r\n  border: 2px inset #461616;\r\n  background: #cc3131;\r\n  color: white;\r\n  border-radius: 8px;\r\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\r\n  max-width: 70px;\r\n  width: auto;\r\n  opacity: 0.75;\r\n  transition:\r\n    opacity 1s ease,\r\n    transform 0.5s ease;\r\n}\r\n\r\n#tag-config-button:hover {\r\n  opacity: 1;\r\n}\r\n\r\n#tag-config-button:active {\r\n  transform: scale(0.9);\r\n}\r\n\r\n\r\n#tag-config-button.hidden {\r\n  opacity: 0;\r\n  pointer-events: auto; \r\n  transition: opacity 0.3s ease;\r\n}\r\n\r\n#tag-config-button.hidden:hover {\r\n  opacity: 0.75; \r\n}\r\n\r\n\r\n.blink-hide {\r\n  animation: blink-hidden 0.4s ease-in-out 3;\r\n}\r\n\r\n#tag-config-modal .modal-content {\r\n  background: black;\r\n  border-radius: 10px;\r\n  min-width: 300px;\r\n  max-height: 80vh;\r\n  overflow-y: scroll; \r\n  background: #191b1e;\r\n  max-width: 400px;\r\n  margin: 100px auto;\r\n}\r\n\r\n#tag-config-modal.show {\r\n  display: flex;\r\n}\r\n\r\n.config-list-details {\r\n  overflow: hidden;\r\n  transition:\r\n    border-width 1s,\r\n    max-height 1s ease;\r\n  max-height: 40px;\r\n}\r\n\r\n.config-list-details[open] {\r\n  border-width: 2px;\r\n  max-height: 1300px;\r\n}\r\n.thick-line {\r\n  border: none;\r\n  height: 1px;\r\n  background-color: #3f4043;\r\n}\r\n.config-list-details summary {\r\n  text-align: center;\r\n  background: #353535;\r\n  border-radius: 8px;\r\n  padding-top: 5px;\r\n  padding-bottom: 5px;\r\n  cursor: pointer;\r\n}\r\n\r\n.config-tag-item {\r\n  margin-left: 5px;\r\n  cursor: pointer;\r\n}\r\n\r\n.modal-settings-spacing {\r\n  padding: 10px;\r\n}\r\n.no-max-width {\r\n  max-width: none !important; \r\n}\r\n.config-label:hover {\r\n  text-decoration: underline;\r\n  text-decoration-style: dotted;\r\n}\r\n\r\n\r\n.img-retry-toast {\r\n  position: fixed;\r\n  top: 20px;\r\n  right: 20px;\r\n  background: rgba(0, 0, 0, 0.85);\r\n  color: #fff;\r\n  padding: 10px 15px;\r\n  border-radius: 8px;\r\n  font-family: sans-serif;\r\n  font-size: 13px;\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 10px;\r\n  z-index: 99999;\r\n  pointer-events: none;\r\n}\r\n\r\n\r\n.img-retry-toast .img-retry-spinner {\r\n  border: 2px solid #fff;\r\n  border-top: 2px solid transparent;\r\n  border-radius: 50%;\r\n  width: 14px;\r\n  height: 14px;\r\n  display: inline-block;\r\n  animation: img-retry-spin 1s linear infinite;\r\n}\r\n\r\n\r\n@keyframes img-retry-spin {\r\n  0% {\r\n    transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    transform: rotate(360deg);\r\n  }\r\n}\r\n\r\n\r\n.img-retry-toast .img-retry-stats {\r\n  margin-left: 10px;\r\n  opacity: 0.8;\r\n}\r\n\r\nhtml.latest-wide .p-body-inner {\r\n  max-width: none !important;\r\n}\r\n\r\nhtml.latest-wide main#latest-page_main-wrap {\r\n  width: 100% !important;\r\n  max-width: none !important;\r\n}\r\n.hide-notices ul.notices.notices--block.js-notices {\r\n  display: none !important;\r\n}\r\n\r\n.header-scroll .uix_headerContainer {\r\n  transition: transform 0.25s ease;\r\n  will-change: transform;\r\n}\r\n\r\n.header-scroll.header-hidden .uix_headerContainer {\r\n  transform: translateY(-100%);\r\n}\r\n\r\n\r\n.thread-scroll-hide .p-navSticky {\r\n  transition: transform 0.25s ease;\r\n  will-change: transform;\r\n}\r\n\r\n\r\n.thread-scroll-hide .p-navSticky.is-sticky {\r\n  transition: transform 0.25s ease;\r\n  will-change: transform;\r\n}\r\n\r\n.thread-scroll-hide.thread-header-hidden .p-navSticky.is-sticky {\r\n  transform: translateY(-100%);\r\n}\r\n\r\n.config-header {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n\r\n  margin: 14px 0 10px;\r\n  padding: 6px 0;\r\n\r\n  font-size: 0.95em;\r\n  font-weight: 600;\r\n  text-align: center;\r\n\r\n  color: #b0b3b8; \r\n  letter-spacing: 0.04em;\r\n  text-transform: uppercase;\r\n\r\n  user-select: none;\r\n}\r\n\r\n\r\nhtml.latest-wide main#latest-page_main-wrap {\r\n  width: 100% !important;\r\n  max-width: none !important;\r\n}\r\n\r\n\r\nhtml.latest-dense .grid-normal {\r\n  grid-template-columns: repeat(\r\n    auto-fill,\r\n    minmax(260px, 1fr)\r\n  ) !important; \r\n  gap: 20px !important; \r\n}\r\n\r\n\r\nhtml.latest-dense .structItem.structItem--latest {\r\n  max-width: none !important;\r\n  width: 100% !important;\r\n}\r\n\r\n\r\nhtml.latest-dense .structItem-cell.structItem-cell--main {\r\n  padding: 12px !important; \r\n}\r\n\r\nhtml.latest-signature-collapsed aside.message-signature {\r\n  max-height: 0 !important;\r\n  overflow: hidden !important;\r\n\r\n  padding-top: 0 !important;\r\n  padding-bottom: 0 !important;\r\n  margin-top: 0 !important;\r\n  border-top: none !important;\r\n\r\n  opacity: 0;\r\n  transition:\r\n    max-height 0.25s ease,\r\n    opacity 0.2s ease;\r\n}\r\n\r\n\r\nhtml.latest-signature-collapsed aside.message-signature.latest-signature-expanded {\r\n  max-height: 300px !important;\r\n  overflow-y: auto !important;\r\n\r\n  padding-top: 10px !important;\r\n  padding-bottom: 10px !important;\r\n  border-top: 1px solid rgba(255, 255, 255, 0.12) !important;\r\n\r\n  opacity: 1;\r\n}\r\n\r\n\r\n.latest-signature-toggle {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n\r\n  width: 100%;\r\n  margin: 6px 0 12px;\r\n  padding: 6px 0;\r\n\r\n  border: none;\r\n  border-radius: 0;\r\n  background: transparent;\r\n\r\n  font-size: 1.2rem;\r\n  color: #ec5555;\r\n  cursor: pointer;\r\n\r\n  position: relative;\r\n}\r\n\r\n\r\n.latest-signature-toggle::before,\r\n.latest-signature-toggle::after {\r\n  content: "";\r\n  flex: 1;\r\n  height: 1px;\r\n  background: rgba(255, 255, 255, 0.12);\r\n  margin: 0 10px;\r\n}\r\n\r\n.latest-signature-toggle span {\r\n  white-space: nowrap;\r\n}\r\n\r\n\r\n@media (max-width: 480px) {\r\n  html.latest-signature-collapsed .latest-signature-toggle {\r\n    display: none;\r\n  }\r\n}\r\n@keyframes blink-hidden {\r\n  0% {\r\n    opacity: 1;\r\n  }\r\n  50% {\r\n    opacity: 0;\r\n  }\r\n  100% {\r\n    opacity: 1;\r\n  }\r\n}\r\n';

  // src/cores/safety.js
  function checkTags() {
    const el = document.getElementById("tag-error-notif");
    if (!el) return;
    if (config.tags.length === 0) {
      el.textContent = "No tag detected, go to f95zone latest page and open this menu again.";
      el.style.display = "block";
    } else {
      el.style.display = "none";
    }
  }
  function colorErrorNotif(text) {
    const el = document.getElementById("color-error-notif");
    if (!el) return;
    el.textContent = text;
    if (text) {
      el.style.display = "block";
    } else {
      el.style.display = "none";
    }
  }
  function tagsErrorNotif(text) {
    const el = document.getElementById("tag-error-notif");
    if (!el) return;
    el.textContent = text;
    if (text) {
      el.style.display = "block";
    } else {
      el.style.display = "none";
    }
  }
  function checkOverlaySettings() {
    if (!config.latestSettings.latestOverlayToggle && !config.threadSettings.threadOverlayToggle) {
      colorErrorNotif("Both Latest and Thread overlay are disabled, nothing will be applied.");
      tagsErrorNotif("Both Latest and Thread overlay are disabled, nothing will be applied.");
    } else {
      colorErrorNotif("");
      tagsErrorNotif("");
    }
  }

  // src/utils/waitFor.js
  function waitFor(conditionFn, interval = 50, timeout = 2e3) {
    return new Promise((resolve, reject) => {
      const start = Date.now();
      const check = () => {
        if (conditionFn()) {
          resolve(true);
        } else if (Date.now() - start > timeout) {
          reject(new Error("Timeout waiting for condition"));
        } else {
          setTimeout(check, interval);
        }
      };
      check();
    });
  }
  function detectPage() {
    const path = location.pathname;
    if (!window.location.hostname === "f95zone.to") return;
    if (path.startsWith("/threads")) {
      state.isThread = true;
    } else if (path.startsWith("/sam/latest_alpha")) {
      state.isLatest = true;
    } else if (path.startsWith("/masked")) {
      state.isMaskedLink = true;
    }
  }
  function waitForBody(callback) {
    if (document.body) {
      callback();
    } else {
      requestAnimationFrame(() => waitForBody(callback));
    }
  }

  // src/data/tags.js
  async function updateTags() {
    if (state.tagsUpdated) return;
    const selector = document.querySelector(".selectize-input.items.not-full");
    const dropdown = document.querySelector(".selectize-dropdown.single.filter-tags-select");
    if (!selector || !dropdown) {
      if (debug) console.log("updateTags: failed to find selector/dropdown");
      return;
    }
    selector.click();
    try {
      await waitFor(() => dropdown.querySelectorAll(".option").length > 0, 50, 3e3);
    } catch (err) {
      if (debug) console.log("updateTags: timeout waiting for options", err);
      return;
    }
    const options = [...dropdown.querySelectorAll(".option")];
    const newTags = options.map((opt) => ({
      id: parseInt(opt.getAttribute("data-value")),
      name: opt.querySelector(".tag-name")?.textContent.trim() || ""
    }));
    const arraysAreDifferent = !(config.tags.length === newTags.length && config.tags.every(
      (tag, index) => tag.id === newTags[index].id && tag.name === newTags[index].name
    ));
    if (arraysAreDifferent) {
      config.tags = newTags;
      saveConfigKeys({ tags: config.tags });
      if (debug) console.log("updateTags: tags updated", newTags);
    }
    checkTags();
    state.tagsUpdated = true;
    if (debug) console.log("updateTags: finished");
  }

  // src/helper/maskedLinkSkipper.js
  function skipMaskedPage() {
    if (!config.threadSettings.skipMaskedLink) return;
    if (!location.pathname.startsWith("/masked/") || location.pathname === "/masked/") return;
    const continueBtn = document.querySelector(".host_link");
    if (continueBtn) {
      continueBtn.click();
      return;
    }
    const $loading = document.getElementById("loading");
    const $captchaDiv = document.getElementById("captcha");
    const $error = document.getElementById("error");
    function handleError(title, message) {
      if ($error) $error.innerHTML = `<h2>${title}</h2><p>${message}</p>`;
      if ($loading) $loading.style.display = "none";
    }
    if ($loading) $loading.style.display = "block";
    function sendRequest(token = "") {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", location.pathname, true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            try {
              const res = JSON.parse(xhr.responseText);
              if (res.status === "ok") {
                location.href = res.msg;
              } else if (res.status === "captcha") {
                if ($captchaDiv) {
                  $captchaDiv.style.display = "block";
                  grecaptcha.render("captcha", {
                    theme: "dark",
                    sitekey: "6LcwQ5kUAAAAAAI-_CXQtlnhdMjmFDt-MruZ2gov",
                    callback: (t) => {
                      $captchaDiv.style.display = "none";
                      if ($loading) $loading.style.display = "block";
                      sendRequest(t);
                    }
                  });
                }
              } else {
                handleError("Error", res.msg || "Unknown");
              }
            } catch (e) {
              handleError("Bad Response", "Try refreshing");
              console.error("skipMaskedPage parse error:", e);
            }
          } else {
            handleError("Server Error", "Chill and retry");
          }
        }
      };
      xhr.send(`xhr=1&download=1${token ? "&captcha=" + token : ""}`);
    }
    sendRequest();
  }
  var clickHandler = null;
  var auxclickHandler = null;
  function hijackMaskedLinks() {
    if (location.pathname.startsWith("/masked/")) return;
    if (state.isMaskedLinkApplied) return;
    if (!config.threadSettings.skipMaskedLink) return;
    state.isMaskedLinkApplied = true;
    const handler = function(e) {
      if (e.button !== 0 && e.button !== 1) return;
      let link = e.target.closest('a[href^="/masked/"], a[href^="https://f95zone.to/masked/"]');
      if (!link) return;
      let href = link.getAttribute("href");
      if (href.startsWith("/masked/")) {
        href = "https://f95zone.to" + href;
      }
      const path = new URL(href).pathname;
      e.preventDefault();
      e.stopImmediatePropagation();
      link.style.color = "#ffff00";
      const xhr = new XMLHttpRequest();
      xhr.open("POST", "https://f95zone.to" + path, true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          let targetUrl = href;
          if (xhr.status === 200) {
            try {
              const data = JSON.parse(xhr.responseText);
              if (data.status === "ok" && data.msg) {
                targetUrl = data.msg;
                link.href = targetUrl;
                link.style.color = "#00ff00";
              }
            } catch (_) {
              console.error("hijackMaskedLinks parse error:", _);
            }
          } else {
            link.style.color = "";
          }
          window.open(targetUrl, "_blank");
        }
      };
      xhr.send("xhr=1&download=1");
    };
    clickHandler = handler;
    auxclickHandler = handler;
    document.addEventListener("click", handler, true);
    document.addEventListener("auxclick", handler, true);
  }
  function disableHijackMaskedLink() {
    if (!state.isMaskedLinkApplied) return;
    if (clickHandler) {
      document.removeEventListener("click", clickHandler, true);
      document.removeEventListener("auxclick", auxclickHandler, true);
      clickHandler = null;
      auxclickHandler = null;
    }
    state.isMaskedLinkApplied = false;
  }
  function toggleHijackMaskedLink() {
    if (config.threadSettings.skipMaskedLink) {
      hijackMaskedLinks();
    } else {
      disableHijackMaskedLink();
    }
  }

  // src/helper/handleTextColor.js
  function getReadableTextColor(hex) {
    hex = hex.replace("#", "");
    const r = parseInt(hex.substring(0, 2), 16);
    const g = parseInt(hex.substring(2, 4), 16);
    const b = parseInt(hex.substring(4, 6), 16);
    const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
    return luminance > 0.5 ? "#181a1d" : "#d9d9d9";
  }
  function getAverageHexColor(hexes) {
    let r = 0, g = 0, b = 0;
    hexes.forEach((hex) => {
      hex = hex.replace("#", "");
      r += parseInt(hex.slice(0, 2), 16);
      g += parseInt(hex.slice(2, 4), 16);
      b += parseInt(hex.slice(4, 6), 16);
    });
    r = Math.round(r / hexes.length);
    g = Math.round(g / hexes.length);
    b = Math.round(b / hexes.length);
    return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
  }
  function getTextColorForGradient(gradientStr) {
    const hexes = gradientStr.match(/#([0-9a-f]{6})/gi);
    if (!hexes) return "#ffffff";
    const avg = getAverageHexColor(hexes);
    return getReadableTextColor(avg);
  }

  // src/helper/tileVerifier.js
  function verifyTilesAfterLoad(retryDelay = 2e3, maxRetries = 3) {
    let retries = 0;
    function checkTiles() {
      const tiles = document.getElementsByClassName("resource-tile");
      let hasModified = false;
      for (let i = 0; i < tiles.length; i++) {
        if (tiles[i].dataset.modified === "true") {
          hasModified = true;
          break;
        }
      }
      if (!hasModified) {
        console.warn("Tiles not modified yet, forcing manual trigger...");
        processAllTiles();
        retries++;
        if (retries < maxRetries) {
          setTimeout(checkTiles, retryDelay);
        } else {
          console.warn("Reached max retries, stopping verification.");
        }
      }
    }
    setTimeout(checkTiles, retryDelay);
  }

  // src/cores/latest.js
  function watchAndUpdateTiles() {
    const latestUpdateWrapper = document.getElementById("latest-page_items-wrap");
    if (!latestUpdateWrapper) return;
    const mutationObserver = new MutationObserver(() => {
      setTimeout(() => {
        handleWebClick();
      }, 100);
      processAllTiles();
    });
    const options = { childList: true, subtree: true };
    mutationObserver.observe(latestUpdateWrapper, options);
  }
  function processAllTiles(reset = false) {
    if (!config.latestSettings.latestOverlayToggle || !state.isLatest) return;
    const tiles = document.getElementsByClassName("resource-tile");
    if (!tiles.length || state.isProcessingTiles) {
      return;
    }
    state.isProcessingTiles = true;
    for (let i = 0; i < tiles.length; i++) {
      processTile(tiles[i], reset);
    }
    verifyTilesAfterLoad();
    state.isProcessingTiles = false;
  }
  function processTile(tile, reset = false) {
    if (tile.dataset.modified === "true" && !reset) return;
    if (reset) tile.dataset.modified = "";
    let isOverlayApplied = false;
    let colors = [];
    const body = tile.querySelector(".resource-tile_body");
    const versionText = getVersionText(tile);
    const match = versionText.match(/(\d+\.\d+)/);
    const versionNumber = match ? parseFloat(match[1]) : null;
    const isValidKeyword = ["full", "final"].some(
      (valid) => versionText.toLowerCase().includes(valid)
    );
    debug && console.log(versionText, versionNumber, match, isValidKeyword);
    const labelText = getLabelText(tile);
    const matchedTag = processTag(tile, config.preferredTags);
    const excludedTag = processTag(tile, config.excludedTags);
    debug && console.log(labelText, matchedTag, excludedTag);
    if (excludedTag && config.overlaySettings.excluded) {
      isOverlayApplied = addOverlayLabel(tile, excludedTag, isOverlayApplied);
      colors.push(config.color.excluded);
    }
    if (matchedTag && config.overlaySettings.preferred) {
      isOverlayApplied = addOverlayLabel(tile, matchedTag, isOverlayApplied);
      colors.push(config.color.preferred);
    }
    if (labelText === "completed" && config.overlaySettings.completed) {
      isOverlayApplied = addOverlayLabel(tile, "Completed", isOverlayApplied);
      colors.push(config.color.completed);
    } else if (labelText === "onhold" && config.overlaySettings.onhold) {
      isOverlayApplied = addOverlayLabel(tile, "On Hold", isOverlayApplied);
      colors.push(config.color.onhold);
    } else if (labelText === "abandoned" && config.overlaySettings.abandoned) {
      isOverlayApplied = addOverlayLabel(tile, "Abandoned", isOverlayApplied);
      colors.push(config.color.abandoned);
    }
    if (config.overlaySettings.highVersion && versionNumber !== null && versionNumber >= config.latestSettings.minVersion || isValidKeyword) {
      isOverlayApplied = addOverlayLabel(tile, "High Version", isOverlayApplied);
      colors.push(config.color.highVersion);
    } else if (versionNumber !== null && versionNumber < config.latestSettings.minVersion && config.overlaySettings.invalidVersion) {
      addOverlayLabel(tile, "Invalid Version", isOverlayApplied);
      colors.push(config.color.invalidVersion);
    }
    body.style.background = "";
    if (colors.length > 0) {
      const gradient = createSegmentedGradient(colors, "45deg");
      body.style.background = gradient;
      const textColor = getTextColorForGradient(gradient);
      body.style.color = textColor;
      const metas = body.querySelectorAll(".resource-tile_info-meta");
      metas.forEach((meta) => {
        meta.style.color = textColor;
        meta.style.fontWeight = "bold";
      });
    }
    tile.dataset.modified = "true";
  }
  function addOverlayLabel(tile, reasonText, isApplied) {
    if (isApplied || !config.overlaySettings.overlayText) {
      if (!config.overlaySettings.overlayText) {
        removeOverlayLabel();
      }
      return true;
    }
    const thumbWrap = tile.querySelector(".resource-tile_thumb-wrap");
    if (!thumbWrap) return false;
    let existingOverlay = thumbWrap.querySelector(".custom-overlay-reason");
    if (!existingOverlay) {
      existingOverlay = document.createElement("div");
      existingOverlay.className = "custom-overlay-reason";
      thumbWrap.prepend(existingOverlay);
    }
    existingOverlay.innerText = reasonText;
    return true;
  }
  function createSegmentedGradient(colors, direction = "to right") {
    if (!Array.isArray(colors) || colors.length === 0) return "";
    if (colors.length === 1) return colors[0];
    const segment = 100 / colors.length;
    return `linear-gradient(${direction}, ` + colors.map((color, i) => {
      const start = (i * segment).toFixed(2);
      const end = ((i + 1) * segment).toFixed(2);
      return `${color} ${start}% ${end}%`;
    }).join(", ") + `)`;
  }
  function removeOverlayLabel() {
    let existingOverlay = document.querySelector(".custom-overlay-reason");
    if (existingOverlay) {
      existingOverlay.remove();
    }
  }
  function getLabelText(tile) {
    const labelWrap = tile.querySelector(".resource-tile_label-wrap_right");
    const labelEl = labelWrap?.querySelector('[class^="label--"]');
    return labelEl?.innerHTML?.toLowerCase().trim() || "";
  }
  function processTag(tile, tags) {
    const tagIds = (tile.getAttribute("data-tags") || "").split(",").map((id) => parseInt(id.trim(), 10)).filter(Number.isFinite);
    debug && console.log(tagIds);
    const matchedId = tagIds.find((id) => tags.some((tag) => tag === id));
    debug && console.log(matchedId);
    if (!matchedId) return false;
    const matchedTag = config.tags.find((tag) => tag.id == matchedId);
    return matchedTag ? matchedTag.name : false;
  }
  function getVersionText(tile) {
    const versionEl = tile.querySelector(".resource-tile_label-version");
    return versionEl?.innerHTML?.toLowerCase().trim() || "";
  }
  function toggleWideLatestPage() {
    const root = document.documentElement;
    if (config.latestSettings.wideLatest) {
      root.classList.add("latest-wide", "hide-notices", "header-scroll");
      enableHeaderScrollBehavior();
    } else {
      root.classList.remove("latest-wide", "hide-notices", "header-scroll");
      disableHeaderScrollBehavior();
    }
  }
  var headerScrollHandler = null;
  function enableHeaderScrollBehavior() {
    if (headerScrollHandler) return;
    let lastScrollY = window.scrollY;
    headerScrollHandler = () => {
      const root = document.documentElement;
      const currentY = window.scrollY;
      if (currentY > lastScrollY && currentY > 80) {
        root.classList.add("header-hidden");
      } else {
        root.classList.remove("header-hidden");
      }
      lastScrollY = currentY;
    };
    window.addEventListener("scroll", headerScrollHandler, { passive: true });
  }
  function disableHeaderScrollBehavior() {
    if (!headerScrollHandler) return;
    window.removeEventListener("scroll", headerScrollHandler);
    headerScrollHandler = null;
    document.documentElement.classList.remove("header-hidden");
  }
  function toggleDenseLatestGrid() {
    const root = document.documentElement;
    if (config.latestSettings.denseLatestGrid) {
      root.classList.add("latest-dense");
    } else {
      root.classList.remove("latest-dense");
    }
  }
  function resetAllTiles() {
    if (config.latestSettings.latestOverlayToggle || !state.isLatest) return;
    debug && console.log("Resetting all tiles on Latest Updates page");
    const tiles = document.getElementsByClassName("resource-tile");
    if (!tiles.length) return;
    for (let i = 0; i < tiles.length; i++) {
      resetTile(tiles[i]);
    }
  }
  function resetTile(tile) {
    if (tile.dataset.modified !== "true") return;
    const body = tile.querySelector(".resource-tile_body");
    if (!body) return;
    const overlays = tile.querySelectorAll(".resource-tile_overlay");
    overlays.forEach((overlay) => overlay.remove());
    body.removeAttribute("style");
    const metas = body.querySelectorAll(".resource-tile_info-meta");
    metas.forEach((meta) => meta.removeAttribute("style"));
    tile.dataset.modified = "";
  }
  function autoRefreshClick() {
    const autoRefreshBtn = document.getElementById("controls_auto-refresh");
    if (!autoRefreshBtn) return;
    const selected = autoRefreshBtn.classList.contains("selected");
    if (!selected && config.latestSettings.autoRefresh || selected && !config.latestSettings.autoRefresh) {
      autoRefreshBtn.click();
    }
  }
  function webNotifClick() {
    const webNotifBtn = document.getElementById("controls_notify");
    if (!webNotifBtn) return;
    const selected = webNotifBtn.classList.contains("selected");
    if (!selected && config.latestSettings.webNotif || selected && !config.latestSettings.webNotif) {
      webNotifBtn.click();
    }
  }
  function handleWebClick() {
    autoRefreshClick();
    webNotifClick();
  }

  // src/cores/thread.js
  function processThreadTags() {
    if (!state.isThread || !config.threadSettings.threadOverlayToggle) return;
    const tagList = document.querySelector(".js-tagList");
    if (!tagList) {
      return;
    }
    let tags = tagList.getElementsByClassName("tagItem");
    tags = Array.from(tags);
    tags.forEach((tag) => {
      processThreadTag(tag);
    });
  }
  function processThreadTag(tagElement) {
    const tagName = tagElement.innerHTML.trim();
    const preferredId = config.preferredTags.find(
      (id) => config.tags.find((t) => t.id === id && t.name === tagName)
    );
    const excludedId = config.excludedTags.find(
      (id) => config.tags.find((t) => t.id === id && t.name === tagName)
    );
    Object.values(STATUS).forEach((cls) => tagElement.classList.remove(cls));
    const { preferred, preferredShadow, excluded, excludedShadow, neutral } = config.threadSettings;
    if (preferredId && preferred) {
      tagElement.classList.add(STATUS.PREFERRED);
      preferredShadow && tagElement.classList.add(STATUS.PREFFERED_SHADOW);
      return;
    } else if (excludedId && excluded) {
      tagElement.classList.add(STATUS.EXCLUDED);
      excludedShadow && tagElement.classList.add(STATUS.EXCLUDED_SHADOW);
      return;
    } else if (neutral) {
      tagElement.classList.add(STATUS.NEUTRAL);
    }
  }
  function toggleThreadTagOverlay() {
    if (!state.isThread) return;
    if (config.threadSettings.threadOverlayToggle) {
      processThreadTags();
    } else {
      disableThreadTagOverlay();
    }
  }
  function disableThreadTagOverlay() {
    if (!state.isThread) return;
    const tagList = document.querySelector(".js-tagList");
    if (!tagList) return;
    const tags = tagList.getElementsByClassName("tagItem");
    Array.from(tags).forEach((tag) => {
      Object.values(STATUS).forEach((cls) => {
        tag.classList.remove(cls);
      });
    });
    console.log("Thread tag overlay disabled \u2014 tags back to vanilla");
  }
  function signatureCollapse() {
    if (!state.isThread) return;
    const enabled = !!config.threadSettings.collapseSignature;
    const root = document.documentElement;
    root.classList.toggle("latest-signature-collapsed", enabled);
    if (!enabled) {
      cleanup();
      return;
    }
    document.querySelectorAll("aside.message-signature").forEach((sig) => {
      debug && console.log("Processing signature collapse", sig);
      if (sig.dataset.latestProcessed) return;
      sig.dataset.latestProcessed = "1";
      const btn = document.createElement("button");
      btn.innerHTML = "<span>Show signature</span>";
      btn.className = "latest-signature-toggle";
      btn.type = "button";
      btn.addEventListener("click", () => {
        const expanded = sig.classList.toggle("latest-signature-expanded");
        btn.querySelector("span").textContent = expanded ? "Hide signature" : "Show signature";
      });
      sig.after(btn);
    });
  }
  function cleanup() {
    document.querySelectorAll(".latest-signature-toggle").forEach((b) => b.remove());
    document.querySelectorAll("aside.message-signature").forEach((sig) => {
      delete sig.dataset.latestProcessed;
      sig.classList.remove("latest-signature-expanded");
    });
  }

  // src/helper/createQueuedTask.js
  function createQueuedTask(fn, delay = 100) {
    let timer = null;
    return function(...args) {
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        fn(...args);
        timer = null;
      }, delay);
    };
  }

  // src/helper/tasksRegistry.js
  var queuedProcessAllTilesReset = createQueuedTask(() => {
    if (!config.latestSettings.latestOverlayToggle || !state.isLatest) return;
    processAllTiles(true);
  }, 100);
  var queuedProcessThreadTags = createQueuedTask(() => {
    if (!state.isThread || !config.threadSettings.threadOverlayToggle) return;
    toggleThreadTagOverlay();
  }, 100);
  var queuedUpdateLatestUI = createQueuedTask(() => updateLatestUI());
  var queuedResetAllTiles = createQueuedTask(() => {
    if (!state.isLatest) return;
    resetAllTiles();
    return false;
  });
  var queuedProcessAllTiles = createQueuedTask(() => {
    if (!state.isLatest && !config.latestSettings.latestOverlayToggle) return;
    processAllTiles();
    return false;
  });

  // src/renderer/updateColorStyle.js
  function updateColorStyle(key) {
    if (key && config.color[key] !== void 0) {
      const varName = `--${key}-color`;
      document.documentElement.style.setProperty(varName, config.color[key]);
      debug && console.log(varName, config.color[key]);
    } else {
      for (const [k, value] of Object.entries(config.color)) {
        const varName = `--${k}-color`;
        document.documentElement.style.setProperty(varName, value);
        debug && console.log(varName, value);
      }
    }
    const preferredShadow = config.threadSettings.preferredShadow ? "0 0 2px 1px white" : "none";
    const excludedShadow = config.threadSettings.excludedShadow ? "0 0 2px 1px white" : "none";
    document.documentElement.style.setProperty("--preferred-shadow", preferredShadow);
    document.documentElement.style.setProperty("--excluded-shadow", excludedShadow);
  }

  // src/meta/colorSettings.js
  var executeBothQueuedTasks = () => {
    queuedProcessAllTilesReset();
    queuedProcessThreadTags();
  };
  function effectCompletedColor() {
    updateColorStyle("completedColor");
    executeBothQueuedTasks();
  }
  function effectOnHoldColor() {
    updateColorStyle("onholdColor");
    executeBothQueuedTasks();
  }
  function effectAbandonedColor() {
    updateColorStyle("abandonedColor");
    executeBothQueuedTasks();
  }
  function effectHighVersionColor() {
    updateColorStyle("highVersionColor");
    executeBothQueuedTasks();
  }
  function effectInvalidVersionColor() {
    updateColorStyle("invalidVersionColor");
    executeBothQueuedTasks();
  }
  function effectTileInfoColor() {
    updateColorStyle("tileInfoColor");
    executeBothQueuedTasks();
  }
  function effectTileHeaderColor() {
    updateColorStyle("tileHeaderColor");
    executeBothQueuedTasks();
  }
  function effectPreferredColor() {
    updateColorStyle("preferredColor");
    executeBothQueuedTasks();
  }
  function effectPreferredTextColor() {
    updateColorStyle("preferredTextColor");
    executeBothQueuedTasks();
  }
  function effectExcludedColor() {
    updateColorStyle("excludedColor");
    executeBothQueuedTasks();
  }
  function effectExcludedTextColor() {
    updateColorStyle("excludedTextColor");
    executeBothQueuedTasks();
  }
  function effectNeutralColor() {
    updateColorStyle("neutralColor");
    executeBothQueuedTasks();
  }
  function effectNeutralTextColor() {
    updateColorStyle("neutralTextColor");
    executeBothQueuedTasks();
  }
  var colorSettingsMeta = {
    completedColor: {
      type: "color",
      text: "Completed",
      config: "color.completed",
      effects: {
        custom: effectCompletedColor,
        toast: () => "Completed color updated"
      }
    },
    onholdColor: {
      type: "color",
      text: "On Hold",
      config: "color.onhold",
      effects: {
        custom: effectOnHoldColor,
        toast: () => "On Hold color updated"
      }
    },
    abandonedColor: {
      type: "color",
      text: "Abandoned",
      config: "color.abandoned",
      effects: {
        custom: effectAbandonedColor,
        toast: () => "Abandoned color updated"
      }
    },
    highVersionColor: {
      type: "color",
      text: "High Version",
      config: "color.highVersion",
      effects: {
        custom: effectHighVersionColor,
        toast: () => "High Version color updated"
      }
    },
    invalidVersionColor: {
      type: "color",
      text: "Invalid Version",
      config: "color.invalidVersion",
      effects: {
        custom: effectInvalidVersionColor,
        toast: () => "Invalid Version color updated"
      }
    },
    tileInfoColor: {
      type: "color",
      text: "Tile Info",
      config: "color.tileInfo",
      effects: {
        custom: effectTileInfoColor,
        toast: () => "Tile Info color updated"
      }
    },
    tileHeaderColor: {
      type: "color",
      text: "Tile Header",
      config: "color.tileHeader",
      effects: {
        custom: effectTileHeaderColor,
        toast: () => "Tile Header color updated"
      }
    },
    preferredColor: {
      type: "color",
      text: "Preferred",
      config: "color.preferred",
      before: "hr",
      effects: {
        custom: effectPreferredColor,
        toast: () => "Preferred color updated"
      }
    },
    preferredTextColor: {
      type: "color",
      text: "Preferred Text",
      config: "color.preferredText",
      effects: {
        custom: effectPreferredTextColor,
        toast: () => "Preferred Text color updated"
      }
    },
    excludedColor: {
      type: "color",
      text: "Excluded",
      config: "color.excluded",
      effects: {
        custom: effectExcludedColor,
        toast: () => "Excluded color updated"
      }
    },
    excludedTextColor: {
      type: "color",
      text: "Excluded Text",
      config: "color.excludedText",
      effects: {
        custom: effectExcludedTextColor,
        toast: () => "Excluded Text color updated"
      }
    },
    neutralColor: {
      type: "color",
      text: "Neutral",
      config: "color.neutral",
      effects: {
        custom: effectNeutralColor,
        toast: () => "Neutral color updated"
      }
    },
    neutralTextColor: {
      type: "color",
      text: "Neutral Text",
      config: "color.neutralText",
      effects: {
        custom: effectNeutralTextColor,
        toast: () => "Neutral Text color updated"
      }
    }
  };

  // src/renderer/applyEffects.js
  function applyEffects(meta, value) {
    if (meta.effects?.toast) {
      showToast(meta.effects.toast(value));
    }
    meta.effects?.custom?.(value);
  }

  // src/meta/overlaySettings.js
  var overlaySettingsMeta = {
    _header_visibility: {
      type: "header",
      text: "Overlay Visibility Settings"
    },
    completed: {
      type: "toggle",
      text: "Completed",
      tooltip: "Show overlay for completed threads",
      config: "overlaySettings.completed",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Completed ${v ? "enabled" : "disabled"}`
      }
    },
    onhold: {
      type: "toggle",
      text: "On Hold",
      tooltip: "Show overlay for threads on hold",
      config: "overlaySettings.onhold",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `On Hold ${v ? "enabled" : "disabled"}`
      }
    },
    abandoned: {
      type: "toggle",
      text: "Abandoned",
      tooltip: "Show overlay for abandoned threads",
      config: "overlaySettings.abandoned",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Abandoned ${v ? "enabled" : "disabled"}`
      }
    },
    highVersion: {
      type: "toggle",
      text: "High Version tag",
      tooltip: "Show overlay for game threads with higher version than your set minimum",
      config: "overlaySettings.highVersion",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `High Version ${v ? "enabled" : "disabled"}`
      }
    },
    invalidVersion: {
      type: "toggle",
      text: "Invalid Version tag",
      tooltip: "Show overlay for threads with invalid version format",
      config: "overlaySettings.invalidVersion",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Invalid Version ${v ? "enabled" : "disabled"}`
      }
    },
    preferred: {
      type: "toggle",
      text: "Preferred",
      tooltip: "Show overlay for threads you've marked as preferred",
      config: "overlaySettings.preferred",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Preferred ${v ? "enabled" : "disabled"}`
      }
    },
    excluded: {
      type: "toggle",
      text: "Excluded",
      tooltip: "Show overlay for threads you've marked as excluded",
      config: "overlaySettings.excluded",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Excluded ${v ? "enabled" : "disabled"}`
      }
    },
    overlayText: {
      type: "toggle",
      text: "Text overlay on tiles",
      tooltip: "Display status text directly over the thread thumbnail",
      config: "overlaySettings.overlayText",
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Overlay Text ${v ? "enabled" : "disabled"}`
      }
    }
  };
  var disabledOverlaySettingsMeta = {
    _header_visibility: {
      type: "header",
      text: "Overlay Settings is Disabled"
    }
  };

  // src/meta/latestSettings.js
  var effectOverlayToggle = () => {
    checkOverlaySettings();
    queuedUpdateLatestUI();
    if (!state.isLatest) return;
    if (!config.latestSettings.latestOverlayToggle) {
      queuedResetAllTiles();
    } else {
      queuedProcessAllTilesReset();
    }
  };
  var latestSettingsMeta = {
    autoRefresh: {
      type: "toggle",
      text: "Auto Refresh",
      tooltip: "Auto activate in site auto refresh for the Latest Updates page",
      config: "latestSettings.autoRefresh",
      effects: {
        custom: () => {
          state.isLatest && handleWebClick();
        },
        toast: (v) => `Auto Refresh ${v ? "enabled" : "disabled"}`
      }
    },
    webNotif: {
      type: "toggle",
      text: "Web Notifications",
      tooltip: "Auto activate in site web notifications for new threads (site might ask for permission)",
      config: "latestSettings.webNotif",
      effects: {
        custom: () => {
          state.isLatest && handleWebClick();
        },
        toast: (v) => `Web Notifications ${v ? "enabled" : "disabled"}`
      }
    },
    minVersion: {
      type: "number",
      text: "Minimum version overlay",
      tooltip: "Show overlay if thread version is below this value (e.g., 0.5 = version 0.5)",
      config: "latestSettings.minVersion",
      input: {
        min: 0,
        step: 0.1
      },
      effects: {
        custom: queuedProcessAllTilesReset,
        toast: (v) => `Min Version set to ${v}`
      }
    },
    wideLatest: {
      type: "toggle",
      text: "Wide Latest Page",
      tooltip: "Remove width limit on the Latest Updates page",
      config: "latestSettings.wideLatest",
      effects: {
        custom: () => {
          state.isLatest && toggleWideLatestPage();
        },
        toast: (v) => `Wide Latest Page ${v ? "enabled" : "disabled"}`
      }
    },
    denseLatestGrid: {
      type: "toggle",
      text: "Dense Latest Grid",
      tooltip: "Reduce spacing between thread tiles on the Latest Updates page",
      config: "latestSettings.denseLatestGrid",
      effects: {
        custom: () => {
          state.isLatest && toggleDenseLatestGrid();
        },
        toast: (v) => `Dense Latest Grid ${v ? "enabled" : "disabled"}`
      }
    },
    latestOverlayToggle: {
      type: "toggle",
      text: "Enable overlay",
      tooltip: "Show thread status overlay on the Latest Updates page",
      config: "latestSettings.latestOverlayToggle",
      effects: {
        custom: effectOverlayToggle,
        toast: (v) => `Latest page overlay ${v ? "enabled" : "disabled"}`
      }
    }
  };

  // src/cores/metrics.js
  function recordSuccess(img, duration, updateToast2) {
    config.metrics.succeeded++;
    config.metrics.avgCache = (config.metrics.avgCache * (config.metrics.succeeded - 1) + duration) / config.metrics.succeeded;
    config.metrics.highest = Math.max(config.metrics.highest, duration);
    config.metrics.lowest = Math.min(config.metrics.lowest, duration);
    config.metrics.mean = (config.metrics.highest + config.metrics.lowest) / 2;
    saveConfigKeys({ metrics: config.metrics });
    if (updateToast2) updateToast2();
  }
  function recordFail(updateToast2) {
    config.metrics.failed++;
    saveConfigKeys({ metrics: config.metrics });
    if (updateToast2) updateToast2();
  }

  // src/utils/notify.js
  function notify(title, body) {
    if (!("Notification" in window)) return;
    if (Notification.permission === "granted") {
      new Notification(title, { body });
    } else if (Notification.permission !== "denied") {
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") new Notification(title, { body });
      });
    }
  }
  function notifyAllDone() {
    notify("Images Reloaded", "\u2705 All images have finished reloading.");
  }
  function notifyMaxAttempts(max) {
    notify("Reload Warning", `\u26A0\uFE0F Some images failed to reload after ${max} attempts. You may need to refresh.`);
  }

  // src/ui/toast.js
  var isUpdating = false;
  var pendingUpdate = false;
  var imgRetryTimeoutContainer;
  function updateToast(retryingImages, metrics2) {
    const imgRetryToastEl = document.querySelector(".img-retry-toast");
    if (!imgRetryToastEl) return;
    if (isUpdating) {
      pendingUpdate = true;
      return;
    }
    isUpdating = true;
    if (retryingImages.size === 0) {
      imgRetryToastEl.style.display = "none";
    } else {
      imgRetryToastEl.style.display = "flex";
      imgRetryToastEl.querySelector(".img-retry-count").textContent = retryingImages.size;
      imgRetryToastEl.querySelector(".img-retry-plural").textContent = retryingImages.size > 1 ? "s" : "";
      imgRetryToastEl.querySelector(".img-retry-succeeded").textContent = metrics2.succeeded;
      imgRetryToastEl.querySelector(".img-retry-failed").textContent = metrics2.failed;
      imgRetryToastEl.querySelector(".img-retry-avg").textContent = metrics2.avgCache.toFixed(1);
    }
    imgRetryTimeoutContainer = setTimeout(() => {
      isUpdating = false;
      if (pendingUpdate) {
        pendingUpdate = false;
        updateToast(retryingImages, metrics2);
      }
    }, 500);
  }

  // src/cores/retryLogic.js
  var notifInfo = {
    isErrorNotified: false,
    isCompleteNotified: false
  };
  function retryImage(img, start, retryingImages, MAX_ATTEMPTS, RETRY_DELAY) {
    function markDone(success = true, duration = null) {
      retryingImages.delete(img);
      img.dataset.retrying = "false";
      img.dataset.retryAttached = "true";
      if (success) recordSuccess(img, duration, () => updateToast(retryingImages, config.metrics));
      else recordFail(() => updateToast(retryingImages, config.metrics));
      updateToast(retryingImages, config.metrics);
      if (retryingImages.size === 0 && !notifInfo.isCompleteNotified) {
        notifInfo.isCompleteNotified = true;
        notifyAllDone();
      }
    }
    function doRetry(attempt = 0) {
      config.metrics.retried++;
      retryingImages.add(img);
      updateToast(retryingImages, config.metrics);
      img.src = img.dataset.originalSrc + "?retry=" + Date.now();
      setTimeout(() => {
        if (img.complete && img.naturalWidth > 0) {
          const duration = performance.now() - start;
          markDone(true, duration);
          return;
        }
        if (attempt >= MAX_ATTEMPTS && !notifInfo.isErrorNotified) {
          notifInfo.isErrorNotified = true;
          markDone(false);
          notifyMaxAttempts(MAX_ATTEMPTS);
        }
        doRetry(attempt + 1);
      }, RETRY_DELAY);
    }
    doRetry(0);
  }

  // src/template/imgRetryUi.html?raw
  var imgRetryUi_default = '<div id="img-retry-toast" class="img-retry-toast">\r\n  <span class="img-retry-spinner"></span>\r\n  Retrying <span class="img-retry-count">0</span> image<span class="img-retry-plural"></span>...\r\n  <div class="img-retry-stats">\r\n    Success: <span class="img-retry-succeeded">0</span> | Fail:\r\n    <span class="img-retry-failed">0</span> | Avg: <span class="img-retry-avg">0</span> ms\r\n  </div>\r\n</div>\r\n';

  // src/ui/imgRetryUi.js
  function injectUI() {
    if (document.getElementById("img-retry-toast")) return;
    const wrapper = document.createElement("div");
    wrapper.id = "image-retry-toast-wrapper";
    wrapper.innerHTML = imgRetryUi_default;
    document.body.appendChild(wrapper);
    const toastEl = document.getElementById("img-retry-toast");
    if (toastEl) {
      toastEl.style.display = "none";
    }
  }
  function destroyInjectedUI() {
    const wrapper = document.getElementById("image-retry-toast-wrapper");
    if (wrapper) {
      wrapper.remove();
    }
    const toastEl = document.getElementById("img-retry-toast");
    if (toastEl) {
      toastEl.style.display = "none";
      toastEl.remove();
    }
  }

  // src/cores/observer.js
  function observeDom(callback) {
    new MutationObserver(callback).observe(document.body, {
      childList: true,
      subtree: true
    });
  }

  // src/cores/imageHandler.js
  var domObserver = null;
  function injectImageRepair() {
    if (!config.threadSettings.imgRetry) {
      destroyImageRepair();
      return;
    }
    if (config.isImgRetryInjected) return;
    config.isImgRetryInjected = true;
    const retryingImages = /* @__PURE__ */ new Set();
    function initImageRetry() {
      document.querySelectorAll("img").forEach((img) => handleImage(img, retryingImages));
      updateToast(retryingImages, config.metrics);
    }
    injectUI();
    initImageRetry();
    domObserver = observeDom(initImageRetry);
  }
  function handleImage(img, retryingImages) {
    if (!config.threadSettings.imgRetry || img.dataset.retryAttached || !img.src.startsWith("https://attachments.f95zone.to/")) {
      return;
    }
    img.dataset.originalSrc = img.dataset.originalSrc || img.src;
    const start = performance.now();
    const MAX_ATTEMPTS = 10;
    const RETRY_DELAY = 4e3;
    function handleSuccess() {
      const duration = performance.now() - start;
      retryingImages.delete(img);
      img.dataset.retrying = "false";
      img.dataset.retryAttached = "true";
      recordSuccess(img, duration);
      config.metrics.succeeded++;
      updateToast(retryingImages, config.metrics);
    }
    function handleError() {
      if (img.dataset.retrying !== "true") {
        img.dataset.retrying = "true";
        retryImage(img, start, retryingImages, MAX_ATTEMPTS, RETRY_DELAY);
      }
    }
    if (img.complete) {
      if (img.naturalWidth > 0) handleSuccess();
      else handleError();
    } else {
      img.addEventListener("load", handleSuccess, { once: true });
      img.addEventListener("error", handleError, { once: true });
    }
  }
  function destroyImageRepair() {
    if (!config.isImgRetryInjected) return;
    config.isImgRetryInjected = false;
    destroyInjectedUI();
    if (domObserver) {
      domObserver.disconnect();
      domObserver = null;
    }
  }

  // src/ui/wideForum.js
  function wideForum() {
    if (!state.isThread) return;
    const isWide = !!config.threadSettings.isWide;
    const root = document.documentElement;
    document.querySelectorAll(".p-body-inner").forEach((el) => el.classList.toggle("no-max-width", isWide));
    if (isWide) {
      root.classList.add("thread-scroll-hide");
      enableThreadHeaderScroll();
    } else {
      root.classList.remove("thread-scroll-hide");
      disableThreadHeaderScroll();
    }
  }
  var threadScrollHandler = null;
  function enableThreadHeaderScroll() {
    if (threadScrollHandler) return;
    let lastScrollY = window.scrollY;
    threadScrollHandler = () => {
      const root = document.documentElement;
      const currentY = window.scrollY;
      if (currentY > lastScrollY && currentY > 120) {
        root.classList.add("thread-header-hidden");
      } else {
        root.classList.remove("thread-header-hidden");
      }
      lastScrollY = currentY;
    };
    window.addEventListener("scroll", threadScrollHandler, { passive: true });
  }
  function disableThreadHeaderScroll() {
    if (!threadScrollHandler) return;
    window.removeEventListener("scroll", threadScrollHandler);
    threadScrollHandler = null;
    document.documentElement.classList.remove("thread-header-hidden");
  }

  // src/meta/threadSettings.js
  var effectOverlayToggle2 = () => {
    updateThreadUI();
    checkOverlaySettings();
    if (!state.isThread) return;
    queuedProcessThreadTags();
  };
  var threadSettingsMeta = {
    skipMaskedLink: {
      type: "toggle",
      text: "Skip masked link page",
      tooltip: "Automatically bypass the masked link intermediary page when accessing masked links",
      config: "threadSettings.skipMaskedLink",
      effects: {
        custom: () => toggleHijackMaskedLink(),
        toast: (v) => `Skip Masked Link ${v ? "enabled" : "disabled"}`
      }
    },
    isWide: {
      type: "toggle",
      text: "Wide thread (full width)",
      tooltip: "Remove max-width restriction \u2014 makes thread use full screen width",
      config: "threadSettings.isWide",
      effects: {
        custom: wideForum,
        toast: (v) => `Wide Thread ${v ? "enabled" : "disabled"}`
      }
    },
    imgRetry: {
      type: "toggle",
      text: "Image Retry",
      tooltip: "Enable image retry for broken images in threads",
      config: "threadSettings.imgRetry",
      effects: {
        custom: injectImageRepair,
        toast: (v) => `Image Retry ${v ? "enabled" : "disabled"}`
      }
    },
    collapseableSignatures: {
      type: "toggle",
      text: "Collapsable Signatures",
      tooltip: "Make user signatures collapsable in threads",
      config: "threadSettings.collapseSignature",
      effects: {
        custom: signatureCollapse,
        toast: (v) => `Collapsable Signatures ${v ? "enabled" : "disabled"}`
      }
    },
    threadOverlayToggle: {
      type: "toggle",
      text: "Enable overlay",
      tooltip: "Show thread status overlay on thread pages",
      config: "threadSettings.threadOverlayToggle",
      effects: {
        custom: effectOverlayToggle2,
        toast: (v) => `Thread overlay ${v ? "enabled" : "disabled"}`
      }
    }
  };
  var threadOverlaySettingsMeta = {
    _header_visibility: {
      type: "header",
      text: "Thread Overlay Settings"
    },
    neutral: {
      type: "toggle",
      text: "Show Neutral overlay",
      tooltip: "Display neutral reaction buttons",
      config: "threadSettings.neutral",
      effects: {
        custom: queuedProcessThreadTags,
        toast: (v) => `Neutral ${v ? "enabled" : "disabled"}`
      }
    },
    preferred: {
      type: "toggle",
      text: "Show Preferred overlay",
      tooltip: "Display your preferred (favorited) overlay",
      config: "threadSettings.preferred",
      effects: {
        custom: queuedProcessThreadTags,
        toast: (v) => `Preferred ${v ? "enabled" : "disabled"}`
      }
    },
    preferredShadow: {
      type: "toggle",
      text: "Preferred overlay shadow",
      tooltip: "Add a subtle shadow effect to preferred overlay",
      config: "threadSettings.preferredShadow",
      effects: {
        custom: queuedProcessThreadTags,
        toast: (v) => `Preferred Shadow ${v ? "enabled" : "disabled"}`
      }
    },
    excluded: {
      type: "toggle",
      text: "Show Excluded overlay",
      tooltip: "Show overlay you've excluded",
      config: "threadSettings.excluded",
      effects: {
        custom: queuedProcessThreadTags,
        toast: (v) => `Excluded ${v ? "enabled" : "disabled"}`
      }
    },
    excludedShadow: {
      type: "toggle",
      text: "Show excluded overlay shadow",
      tooltip: "Add shadow to excluded overlay",
      config: "threadSettings.excludedShadow",
      effects: {
        custom: queuedProcessThreadTags,
        toast: (v) => `Excluded Shadow ${v ? "enabled" : "disabled"}`
      }
    }
  };
  var disabledThreadOverlayMeta = {
    _header_visibility: {
      type: "header",
      text: "Thread Overlay is disabled"
    }
  };

  // src/meta/metaRegistry.js
  var metaRegistry = {
    overlaySettings: overlaySettingsMeta,
    color: colorSettingsMeta,
    latestSettings: latestSettingsMeta,
    threadSettings: threadSettingsMeta
  };

  // src/storage/crossTabSync.js
  function initCrossTabSync() {
    Object.keys(crossTabKeys).forEach((key) => {
      GM_addValueChangeListener(key, (name, oldVal, newVal, remote) => {
        if (!remote) return;
        if (!config.latestSettings.enableCrossTabSync) return;
        state.isCrossTabSyncInitialized = true;
        handleSectionChange(key, oldVal, newVal);
      });
    });
  }
  function handleSectionChange(section, oldVal = {}, newVal = {}) {
    const metaMap = metaRegistry[section];
    if (!metaMap) return;
    Object.keys(newVal).forEach((subKey) => {
      if (oldVal?.[subKey] === newVal[subKey]) return;
      config[section][subKey] = newVal[subKey];
      const meta = metaMap[subKey];
      if (!meta) return;
      applyEffects(meta, newVal[subKey]);
    });
  }
  function disableCrossTabSync() {
    if (!state.isCrossTabSyncInitialized) return;
    Object.keys(crossTabKeys).forEach((key) => {
      GM_removeValueChangeListener(key);
    });
    state.isCrossTabSyncInitialized = false;
  }
  function toggleCrossTabSync(enabled) {
    if (enabled) {
      initCrossTabSync();
    } else {
      disableCrossTabSync();
    }
  }

  // src/meta/globalSettings.js
  var globalSettingsMeta = {
    configVisibility: {
      type: "toggle",
      text: "Show configuration button",
      tooltip: "Show or hide the configuration button on the page",
      config: "globalSettings.configVisibility",
      effects: {
        custom: updateButtonVisibility,
        toast: (v) => `Configuration menu ${v ? "shown" : "hidden"}`
      }
    },
    enableCrossTabSync: {
      type: "toggle",
      text: "Sync settings across tabs",
      tooltip: "Automatically apply changes made in other tabs(requires to refresh other tabs) experimental",
      config: "globalSettings.enableCrossTabSync",
      effects: {
        custom: () => {
          toggleCrossTabSync(config.globalSettings.enableCrossTabSync);
        },
        toast: (v) => `(experimental)Cross-tab settings sync ${v ? "enabled" : "disabled"}`
      }
    }
  };

  // src/renderer/searchTags.js
  function renderList(filteredTags) {
    const results = document.getElementById("search-results");
    const input = document.getElementById("tags-search");
    if (!results || !input) return;
    results.innerHTML = "";
    const visibleTags = filteredTags.filter(
      (tag) => !config.preferredTags.includes(tag.id) && !config.excludedTags.includes(tag.id)
    );
    if (visibleTags.length === 0) {
      results.style.display = "none";
      return;
    }
    visibleTags.forEach((tag) => {
      results.appendChild(createTagResultItem(tag, input, results));
    });
    results.style.display = "block";
  }
  function createTagResultItem(tag, input, results) {
    const li = document.createElement("li");
    li.className = "search-result-item";
    const nameSpan = document.createElement("span");
    nameSpan.textContent = tag.name;
    const actions = document.createElement("div");
    actions.className = "tag-actions";
    actions.appendChild(
      createActionButton(
        "\u2713",
        "Add to preferred",
        "preferred",
        () => {
          if (!config.preferredTags.includes(tag.id)) {
            config.preferredTags.push(tag.id);
            renderPreferred();
            showToast(`${tag.name} added to preferred`);
            saveConfigKeys({ preferredTags: config.preferredTags });
            queuedProcessAllTilesReset();
            queuedProcessThreadTags();
          }
        },
        input,
        results
      )
    );
    actions.appendChild(
      createActionButton(
        "\u2717",
        "Add to excluded",
        "excluded",
        () => {
          if (!config.excludedTags.includes(tag.id)) {
            config.excludedTags.push(tag.id);
            renderExcluded();
            showToast(`${tag.name} added to exclusion`);
            saveConfigKeys({ excludedTags: config.excludedTags });
            queuedProcessAllTilesReset();
            queuedProcessThreadTags();
          }
        },
        input,
        results
      )
    );
    li.appendChild(nameSpan);
    li.appendChild(actions);
    return li;
  }
  function createActionButton(text, title, typeClass, onClick, input, results) {
    const btn = document.createElement("button");
    btn.textContent = text;
    btn.title = title;
    btn.className = `tag-btn ${typeClass}`;
    btn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      onClick();
      input.value = "";
      results.style.display = "none";
    });
    return btn;
  }
  function renderPreferred() {
    renderTagList({
      containerId: "preffered-tags-list",
      ids: config.preferredTags,
      itemClass: "preferred-tag-item",
      removeBtnClass: "preferred-tag-remove",
      onRemove: (index, tag) => {
        config.preferredTags.splice(index, 1);
        showToast(`${tag.name} removed from preferred`);
        saveConfigKeys({ preferredTags: config.preferredTags });
        queuedProcessAllTilesReset();
        queuedProcessThreadTags();
      }
    });
  }
  function renderExcluded() {
    renderTagList({
      containerId: "excluded-tags-list",
      ids: config.excludedTags,
      itemClass: "excluded-tag-item",
      removeBtnClass: "excluded-tag-remove",
      onRemove: (index, tag) => {
        config.excludedTags.splice(index, 1);
        showToast(`${tag.name} removed from exclusion`);
        saveConfigKeys({ excludedTags: config.excludedTags });
        queuedProcessAllTilesReset();
        queuedProcessThreadTags();
      }
    });
  }
  function renderTagList({ containerId, ids, itemClass, removeBtnClass, onRemove }) {
    const container = document.getElementById(containerId);
    if (!container) return;
    container.innerHTML = "";
    ids.forEach((id, index) => {
      const tag = config.tags.find((t) => t.id === id);
      if (!tag) return;
      container.appendChild(createTagListItem(tag, index, itemClass, removeBtnClass, onRemove));
    });
  }
  function createTagListItem(tag, index, itemClass, removeBtnClass, onRemove) {
    const item = document.createElement("div");
    item.className = `tag-list-item ${itemClass}`;
    const text = document.createElement("span");
    text.textContent = tag.name;
    const removeBtn = document.createElement("button");
    removeBtn.textContent = "X";
    removeBtn.className = `tag-remove-btn ${removeBtnClass}`;
    removeBtn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      onRemove(index, tag);
      if (itemClass.includes("preferred")) renderPreferred();
      else renderExcluded();
    });
    item.appendChild(text);
    item.appendChild(removeBtn);
    return item;
  }

  // src/helper/rendererHelper.js
  function getByPath(obj, path) {
    return path.split(".").reduce((o, k) => o?.[k], obj);
  }
  function setByPath(obj, path, value) {
    const keys = path.split(".");
    const last = keys.pop();
    const target = keys.reduce((o, k) => o[k], obj);
    target[last] = value;
  }

  // src/renderer/createInput.js
  function createInput(meta, id) {
    const input = document.createElement("input");
    input.id = id;
    switch (meta.type) {
      case "toggle":
        input.type = "checkbox";
        break;
      case "number":
        input.type = "number";
        Object.assign(input, meta.input);
        break;
      case "color":
        input.type = "color";
        break;
      default:
        throw new Error(`Unknown input type: ${meta.type}`);
    }
    return input;
  }

  // src/renderer/createLabel.js
  function createLabel(meta, id) {
    const label = document.createElement("label");
    label.htmlFor = id;
    label.textContent = meta.text;
    if (meta.tooltip) {
      label.title = meta.tooltip;
    }
    return label;
  }

  // src/renderer/renderSetting.js
  function renderSetting(key, meta) {
    if (meta.type === "header") {
      const header = document.createElement("div");
      header.className = "config-header";
      header.textContent = meta.text;
      return header;
    }
    if (meta.type === "separator") {
      const hr = document.createElement("hr");
      hr.className = "config-separator";
      return hr;
    }
    const row = document.createElement("div");
    row.className = "config-row";
    const id = `setting-${key}`;
    const label = createLabel(meta, id);
    const input = createInput(meta, id);
    const value = getByPath(config, meta.config);
    if (meta.type === "toggle") {
      input.checked = Boolean(value);
    } else {
      input.value = value;
    }
    input.addEventListener("change", () => {
      const newValue = meta.type === "toggle" ? input.checked : input.value;
      setByPath(config, meta.config, newValue);
      saveConfigKeys(config);
      applyEffects(meta, newValue);
    });
    row.appendChild(label);
    row.appendChild(input);
    return row;
  }

  // src/renderer/settingsSection.js
  function renderSettingsSection(containerId, metaMap) {
    const container = document.getElementById(containerId);
    debug && console.log("Rendering settings section:", containerId, metaMap);
    if (!container) {
      debug && console.warn("Container not found:", containerId);
      return;
    }
    container.innerHTML = "";
    Object.entries(metaMap).forEach(([key, meta]) => {
      container.appendChild(renderSetting(key, meta));
    });
  }

  // src/cores/tags.js
  function updateSearch(event) {
    const query = event.target.value.trim().toLowerCase();
    const results = document.getElementById("search-results");
    if (!query || !results) {
      if (results) results.style.display = "none";
      return;
    }
    const filteredTags = config.tags.filter((tag) => tag.name.toLowerCase().includes(query));
    renderList(filteredTags);
  }
  function showAllTags() {
    const results = document.getElementById("search-results");
    if (!results) return;
    renderList(config.tags);
    results.style.display = "block";
  }

  // src/renderer/reRenderSetting.js
  function reRenderSettingsSection(containerId, meta) {
    if (!clearContainer(containerId)) return;
    renderSettingsSection(containerId, meta);
  }
  function clearContainer(id) {
    const el = document.getElementById(id);
    if (el) {
      el.innerHTML = "";
      return true;
    }
    return false;
  }

  // src/ui/listeners.js
  function injectListener() {
    setEventById("tags-search", updateSearch, "input");
    setEventById("close-modal", closeModal);
    setEventById("tags-search", showAllTags, "focus");
    setEventById("reset-color", resetColor);
    document.addEventListener("click", (e) => {
      const input = document.getElementById("tags-search");
      const results = document.getElementById("search-results");
      if (!input || !results) return;
      if (!input.contains(e.target) && !results.contains(e.target)) {
        results.style.display = "none";
      }
    });
  }
  function setEventById(idSelector, callback, eventType = "click") {
    const el = document.getElementById(idSelector);
    if (el) {
      el.addEventListener(eventType, callback);
    } else {
      console.warn(`setEventById: element with id "${idSelector}" not found.`);
    }
  }
  function resetColor() {
    if (confirm("Are you sure you want to reset all colors to default?")) {
      config.color = { ...defaultColors };
      updateColorStyle();
      saveConfigKeys({ color: config.color });
      if (config.latestSettings.latestOverlayToggle && state.isLatest) {
        queuedProcessAllTilesReset();
      } else if (config.threadSettings.threadOverlayToggle && state.isThread) {
        queuedProcessThreadTags();
      }
      reRenderSettingsSection("color-container", colorSettingsMeta);
      showToast("Colors have been reset to default");
    }
  }

  // src/cores/init.js
  function initUI() {
    injectCSS();
    injectButton();
    updateColorStyle();
  }
  function initModalUi() {
    if (!state.modalInjected) {
      state.modalInjected = true;
      injectModal();
      injectListener();
    }
    if (!state.globalSettingsRendered) {
      state.globalSettingsRendered = true;
      renderSettingsSection("global-settings-container", globalSettingsMeta);
    }
    if (!state.colorRendered) {
      state.colorRendered = true;
      renderSettingsSection("color-container", colorSettingsMeta);
    }
    if (!state.overlayRendered) {
      state.overlayRendered = true;
      updateLatestUI();
    }
    if (!state.threadSettingsRendered) {
      state.threadSettingsRendered = true;
      updateThreadUI();
    }
    renderPreferred();
    renderExcluded();
    updateTags();
    checkTags();
  }
  function updateLatestUI() {
    renderSettingsSection("latest-settings-container", latestSettingsMeta);
    if (config.latestSettings.latestOverlayToggle) {
      renderSettingsSection("overlay-settings-container", overlaySettingsMeta);
    } else {
      renderSettingsSection("overlay-settings-container", disabledOverlaySettingsMeta);
    }
  }
  function updateThreadUI() {
    renderSettingsSection("thread-settings-container", threadSettingsMeta);
    if (config.threadSettings.threadOverlayToggle) {
      renderSettingsSection("thread-overlay-settings-container", threadOverlaySettingsMeta);
    } else {
      renderSettingsSection("thread-overlay-settings-container", disabledThreadOverlayMeta);
    }
  }
  async function initLatestPage() {
    try {
      await waitFor(() => document.getElementById("latest-page_items-wrap"));
      if (config.latestSettings.wideLatest) toggleWideLatestPage();
      watchAndUpdateTiles();
      if (config.latestSettings.denseLatestGrid) toggleDenseLatestGrid();
      processAllTiles();
      handleWebClick();
    } catch {
      console.warn("Observer container not found on latest page");
    }
  }
  function initThreadPage() {
    if (config.threadSettings.threadOverlayToggle) processThreadTags();
    if (config.threadSettings.isWide) wideForum();
    if (config.threadSettings.imgRetry) injectImageRepair();
    if (config.threadSettings.collapseSignature) signatureCollapse();
    if (config.threadSettings.skipMaskedLink) hijackMaskedLinks();
  }
  function initPageState() {
    if (state.isLatest) initLatestPage();
    if (state.isThread) initThreadPage();
  }

  // src/ui/modal.js
  function injectButton() {
    const button = document.createElement("button");
    button.textContent = "\u2699";
    button.id = "tag-config-button";
    button.addEventListener("click", () => openModal());
    document.body.appendChild(button);
  }
  var MAX_TOASTS = 4;
  var TOAST_DURATION = 2e3;
  function showToast(message, duration = TOAST_DURATION) {
    let container = document.getElementById("toast-container");
    if (!container) {
      container = document.createElement("div");
      container.id = "toast-container";
      document.body.appendChild(container);
    }
    const toast = document.createElement("div");
    toast.className = "toast";
    toast.textContent = message;
    container.appendChild(toast);
    while (container.children.length > MAX_TOASTS) {
      container.firstElementChild.remove();
    }
    requestAnimationFrame(() => toast.classList.add("show"));
    setTimeout(() => {
      toast.classList.remove("show");
      setTimeout(() => toast.remove(), 200);
    }, duration);
  }
  function openModal() {
    initModalUi();
    document.getElementById("tag-config-modal").style.display = "block";
  }
  function closeModal() {
    document.getElementById("tag-config-modal").style.display = "none";
  }
  function injectModal() {
    const modal = document.createElement("div");
    modal.id = "tag-config-modal";
    modal.innerHTML = `${ui_default}`;
    document.body.appendChild(modal);
    const visibility = document.getElementById("config-visibility");
    if (visibility) visibility.checked = config.configVisibility;
    const modalContent = modal.querySelector(".modal-content");
    modal.addEventListener("click", (e) => {
      if (!modalContent.contains(e.target)) {
        closeModal();
      }
    });
  }
  function injectCSS() {
    const hasStyle = document.head.lastElementChild.textContent.includes("#tag-config-button");
    const customCSS = hasStyle ? document.head.lastElementChild : document.createElement("style");
    customCSS.textContent = `${css_default}`;
    document.head.appendChild(customCSS);
  }
  function updateButtonVisibility() {
    const button = document.getElementById("tag-config-button");
    if (!button) return;
    if (config.globalSettings.configVisibility === false) {
      button.classList.add("blink-hide", "hover-reveal");
      const onEnd = () => {
        button.classList.remove("blink-hide");
        button.classList.add("hidden");
        button.removeEventListener("animationend", onEnd);
      };
      button.addEventListener("animationend", onEnd);
    } else {
      button.classList.remove("hidden", "blink-hide", "hover-reveal");
    }
  }

  // src/main.js
  waitForBody(async () => {
    Object.assign(config, await loadData());
    migrateLatestSettings();
    detectPage();
    if (state.isMaskedLink) {
      if (config.threadSettings.skipMaskedLink) skipMaskedPage();
      return;
    }
    initUI();
    updateButtonVisibility();
    toggleCrossTabSync(config.globalSettings.enableCrossTabSync);
    initPageState();
  });
})();