Ripper.Store アセットハンター

Ripper.Store のスレッド内のダウンロードリンク、関連トピック、外部URLを自動スキャン。階層ハイライトの色/不透明度をカスタマイズ、スキャン範囲を設定、3つのカテゴリタブで結果表示、7言語切り替え、ワンクリックで階層/リンクへジャンプ、大サムネイルとホバープレビュー、階層角丸、パネルテーマ(色/不透明度)を完全カスタマイズ。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name      Ripper.Store Assets Hunter
// @name:zh-CN      Ripper.Store 资源猎手
// @name:zh-TW      Ripper.Store 資源獵手
// @name:ja      Ripper.Store アセットハンター
// @name:ko      Ripper.Store 에셋 헌터
// @name:ru      Ripper.Store Охотник за активами
// @name:es      Ripper.Store Cazador de activos
// @name:fr      Ripper.Store Chasseur d'actifs
// @version      2.0.4
// @description          Automatically scans download links, related topics, and external URLs in Ripper.Store threads. Customize floor highlight color & opacity, set scan range, view results in 3 categorized tabs, switch between 7 languages, jump to any floor or link with one click, enable large thumbnails with hover preview, round floor borders, and fully personalize the panel theme (colors & opacity).
// @description:zh-CN   自动扫描 Ripper.Store 帖子中的下载链接、相关主题及外部链接。支持自定义楼层高亮颜色/不透明度、设置扫描范围、三标签分类结果、七语言切换、一键跳转楼层或链接、大缩略图悬停预览、楼层圆角美化,以及面板颜色/不透明度全面定制。
// @description:zh-TW   自動掃描 Ripper.Store 帖子中的下載連結、相關主題及外部連結。支援自訂樓層高亮顏色/不透明度、設定掃描範圍、三標籤分類結果、七語言切換、一鍵跳轉樓層或連結、大縮略圖懸停預覽、樓層圓角美化,以及面板顏色/不透明度全面自訂。
// @description:ja       Ripper.Store のスレッド内のダウンロードリンク、関連トピック、外部URLを自動スキャン。階層ハイライトの色/不透明度をカスタマイズ、スキャン範囲を設定、3つのカテゴリタブで結果表示、7言語切り替え、ワンクリックで階層/リンクへジャンプ、大サムネイルとホバープレビュー、階層角丸、パネルテーマ(色/不透明度)を完全カスタマイズ。
// @description:ko       Ripper.Store 스레드에서 다운로드 링크, 관련 주제 및 외부 URL을 자동 스캔합니다. 층 하이라이트 색상/불투명도 사용자 지정, 스캔 범위 설정, 3개 카테고리 탭으로 결과 보기, 7개 언어 전환, 원클릭 층/링크 점프, 큰 썸네일 및 호버 미리보기, 층 둥근 모서리, 패널 테마(색상/불투명도) 완전 맞춤 설정.
// @description:ru       Автоматически сканирует ссылки для скачивания, связанные темы и внешние URL на форуме Ripper.Store. Настраивайте цвет и прозрачность подсветки этажей, задавайте диапазон сканирования, просматривайте результаты в 3 вкладках, переключайте 7 языков, переходите на этаж или по ссылке в один клик, включайте большие миниатюры с предпросмотром, скругляйте границы этажей и полностью настраивайте тему панели (цвета и прозрачность).
// @description:es       Escanea automáticamente enlaces de descarga, temas relacionados y URLs externas en los hilos de Ripper.Store. Personaliza color/opacidad del resaltado de pisos, establece el rango de escaneo, visualiza resultados en 3 pestañas, cambia entre 7 idiomas, salta a cualquier piso o enlace con un clic, activa miniaturas grandes con vista previa al pasar el cursor, redondea bordes de pisos y personaliza completamente el tema del panel (colores y opacidad).
// @description:fr       Scanne automatiquement les liens de téléchargement, les sujets associés et les URL externes dans les fils Ripper.Store. Personnalisez la couleur/opacité de la surbrillance des étages, définissez la plage de scan, visualisez les résultats dans 3 onglets, basculez entre 7 langues, sautez à n'importe quel étage/lien en un clic, activez les grandes vignettes avec aperçu au survol, arrondissez les bordures d'étages et personnalisez entièrement le thème du panneau (couleurs et opacité).
// @author       Xuebao-Wang
// @license      MIT
// @match        *://forum.ripper.store/
// @match        *://forum.ripper.store/category/*
// @match        *://forum.ripper.store/topic/*
// @match        *://forum.ripper.store/search*
// @match        *://forum.ripper.store/tags/*
// @match        *://forum.ripper.store/recent
// @match        *://forum.ripper.store/popular
// @match        *://forum.ripper.store/unread
// @match        *://forum.ripper.store/notifications
// @match        *://forum.ripper.store/user/*/bookmarks
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      forum.ripper.store
// @run-at       document-idle
// @namespace https://greasyfork.org/users/1611380
// ==/UserScript==

(function () {
  "use strict";

  const CONFIG = {
    requestTimeout: 10000,
    resolveTimeout: 5000,
    resolveDelay: 300,
    resolveConcurrency: 3,
    scanDelay: 1500,
    debounceDelay: 300,
    highlightDuration: 2000,
    cacheMaxSize: 50,
    concurrentPages: 4,
    colors: {
      download: "#72f0a8",
      topic: "#9898ff",
      other: "#7dd3fc",
    },
  };

  const DEFAULT_THEME = {
    bg: "#0a0a0c",
    bgCard: "#121214",
    bgElevated: "#1a1a1e",
    border: "#222222",
    borderLight: "#333333",
    text: "#dddddd",
    textMuted: "#888888",
    primary: "#c084fc",
    panelOpacity: 95,
  };

  function getThemeColor(key) {
    const val = GM_getValue("ah-theme-" + key, null);
    if (val !== null) return val;
    return DEFAULT_THEME[key];
  }
  function setThemeColor(key, val) {
    GM_setValue("ah-theme-" + key, val);
  }
  function resetThemeColors() {
    Object.keys(DEFAULT_THEME).forEach((k) =>
      GM_setValue("ah-theme-" + k, DEFAULT_THEME[k]),
    );
  }
  function applyThemeCSS() {
    let style = document.getElementById("ah-theme-vars");
    if (!style) {
      style = document.createElement("style");
      style.id = "ah-theme-vars";
      document.head.appendChild(style);
    }
    const alpha = (getThemeColor("panelOpacity") || 95) / 100;
    style.textContent = `
      :root {
        --ah-bg: ${getThemeColor("bg")};
        --ah-bg-card: ${getThemeColor("bgCard")};
        --ah-bg-elevated: ${getThemeColor("bgElevated")};
        --ah-border: ${getThemeColor("border")};
        --ah-border-light: ${getThemeColor("borderLight")};
        --ah-text: ${getThemeColor("text")};
        --ah-text-muted: ${getThemeColor("textMuted")};
        --ah-primary: ${getThemeColor("primary")};
        --ah-panel-opacity: ${alpha};
      }
    `;
  }

  function getEnableLargeThumbs() {
    const val = GM_getValue("ah-large-thumbs", null);
    return val === null ? true : val === true || val === "true";
  }
  function setEnableLargeThumbs(val) {
    GM_setValue("ah-large-thumbs", !!val);
  }
  function getThumbnailScale() {
    const val = GM_getValue("ah-thumb-scale", null);
    return val === null ? 2.4 : Math.max(1, Math.min(8, parseFloat(val)));
  }
  function setThumbnailScale(val) {
    GM_setValue(
      "ah-thumb-scale",
      String(Math.max(1, Math.min(8, parseFloat(val)))),
    );
  }
  function getEnableHoverPreview() {
    const val = GM_getValue("ah-hover-preview", null);
    return val === null ? true : val === true || val === "true";
  }
  function setEnableHoverPreview(val) {
    GM_setValue("ah-hover-preview", !!val);
  }

  function getEnableFloorBorder() {
    const val = GM_getValue("ah-floor-border", null);
    return val === null ? false : val === true || val === "true";
  }
  function setEnableFloorBorder(val) {
    GM_setValue("ah-floor-border", !!val);
  }

  function getFloorBorderRadius() {
    const val = GM_getValue("ah-floor-radius", null);
    return val === null ? 12 : Math.max(0, Math.min(32, parseInt(val, 10)));
  }
  function setFloorBorderRadius(val) {
    GM_setValue(
      "ah-floor-radius",
      String(Math.max(0, Math.min(32, parseInt(val, 10)))),
    );
  }

  function getFloorBorderWidth() {
    const val = GM_getValue("ah-floor-border-width", null);
    return val === null ? 1 : Math.max(0, Math.min(4, parseInt(val, 10)));
  }
  function setFloorBorderWidth(val) {
    GM_setValue(
      "ah-floor-border-width",
      String(Math.max(0, Math.min(4, parseInt(val, 10)))),
    );
  }

  function getFloorBorderColor() {
    const val = GM_getValue("ah-floor-border-color", null);
    return val && /^#[0-9A-Fa-f]{6}$/.test(val) ? val : "#2a2a2e";
  }
  function setFloorBorderColor(val) {
    GM_setValue("ah-floor-border-color", val);
  }

  function applyFloorBorderStyles() {
    let style = document.getElementById("ah-floor-border-styles");
    if (!style) {
      style = document.createElement("style");
      style.id = "ah-floor-border-styles";
      document.head.appendChild(style);
    }
    const enabled = getEnableFloorBorder();
    const radius = getFloorBorderRadius();
    const width = getFloorBorderWidth();
    const color = getFloorBorderColor();
    if (!enabled) {
      style.textContent = `.ah-floor-card { /* disabled */ }`;
      return;
    }
    const primary = getThemeColor("primary") || "#c084fc";
    const pr = parseInt(primary.substring(1, 3), 16);
    const pg = parseInt(primary.substring(3, 5), 16);
    const pb = parseInt(primary.substring(5, 7), 16);
    const hoverColor = `rgba(${pr}, ${pg}, ${pb}, 0.35)`;
    style.textContent = `
      .ah-floor-card {
        padding: 1.2rem 1.15rem 1.15rem 3rem !important;
        margin-bottom: 1.15rem !important;
        border: ${width}px solid ${color} !important;
        border-radius: ${radius}px !important;
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12) !important;
        overflow: visible !important;
        transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
      }
      .ah-floor-card:hover {
        border-color: ${hoverColor} !important;
      }
      .ah-floor-card .post-container-parent {
        align-items: flex-start !important;
        gap: 0.9rem !important;
        margin-top: 0.1rem;
      }
      .ah-floor-card .post-header {
        flex-wrap: wrap !important;
        align-items: flex-start !important;
        gap: 0.45rem !important;
        overflow: visible;
        min-width: 0;
      }
      .ah-floor-card .post-header .flex-nowrap {
        flex-wrap: wrap !important;
      }
      .ah-floor-card .post-container {
        min-width: 0;
        overflow: visible;
      }
      .ah-floor-card [component="user/status"] {
        z-index: 1;
      }
      .ah-floor-card .avatar,
      .ah-floor-card [component="user/picture"],
      .ah-floor-card [component="user/avatar"] {
        border-radius: 50% !important;
        object-fit: cover !important;
      }
      .ah-floor-card [component="post/content"] img.img-markdown {
        border-radius: 0.85rem;
        border: 1px solid var(--ah-border);
        max-width: 100%;
        height: auto;
      }
    `;

    const applyToPost = (el) => {
      if (!el.closest('[component="post/replies/container"]')) {
        el.classList.add("ah-floor-card");
      }
    };
    document.querySelectorAll('[component="post"]').forEach(applyToPost);

    if (!window.__ahPostObserver) {
      window.__ahPostObserver = new MutationObserver((mutations) => {
        for (const m of mutations) {
          for (const node of m.addedNodes) {
            if (node.nodeType !== 1) continue;
            if (node.matches?.('[component="post"]')) applyToPost(node);
            node.querySelectorAll?.('[component="post"]')?.forEach(applyToPost);
          }
        }
      });
      const container =
        document.querySelector('[component="topic"]') ||
        document.querySelector("#content") ||
        document.body;
      window.__ahPostObserver.observe(container, {
        childList: true,
        subtree: true,
      });
    }
  }

  function getCSRFToken(cb) {
    GM_xmlhttpRequest({
      method: "GET",
      url: "https://forum.ripper.store/api/config",
      responseType: "json",
      timeout: 8000,
      onload: (r) => {
        try {
          const d =
            typeof r.response === "string"
              ? JSON.parse(r.response)
              : r.response;
          cb(null, d.csrf_token || d["csrf-token"] || d.csrfToken || "");
        } catch (e) {
          cb("Failed to get CSRF token");
        }
      },
      onerror: () => cb("Network error"),
      ontimeout: () => cb("Timeout"),
    });
  }

  function upvoteTopic(tid, cb) {
    getCSRFToken((err, csrf) => {
      if (err) return cb(err);
      GM_xmlhttpRequest({
        method: "GET",
        url: `https://forum.ripper.store/api/topic/${tid}`,
        responseType: "json",
        timeout: 10000,
        onload: (r) => {
          try {
            const d =
              typeof r.response === "string"
                ? JSON.parse(r.response)
                : r.response;
            const posts = d.posts || [];
            const firstPost = posts.find((p) => (p.index || 0) === 0);
            const pid = firstPost ? firstPost.pid : posts[0] && posts[0].pid;
            if (!pid) return cb("Cannot find first post pid");

            GM_xmlhttpRequest({
              method: "PUT",
              url: `https://forum.ripper.store/api/v3/posts/${pid}/vote`,
              headers: {
                "Content-Type": "application/json",
                "x-csrf-token": csrf,
              },
              data: JSON.stringify({ delta: 1 }),
              responseType: "json",
              timeout: 15000,
              onload: (r2) => {
                try {
                  const d2 =
                    typeof r2.response === "string"
                      ? JSON.parse(r2.response)
                      : r2.response;
                  const isOk =
                    (d2 && d2.status && d2.status.code === "ok") ||
                    (r2.status >= 200 && r2.status < 300);
                  if (isOk) cb(null, d2);
                  else
                    cb(
                      "Upvote failed: " +
                        (d2 && d2.status && d2.status.message
                          ? d2.status.message
                          : "unknown"),
                    );
                } catch (e) {
                  cb("Parse error: " + e.message);
                }
              },
              onerror: () => cb("Network error"),
              ontimeout: () => cb("Timeout"),
            });
          } catch (e) {
            cb("Failed to get topic data: " + e.message);
          }
        },
        onerror: () => cb("Network error getting topic"),
        ontimeout: () => cb("Timeout getting topic"),
      });
    });
  }

  function hasAutoUpvoted(tid) {
    return GM_getValue("ah-auto-upvoted-" + tid, false) === true;
  }
  function setAutoUpvoted(tid) {
    GM_setValue("ah-auto-upvoted-" + tid, true);
  }
  function postReply(tid, content, cb) {
    getCSRFToken((err, csrf) => {
      if (err) return cb(err);
      GM_xmlhttpRequest({
        method: "POST",
        url: `https://forum.ripper.store/api/v3/topics/${tid}`,
        headers: { "Content-Type": "application/json", "x-csrf-token": csrf },
        data: JSON.stringify({ content }),
        responseType: "json",
        timeout: 15000,
        onload: (r) => {
          try {
            const d =
              typeof r.response === "string"
                ? JSON.parse(r.response)
                : r.response;
            const isOk =
              (d && d.status && d.status.code === "ok") ||
              (r.status >= 200 &&
                r.status < 300 &&
                d &&
                (d.pid || (d.response && d.response.pid)));
            if (isOk) cb(null, d);
            else cb("Reply failed");
          } catch (e) {
            cb("Parse error");
          }
        },
        onerror: () => cb("Network error"),
        ontimeout: () => cb("Timeout"),
      });
    });
  }

  const STRINGS = {
    zh: {
      panelTitle: (s, e, t) => `↓ 链接检测 (范围 ${s}–${e}层,共 ${t} 个结果)`,
      panelTitleAll: (t) => `↓ 链接检测 (全部楼层,共 ${t} 个结果)`,
      tabDL: "📥 下载链接",
      tabTopic: "📌 相关帖子",
      tabOther: "🌐 其他链接",
      refresh: "↺",
      refreshTitle: "刷新当前楼层范围",
      settings: "⚙︎",
      settingsTitle: "检测范围",
      minimize: "–",
      close: "✕",
      jumpLink: "跳转链接",
      jumpFloor: "跳转楼层",
      jumpTopic: "跳转帖子",
      floor20: "20层",
      floor40: "40层",
      floor60: "60层",
      floor80: "80层",
      floor100: "100层",
      floorAll: "全部",
      lang: "语言",
      confirmTitle: '⚠️ 选择"全部"可能需要较长时间加载,建议选择受限范围。',
      confirmYes: "确认",
      confirmNo: "取消",
      noData: "暂无数据",
      netDisk: "网盘链接",
      file: "文件",
      download: "下载链接",
      unknown: "未知",
      unknownLink: "未知链接",
      topicNumber: "帖子",
      floorNumber: "楼层",
      floorLabel: "楼层",
      unknownUser: "未知用户",
      loadError: "加载失败,请刷新重试",
      loading: "扫描中...",
      defaultCollapsed: "主面板默认折叠",
      defaultCollapsedOn: "开",
      defaultCollapsedOff: "关",
      highlightTitle: "高亮设置",
      highlightColor: "颜色",
      highlightOpacity: "不透明度",
      highlightEnabled: "楼层高亮",
      apply: "应用",
      themeTitle: "面板颜色",
      themeBg: "背景色",
      themeBgCard: "卡片色",
      themeBgElevated: "浮层色",
      themeBorder: "边框色",
      themeBorderLight: "浅边框",
      themeText: "文字色",
      themeTextMuted: "次要文字",
      themePrimary: "主题色",
      themePanelOpacity: "不透明度",
      themeReset: "恢复默认",
      floorBorderTitle: "楼层圆角美化",
      floorBorderEnabled: "楼层圆角美化",
      borderRadius: "圆角大小",
      borderWidth: "边框粗细",
      borderColor: "边框颜色",
      thumbTitle: "缩略图设置",
      largeThumbs: "大缩略图",
      hoverPreview: "悬停预览",
      thumbScale: "缩放比例",
    },
    en: {
      panelTitle: (s, e, t) => `↓ Link Detection (Range ${s}–${e}, Total ${t})`,
      panelTitleAll: (t) => `↓ Link Detection (All floors, Total ${t})`,
      tabDL: "📥 Downloads",
      tabTopic: "📌 Topics",
      tabOther: "🌐 Others",
      refresh: "↺",
      refreshTitle: "Refresh current range",
      settings: "⚙︎",
      settingsTitle: "Detection range",
      minimize: "–",
      close: "✕",
      jumpLink: "Jump Link",
      jumpFloor: "Jump Floor",
      jumpTopic: "Jump Topic",
      floor20: "20 floors",
      floor40: "40 floors",
      floor60: "60 floors",
      floor80: "80 floors",
      floor100: "100 floors",
      floorAll: "All",
      lang: "Language",
      confirmTitle:
        '⚠️ Selecting "All" may take a long time. Choose a limited range.',
      confirmYes: "Confirm",
      confirmNo: "Cancel",
      noData: "No data",
      netDisk: "Cloud Storage",
      file: "File",
      download: "Download Link",
      unknown: "Unknown",
      unknownLink: "Unknown Link",
      topicNumber: "Topic",
      floorNumber: "Floor",
      floorLabel: "Floor",
      unknownUser: "Unknown User",
      loadError: "Load failed, please refresh",
      loading: "Scanning...",
      defaultCollapsed: "Collapse Panel by Default",
      defaultCollapsedOn: "On",
      defaultCollapsedOff: "Off",
      highlightTitle: "Highlight Settings",
      highlightColor: "Color",
      highlightOpacity: "Opacity",
      highlightEnabled: "Floor Highlight",
      apply: "Apply",
      themeTitle: "Panel Colors",
      themeBg: "Background",
      themeBgCard: "Card",
      themeBgElevated: "Elevated",
      themeBorder: "Border",
      themeBorderLight: "Light Border",
      themeText: "Text",
      themeTextMuted: "Muted Text",
      themePrimary: "Primary",
      themePanelOpacity: "Opacity",
      themeReset: "Reset to Default",
      floorBorderTitle: "Floor Rounding",
      floorBorderEnabled: "Floor Rounding",
      borderRadius: "Border Radius",
      borderWidth: "Border Width",
      borderColor: "Border Color",
      thumbTitle: "Thumbnail Settings",
      largeThumbs: "Large Thumbnails",
      hoverPreview: "Hover Preview",
      thumbScale: "Scale Factor",
    },
    ja: {
      panelTitle: (s, e, t) => `↓ リンク検出 (範囲 ${s}–${e}階、全${t}件)`,
      panelTitleAll: (t) => `↓ リンク検出 (全階、全${t}件)`,
      tabDL: "📥 ダウンロードリンク",
      tabTopic: "📌 関連スレッド",
      tabOther: "🌐 その他のリンク",
      refresh: "↺",
      refreshTitle: "現在の階の範囲を更新",
      settings: "⚙︎",
      settingsTitle: "検出範囲",
      minimize: "–",
      close: "✕",
      jumpLink: "リンクへジャンプ",
      jumpFloor: "階へジャンプ",
      jumpTopic: "スレッドへジャンプ",
      floor20: "20階",
      floor40: "40階",
      floor60: "60階",
      floor80: "80階",
      floor100: "100階",
      floorAll: "すべて",
      lang: "言語",
      confirmTitle:
        "⚠️ 「すべて」を選択すると読み込みに時間がかかる場合があります。範囲を制限することをお勧めします。",
      confirmYes: "確認",
      confirmNo: "キャンセル",
      noData: "データなし",
      netDisk: "クラウドストレージ",
      file: "ファイル",
      download: "ダウンロードリンク",
      unknown: "不明",
      unknownLink: "不明なリンク",
      topicNumber: "スレッド",
      floorNumber: "階",
      floorLabel: "階",
      unknownUser: "不明なユーザー",
      loadError: "読み込みに失敗しました。再読み込みしてください。",
      loading: "スキャン中...",
      defaultCollapsed: "パネルのデフォルト折りたたみ",
      defaultCollapsedOn: "オン",
      defaultCollapsedOff: "オフ",
      highlightTitle: "ハイライト設定",
      highlightColor: "色",
      highlightOpacity: "不透明度",
      highlightEnabled: "階ハイライト",
      apply: "適用",
      themeTitle: "パネルカラー",
      themeBg: "背景色",
      themeBgCard: "カード色",
      themeBgElevated: "浮層色",
      themeBorder: "枠線色",
      themeBorderLight: "明るい枠線",
      themeText: "文字色",
      themeTextMuted: "副次文字",
      themePrimary: "アクセント",
      themePanelOpacity: "不透明度",
      themeReset: "デフォルトに戻す",
      floorBorderTitle: "階角丸装飾",
      floorBorderEnabled: "階角丸装飾",
      borderRadius: "角丸サイズ",
      borderWidth: "枠線太さ",
      borderColor: "枠線色",
      thumbTitle: "サムネイル設定",
      largeThumbs: "大きなサムネイル",
      hoverPreview: "ホバープレビュー",
      thumbScale: "倍率",
    },
    ko: {
      panelTitle: (s, e, t) => `↓ 링크 감지 (범위 ${s}–${e}층, 총 ${t}개)`,
      panelTitleAll: (t) => `↓ 링크 감지 (전체 층, 총 ${t}개)`,
      tabDL: "📥 다운로드 링크",
      tabTopic: "📌 관련 게시물",
      tabOther: "🌐 기타 링크",
      refresh: "↺",
      refreshTitle: "현재 층 범위 새로고침",
      settings: "⚙︎",
      settingsTitle: "감지 범위",
      minimize: "–",
      close: "✕",
      jumpLink: "링크 이동",
      jumpFloor: "층 이동",
      jumpTopic: "게시물 이동",
      floor20: "20층",
      floor40: "40층",
      floor60: "60층",
      floor80: "80층",
      floor100: "100층",
      floorAll: "전체",
      lang: "언어",
      confirmTitle:
        '⚠️ "전체"를 선택하면 로딩 시간이 길어질 수 있습니다. 범위를 제한하세요.',
      confirmYes: "확인",
      confirmNo: "취소",
      noData: "데이터 없음",
      netDisk: "클라우드",
      file: "파일",
      download: "다운로드 링크",
      unknown: "알 수 없음",
      unknownLink: "알 수 없는 링크",
      topicNumber: "게시물",
      floorNumber: "층",
      floorLabel: "층",
      unknownUser: "알 수 없는 사용자",
      loadError: "로드 실패, 새로고침 해주세요",
      loading: "스캔 중...",
      defaultCollapsed: "패널 기본 접기",
      defaultCollapsedOn: "켜짐",
      defaultCollapsedOff: "꺼짐",
      highlightTitle: "하이라이트 설정",
      highlightColor: "색상",
      highlightOpacity: "불투명도",
      highlightEnabled: "층 하이라이트",
      apply: "적용",
      themeTitle: "패널 색상",
      themeBg: "배경색",
      themeBgCard: "카드색",
      themeBgElevated: "플로팅색",
      themeBorder: "테두리색",
      themeBorderLight: "밝은 테두리",
      themeText: "글자색",
      themeTextMuted: "보조 글자",
      themePrimary: "강조색",
      themePanelOpacity: "불투명도",
      themeReset: "기본값 복원",
      floorBorderTitle: "층 둥근 모서리",
      floorBorderEnabled: "층 둥근 모서리",
      borderRadius: "모서리 크기",
      borderWidth: "테두리 두께",
      borderColor: "테두리 색상",
      thumbTitle: "썸네일 설정",
      largeThumbs: "큰 썸네일",
      hoverPreview: "호버 미리보기",
      thumbScale: "배율",
    },
    ru: {
      panelTitle: (s, e, t) =>
        `↓ Обнаружение ссылок (диапазон ${s}–${e}, всего ${t})`,
      panelTitleAll: (t) => `↓ Обнаружение ссылок (все этажи, всего ${t})`,
      tabDL: "📥 Ссылки на скачивание",
      tabTopic: "📌 Связанные темы",
      tabOther: "🌐 Другие ссылки",
      refresh: "↺",
      refreshTitle: "Обновить текущий диапазон",
      settings: "⚙︎",
      settingsTitle: "Диапазон обнаружения",
      minimize: "–",
      close: "✕",
      jumpLink: "Перейти по ссылке",
      jumpFloor: "Перейти на этаж",
      jumpTopic: "Перейти к теме",
      floor20: "20 этажей",
      floor40: "40 этажей",
      floor60: "60 этажей",
      floor80: "80 этажей",
      floor100: "100 этажей",
      floorAll: "Все",
      lang: "Язык",
      confirmTitle:
        '⚠️ Выбор "Все" может занять много времени. Рекомендуется ограничить диапазон.',
      confirmYes: "Подтвердить",
      confirmNo: "Отмена",
      noData: "Нет данных",
      netDisk: "Облачное хранилище",
      file: "Файл",
      download: "Ссылка на скачивание",
      unknown: "Неизвестно",
      unknownLink: "Неизвестная ссылка",
      topicNumber: "Тема",
      floorNumber: "Этаж",
      floorLabel: "Этаж",
      unknownUser: "Неизвестный пользователь",
      loadError: "Ошибка загрузки, обновите страницу",
      loading: "Сканирование...",
      defaultCollapsed: "Сворачивание по умолчанию",
      defaultCollapsedOn: "Вкл",
      defaultCollapsedOff: "Выкл",
      highlightTitle: "Настройки подсветки",
      highlightColor: "Цвет",
      highlightOpacity: "Непрозрачность",
      highlightEnabled: "Подсветка этажа",
      apply: "Применить",
      themeTitle: "Цвета панели",
      themeBg: "Фон",
      themeBgCard: "Карточка",
      themeBgElevated: "Приподнятый фон",
      themeBorder: "Граница",
      themeBorderLight: "Светлая граница",
      themeText: "Текст",
      themeTextMuted: "Приглушённый текст",
      themePrimary: "Акцент",
      themePanelOpacity: "Непрозрачность",
      themeReset: "Сбросить",
      floorBorderTitle: "Округление этажа",
      floorBorderEnabled: "Округление этажа",
      borderRadius: "Радиус скругления",
      borderWidth: "Толщина границы",
      borderColor: "Цвет границы",
      thumbTitle: "Настройки миниатюр",
      largeThumbs: "Большие миниатюры",
      hoverPreview: "Предпросмотр при наведении",
      thumbScale: "Масштаб",
    },
    es: {
      panelTitle: (s, e, t) =>
        `↓ Detección de enlaces (rango ${s}–${e}, total ${t})`,
      panelTitleAll: (t) =>
        `↓ Detección de enlaces (todos los pisos, total ${t})`,
      tabDL: "📥 Enlaces de descarga",
      tabTopic: "📌 Temas relacionados",
      tabOther: "🌐 Otros enlaces",
      refresh: "↺",
      refreshTitle: "Actualizar rango actual",
      settings: "⚙︎",
      settingsTitle: "Rango de detección",
      minimize: "–",
      close: "✕",
      jumpLink: "Ir al enlace",
      jumpFloor: "Ir al piso",
      jumpTopic: "Ir al tema",
      floor20: "20 pisos",
      floor40: "40 pisos",
      floor60: "60 pisos",
      floor80: "80 pisos",
      floor100: "100 pisos",
      floorAll: "Todo",
      lang: "Idioma",
      confirmTitle:
        '⚠️ Seleccionar "Todo" puede tardar mucho. Elija un rango limitado.',
      confirmYes: "Confirmar",
      confirmNo: "Cancelar",
      noData: "Sin datos",
      netDisk: "Almacenamiento en la nube",
      file: "Archivo",
      download: "Enlace de descarga",
      unknown: "Desconocido",
      unknownLink: "Enlace desconocido",
      topicNumber: "Tema",
      floorNumber: "Piso",
      floorLabel: "Piso",
      unknownUser: "Usuario desconocido",
      loadError: "Error al cargar, actualice la página",
      loading: "Escaneando...",
      defaultCollapsed: "Colapso por defecto",
      defaultCollapsedOn: "Activado",
      defaultCollapsedOff: "Desactivado",
      highlightTitle: "Configuración de resaltado",
      highlightColor: "Color",
      highlightOpacity: "Opacidad",
      highlightEnabled: "Resaltado de piso",
      apply: "Aplicar",
      themeTitle: "Colores del panel",
      themeBg: "Fondo",
      themeBgCard: "Tarjeta",
      themeBgElevated: "Elevado",
      themeBorder: "Borde",
      themeBorderLight: "Borde claro",
      themeText: "Texto",
      themeTextMuted: "Texto atenuado",
      themePrimary: "Acento",
      themePanelOpacity: "Opacidad",
      themeReset: "Restablecer",
      floorBorderTitle: "Embellecimiento de piso",
      floorBorderEnabled: "Embellecimiento de piso",
      borderRadius: "Radio de esquina",
      borderWidth: "Grosor del borde",
      borderColor: "Color del borde",
      thumbTitle: "Configuración de miniaturas",
      largeThumbs: "Miniaturas grandes",
      hoverPreview: "Vista previa al pasar el cursor",
      thumbScale: "Escala",
    },
    fr: {
      panelTitle: (s, e, t) =>
        `↓ Détection de liens (plage ${s}–${e}, total ${t})`,
      panelTitleAll: (t) =>
        `↓ Détection de liens (tous les étages, total ${t})`,
      tabDL: "📥 Liens de téléchargement",
      tabTopic: "📌 Sujets associés",
      tabOther: "🌐 Autres liens",
      refresh: "↺",
      refreshTitle: "Actualiser la plage actuelle",
      settings: "⚙︎",
      settingsTitle: "Plage de détection",
      minimize: "–",
      close: "✕",
      jumpLink: "Aller au lien",
      jumpFloor: "Aller à l'étage",
      jumpTopic: "Aller au sujet",
      floor20: "20 étages",
      floor40: "40 étages",
      floor60: "60 étages",
      floor80: "80 étages",
      floor100: "100 étages",
      floorAll: "Tout",
      lang: "Langue",
      confirmTitle:
        '⚠️ Choisir "Tout" peut prendre du temps. Choisissez une plage limitée.',
      confirmYes: "Confirmer",
      confirmNo: "Annuler",
      noData: "Aucune donnée",
      netDisk: "Stockage cloud",
      file: "Fichier",
      download: "Lien de téléchargement",
      unknown: "Inconnu",
      unknownLink: "Lien inconnu",
      topicNumber: "Sujet",
      floorNumber: "Étage",
      floorLabel: "Étage",
      unknownUser: "Utilisateur inconnu",
      loadError: "Échec du chargement, veuillez actualiser",
      loading: "Analyse en cours...",
      defaultCollapsed: "Réduction par défaut",
      defaultCollapsedOn: "Activé",
      defaultCollapsedOff: "Désactivé",
      highlightTitle: "Paramètres de surbrillance",
      highlightColor: "Couleur",
      highlightOpacity: "Opacité",
      highlightEnabled: "Surbrillance d'étage",
      apply: "Appliquer",
      themeTitle: "Couleurs du panneau",
      themeBg: "Fond",
      themeBgCard: "Carte",
      themeBgElevated: "Surélevé",
      themeBorder: "Bordure",
      themeBorderLight: "Bordure claire",
      themeText: "Texte",
      themeTextMuted: "Texte atténué",
      themePrimary: "Accent",
      themePanelOpacity: "Opacité",
      themeReset: "Réinitialiser",
      floorBorderTitle: "Arrondi d'étage",
      floorBorderEnabled: "Arrondi d'étage",
      borderRadius: "Rayon de bordure",
      borderWidth: "Épaisseur de bordure",
      borderColor: "Couleur de bordure",
      thumbTitle: "Paramètres des vignettes",
      largeThumbs: "Grandes vignettes",
      hoverPreview: "Aperçu au survol",
      thumbScale: "Échelle",
    },
  };

  let currentLang = GM_getValue("ah-lang", "zh");
  let currentStrings = STRINGS[currentLang] || STRINGS.zh;
  const i18n = (key, ...args) => {
    const str = currentStrings[key] || key;
    return typeof str === "function" ? str(...args) : str;
  };

  const DL_PATTERNS = [
    /mega\.nz/i,
    /mega\.io/i,
    /mediafire/i,
    /drive\.google/i,
    /gofile\.io/i,
    /pixeldrain/i,
    /workupload/i,
    /1fichier/i,
    /dropbox/i,
    /onedrive/i,
    /terabox/i,
    /bowfile/i,
    /1cloudfile\.com/i,
    /archive\.org\/download/i,
    /anonfile\.la/i,
    /app\.bunkrr\.su/i,
    /buzzheavier\.com/i,
    /clicknupload\.click/i,
    /cyberfile\.me/i,
    /dailyuploads\.net/i,
    /datanodes\.to/i,
    /disk\.yandex\.com/i,
    /fastupload\.io/i,
    /filebin\.net/i,
    /fileditch\.com/i,
    /filepost\.io/i,
    /files\.fm/i,
    /filetransfer\.io/i,
    /fuckingfast\.net/i,
    /hexload\.com/i,
    /mixdrop\.ag/i,
    /send\.cm/i,
    /terminal\.lc/i,
    /transfer\.it/i,
    /uploadfile\.pl/i,
    /uploadhaven\.com/i,
    /uploadnow\.io/i,
    /wdho\.ru/i,
    /wetransfer/i,
    /axfc\.net/i,
    /filemail\.com/i,
    /sendspace/i,
    /swisstransfer\.com/i,
    /zippyshare\.day/i,
    /hidelinks\/r\//i,
    /\.zip\b/i,
    /\.rar\b/i,
    /\.7z\b/i,
    /\bdownload\s*(?:link|here|now|file|this)\b/i,
    /(?:^|\s)dl\s*(?:link|here|:)/im,
    /baixar/i,
    /descargar/i,
    /🔗[\s]*DL/,
  ];

  const URL_EXTRACT_PATTERNS = [
    /https?:\/\/mega\.nz\/[^\s"'<>]+/gi,
    /https?:\/\/mega\.io\/[^\s"'<>]+/gi,
    /https?:\/\/(?:www\.)?mediafire\.com\/[^\s"'<>]+/gi,
    /https?:\/\/drive\.google\.com\/[^\s"'<>]+/gi,
    /https?:\/\/gofile\.io\/[^\s"'<>]+/gi,
    /https?:\/\/pixeldrain\.com\/[^\s"'<>]+/gi,
    /https?:\/\/workupload\.com\/[^\s"'<>]+/gi,
    /https?:\/\/1fichier\.com\/[^\s"'<>]+/gi,
    /https?:\/\/(?:www\.)?dropbox\.com\/[^\s"'<>]+/gi,
    /https?:\/\/onedrive\.live\.com\/[^\s"'<>]+/gi,
    /https?:\/\/terabox\.com\/[^\s"'<>]+/gi,
    /https?:\/\/bowfile\.com\/[^\s"'<>]+/gi,
    /https?:\/\/forum\.ripper\.store\/hidelinks\/r\/[^\s"'<>]+/gi,
    /https?:\/\/forum\.ripper\.store\/[^\s"'<>]*hidelinks[^\s"'<>]*/gi,
    /https?:\/\/1cloudfile\.com\/[^\s"'<>]+/gi,
    /https?:\/\/archive\.org\/download\/[^\s"'<>]+/gi,
    /https?:\/\/anonfile\.la\/[^\s"'<>]+/gi,
    /https?:\/\/app\.bunkrr\.su\/[^\s"'<>]+/gi,
    /https?:\/\/buzzheavier\.com\/[^\s"'<>]+/gi,
    /https?:\/\/clicknupload\.click\/[^\s"'<>]+/gi,
    /https?:\/\/cyberfile\.me\/[^\s"'<>]+/gi,
    /https?:\/\/dailyuploads\.net\/[^\s"'<>]+/gi,
    /https?:\/\/datanodes\.to\/[^\s"'<>]+/gi,
    /https?:\/\/disk\.yandex\.com\/[^\s"'<>]+/gi,
    /https?:\/\/fastupload\.io\/[^\s"'<>]+/gi,
    /https?:\/\/filebin\.net\/[^\s"'<>]+/gi,
    /https?:\/\/fileditch\.com\/[^\s"'<>]+/gi,
    /https?:\/\/filepost\.io\/[^\s"'<>]+/gi,
    /https?:\/\/files\.fm\/[^\s"'<>]+/gi,
    /https?:\/\/filetransfer\.io\/[^\s"'<>]+/gi,
    /https?:\/\/fuckingfast\.net\/[^\s"'<>]+/gi,
    /https?:\/\/hexload\.com\/[^\s"'<>]+/gi,
    /https?:\/\/mixdrop\.ag\/[^\s"'<>]+/gi,
    /https?:\/\/send\.cm\/[^\s"'<>]+/gi,
    /https?:\/\/terminal\.lc\/[^\s"'<>]+/gi,
    /https?:\/\/transfer\.it\/[^\s"'<>]+/gi,
    /https?:\/\/uploadfile\.pl\/[^\s"'<>]+/gi,
    /https?:\/\/uploadhaven\.com\/[^\s"'<>]+/gi,
    /https?:\/\/uploadnow\.io\/[^\s"'<>]+/gi,
    /https?:\/\/wdho\.ru\/[^\s"'<>]+/gi,
    /https?:\/\/(?:www\.)?wetransfer\.com\/[^\s"'<>]+/gi,
    /https?:\/\/axfc\.net\/[^\s"'<>]+/gi,
    /https?:\/\/filemail\.com\/[^\s"'<>]+/gi,
    /https?:\/\/(?:www\.)?sendspace\.com\/[^\s"'<>]+/gi,
    /https?:\/\/swisstransfer\.com\/[^\s"'<>]+/gi,
    /https?:\/\/zippyshare\.day\/[^\s"'<>]+/gi,
  ];

  const GENERIC_URL_REGEX = /https?:\/\/[^\s"'<>]+/gi;

  const hasDLPattern = (text) => {
    if (!text) return false;
    return DL_PATTERNS.some((pat) => {
      pat.lastIndex = 0;
      return pat.test(text);
    });
  };

  const extractURLs = (text) => {
    if (!text) return [];
    const results = [];
    for (const pat of URL_EXTRACT_PATTERNS) {
      pat.lastIndex = 0;
      let m;
      while ((m = pat.exec(text)) !== null) {
        results.push(m[0].replace(/[.,;!?]+$/, ""));
      }
    }
    return [...new Set(results)];
  };

  const FLOOR_OPTIONS = [
    { value: 20, labelKey: "floor20" },
    { value: 40, labelKey: "floor40" },
    { value: 60, labelKey: "floor60" },
    { value: 80, labelKey: "floor80" },
    { value: 100, labelKey: "floor100" },
    { value: 0, labelKey: "floorAll" },
  ];
  const DEFAULT_FLOOR_LIMIT = 20;

  function getFloorLimit() {
    const val = GM_getValue("ah-floor-limit", null);
    return val === null ? DEFAULT_FLOOR_LIMIT : parseInt(val, 10);
  }

  function setFloorLimit(limit) {
    GM_setValue("ah-floor-limit", String(limit));
  }

  function hasWelcomed() {
    return GM_getValue("ah-welcomed", false) === true;
  }
  function setWelcomed() {
    GM_setValue("ah-welcomed", true);
  }

  function getDefaultCollapsed() {
    const val = GM_getValue("ah-default-collapsed", false);
    return val === true || val === "true";
  }

  function setDefaultCollapsed(val) {
    GM_setValue("ah-default-collapsed", !!val);
  }

  const DEFAULT_HL_COLOR = "#72f0a8";
  const DEFAULT_HL_OPACITY = 18;

  function getHighlightEnabled() {
    const val = GM_getValue("ah-hl-enabled", null);
    return val === null ? true : val === true || val === "true";
  }

  function setHighlightEnabled(val) {
    GM_setValue("ah-hl-enabled", !!val);
  }

  function getHighlightColor() {
    const val = GM_getValue("ah-hl-color", null);
    return val && /^#[0-9A-Fa-f]{6}$/.test(val) ? val : DEFAULT_HL_COLOR;
  }

  function setHighlightColor(val) {
    GM_setValue("ah-hl-color", val);
  }

  function getHighlightOpacity() {
    const val = GM_getValue("ah-hl-opacity", null);
    const num = val === null ? DEFAULT_HL_OPACITY : parseInt(val, 10);
    return isNaN(num) ? DEFAULT_HL_OPACITY : Math.max(0, Math.min(100, num));
  }

  function setHighlightOpacity(val) {
    GM_setValue("ah-hl-opacity", String(Math.max(0, Math.min(100, val))));
  }

  const hexToRgb = (hex) => {
    const clean = hex.replace("#", "");
    return {
      r: parseInt(clean.substring(0, 2), 16),
      g: parseInt(clean.substring(2, 4), 16),
      b: parseInt(clean.substring(4, 6), 16),
    };
  };

  const updateHighlightStyles = () => {
    const enabled = getHighlightEnabled();
    const color = getHighlightColor();
    const opacity = getHighlightOpacity();
    const { r, g, b } = hexToRgb(color);
    const alpha = opacity / 100;

    let style = document.getElementById("ah-dl-highlight-styles");
    if (!style) {
      style = document.createElement("style");
      style.id = "ah-dl-highlight-styles";
      document.head.appendChild(style);
    }

    if (!enabled) {
      style.textContent = `.${HIGHLIGHT_CLASS} { /* disabled */ }`;
      return;
    }

    const borderEnabled = getEnableFloorBorder();
    const radius = borderEnabled ? getFloorBorderRadius() : 0;
    const baseRadius = radius > 0 ? `${radius}px` : "0px";
    style.textContent = `
      .${HIGHLIGHT_CLASS} {
        background: rgba(${r}, ${g}, ${b}, ${alpha.toFixed(2)}) !important;
        border: 1px solid rgba(${r}, ${g}, ${b}, 0.45) !important;
        box-shadow: 0 0 12px rgba(${r}, ${g}, ${b}, 0.12) !important;
        outline: none !important;
        border-radius: ${baseRadius} !important;
        overflow: hidden !important;
      }
      .${HIGHLIGHT_CLASS} .content,
      .${HIGHLIGHT_CLASS} [component="post/content"],
      .${HIGHLIGHT_CLASS} .post-content {
        background: transparent !important;
      }
      .ah-floor-card.${HIGHLIGHT_CLASS} {
        border: 1px solid rgba(${r}, ${g}, ${b}, 0.45) !important;
        box-shadow: 0 0 12px rgba(${r}, ${g}, ${b}, 0.12), 0 10px 24px rgba(0, 0, 0, 0.12) !important;
      }
    `;
  };

  const stripHTML = (() => {
    const parser = new DOMParser();
    return (html) => {
      if (!html) return "";
      const doc = parser.parseFromString(html, "text/html");
      return doc.body.textContent || "";
    };
  })();

  const getLinkLabel = (url) => {
    if (!url) return i18n("unknown");
    const lower = url.toLowerCase();
    if (lower.includes("hidelinks")) return i18n("netDisk");
    if (/\.(zip|rar|7z)\b/.test(lower)) return i18n("file");
    return i18n("download");
  };

  const OTHER_LINK_LABELS = [
    [/vrchat\.com/i, 'VRChat', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAYFBMVEVHcEybm5sHBwcGBgYqKirc3NxnZ2f5+fmzs7OHh4cvLy8JCQkICAgICAgEBARMTEwDAwMKCgofHx+jo6MNDQ3///8AAADn5+cHBwdXV1c8PDzS0tLu7u7CwsIYGBh5eXmV9tWrAAAAFXRSTlMA/mqk/f7+/v7+DsaLTPD+3jEi/kBKSgmMAAABXklEQVQ4ja2S65KDIAyFEbm2Xmo1AQXx/d9yA+5u7bT1z+6ZAWeSD86RCWN/16W+1g1vh/vlU19UVi2pu90K+MLVqCFLu7COVs39IojkfPq5suvhKB2dDGa0W0/ctSbGz/BBOkoj2gJoG4rJK7ReC+BEvmbFVaLolaFQWsqCu2W3UH0EqFAGVLbHjQoJhdUPwCB5LAkCrgAbRoOW1vYAHI6gqRDQAIwYKy8BxPIAgEJQBAJsWFOCKsmc+gBsCFY4Akiz/AYOIel4mBVkCyfIugAxHgAqC1MAsGRVMvTzAdAKMe6AxoW+Kjz9RX6EBDtAVzhaiJs7ArDuL5e3WDb9FPKTnGBenLTlzFmXbTWNAc1BWI0ZK7spZYuBSg1jjcdn+a67dagyQOfzTHpPxTw+TcN5O03DcJ88Vhlo97F9M85tQvsLvJ11nijbCUDZElbLcALk9Pysz9hwP+//m74A1NkyoLkyP8QAAAAASUVORK5CYII=" alt="image">'],

    [/booth\.pm/i, 'Booth', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAMFBMVEX8TVD////9enz+09P8WFv+pqj+3t7/9PT9bnH/6en8Y2b+vL39hYf+sbL9m539kJKF5Gn5AAAAnklEQVQ4jc3S0Q6DIAwF0F4KRXDq///tViZmFIwPZtnuA0R6IFok+re4SUcGdxVmftUckIkmAI92HzSOaAbmMiL6Ecg6eR91WkYg6BRW1AOPeOfKStJCTG8g7Usq2NAkdIBbEC1YYWJBsmAzoM9yBT4/9T4I8QLUfiOx1HK5YAMka+f349peFyD7JXs5AcdvUsQAnD2NluQKhC+AH+YJHMoDCXZwGn0AAAAASUVORK5CYII=" alt="image">'],

    [/gumroad\.com/i, 'Gumroad', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAclBMVEVHcEwAAAAAAAAAAABmOVwAAAA6ITUHAwYAAAAAAABSLkoAAAAGAwZLKkUAAAAAAAAAAAD/kOgAAAD/kev/lvH/k+3/lO+uYp7kgM/1it/Lcrh5RG7rhdaLTn5UL0ykXJXZesX8juW4aKgoFiQ9IziaV4ytfW5xAAAAEXRSTlMADzsp8mXlonfe7hrCx1aM/EiuaFkAAAGmSURBVDiNbVPbgqowDERAQER3JW0obYEC7v//4kl6WXE98yKYmcmkpFn2i6K5laUpb/X9lP0H53awKzJGPXXXv+W80ijh4QGA4/T1Xm+mh3wcAOiqo0k9iMcfyLF9MZoBk5KQHtc2Zc2nqJdiVMoiBg7YLhKqpB7mnrAbFQxxO4f5tAyCZ5+wBEucC2/g+TD60nPe+WcIGn3hBOFFsv08CgE/zBjB59zvNIL1z47lPAKIkpt4lTBkcVt9O9bFMM4YE223vshKDHmIwFZSShQECNy+iQRBI+zAmiEghFB9nZng8AwOIk2qIuFybEEqsNbqF8ER4RbiLPQnHzmAmF4ETYQ6dSM4Qbvg+jkRcCHCXfvAaJgxaUdWyy9hppCnKR71nvINWySA7WnM2IMYJtR/xBAJfHr0ua7BgtZMLXSEFsFqrdfw/S5+4VzaVol+c+NecaywEdWY9uwIHKJBlp3a9ZOBHDWPS3etLL6XgfWxgffoNgQ4lP1Ib5fn3uqVEjIQrT/tc/aOZjebU0rpxS93V3ze3+aSDvO7yz/LjOL8Vdd1kx/u/z9qTTzsgua4WwAAAABJRU5ErkJggg==" alt="image">'],

    [/jinxxy\.com/i, 'Jinxxy', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAaVBMVEVHcEwQEBAQEBAQEBAQEBAQEBAODg4PDw8QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAD///8ICAgsLCyQkJCurq7f399CQkLq6upWVlb29vZycnKGhobNzc3Hx8cZGRmdnZ0fHx8DLFfnAAAAEHRSTlMAyhyj8gw/W3KUxeItv820HFFNFgAAAV1JREFUOI2VU9uygjAM9IKWm0qSWkBEwP//SJOWQrmch5MZ7Ti72Wy29XD4R11UkmZZmqjLLnw9FlMdr9vuALaUlcq12NRCRG3xAtR+P/jz/Z01Qrx1DHrjCzz+CPAXapLziVjRw+GnAGfAwIjzebKEsygCyNdgKqxB8EbLqLN3QNp8gKDGDhBFpumdFcFzq43sqZS+EnWLNY3L5EyIR3Omkz7o0GAJftmYCYmzVyMiz4UB2QV518nokQkf51v8977fuUxcbohPsKPwM+NWQQhQ8YCOB2h3Lgh8UVQ1OJS8HzX4YoszQbkg6duUPPzNTlv+BAQbZcYjtAbAuuSceU89LZHZJCUIIutfbBhW8oQ4vG0wdjq09rqKKWmfpazSkxieXcb+QdxHBk3XYn/f5zcXzb457Na5jMJHv2A4pWj58G/Fqm7rf44KRYpIrXGhpB5O92BbuYpjlf+F7tcP3YQuaPxZ99gAAAAASUVORK5CYII=" alt="image">'],

    [/payhip\.com/i, 'Payhip', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAARVBMVEVHcEz///82lddHkNs2kNF4geZ4geZHkNtDkdo7k9hwg+RohuI9k9lTjN1eieCww/BynePv9Pw5i9mTsena5PdrgePF2vMTDAaaAAAACnRSTlMA////CcL/XKXOLkfEuAAAAPVJREFUOI2t0+uOhCAMBWAuzoIOSmnV93/ULTKTtKIz2WTPXz5OEKoxJowpJV8z1yycJ2eaph+OMdG5BuYKlg4E9wY3DaP70uC+Nbh/a0APRA0AQQ+QirW25HmGXENnQLaUSjLlFtIA7IreryzWF8gggEOyO3rcrRC0iQZe2hEbeAsJHGY+X87FCqEa+IwyhwAF0toJ/RUI5SRO9+AQFLDPrbtq2VFg6x9LiHLxFl6IY/3quV+iwN08zP44Ka8vAiQ5MNzB+/XARNHAAfjz2A9Jg6X7s4Ka6kYU4FN8aHgwGKJq0CCYmuDvGqJpGUIcL77iwft/AaSnImZSJCvmAAAAAElFTkSuQmCC" alt="image">'],

    [/patreon\.com/i, 'Patreon', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACH0lEQVRYhcWXsa7aMBiFz09ATiayZQxdYAP1BVDvACPPcDswdyF7pWSvKvEAJYxssLFUzcjSkhdovJUpiXSFggC5A3AnYnKTXDiSl/i3z+f4JJYJZ+m6rm+32y9E9AlA49zKVAAgIKJJkiQ/Lg8JAFRVbQghfr6DaSoMET0lSRIQADDG/t7R/BVC07SPiqqqzwCe72wOAPp+v98piqJ8w/1XDwAgIlTzmpumiU6ng3q9DgDgnCOOY6zX67dM0wBjTGRtvV5PuK4rwjAUaQrDULiuK5rNZqY5MwEYhiFc1001TZNt2+UArNfrN5tfNBqNigHYtp3bXIjTlhiGkTq/Uq1Wv8qCNpvN8mT0VaqqYrPZYLVaXe2vyAZ3u91C5he12+3UPinAYDAoBSCO43wApmmWAiD7N0gBZK8uqzjn8DwvH0AZchwHnPP7A0RRBMuyMJ1OpXXVso09z4PneRiPx4ii6Ga9FIBznimIURRhPB5nNs0MIPt8LuKco9/vS/dZJmkGshytt0L2rgCc85shKwSwWCykgx3HKWQOAMQYE7KC5XJ59UzgnKPVahUGuPkfGI1GV5NtWVZh80wAvu+j3+/D930Ap5UPh0PM5/NSAOhBd4KLggpOV6ZHKagQ0a9HuRPRhADojLHfeMDVbLfbfagAiIjoCffdiuDsCQUADodDdDwev9dqNU5E+rlITx2e3/QPEU00Tfv88vLyDwD+A/zH0EiWizBNAAAAAElFTkSuQmCC" alt="image">'],

    [/x\.com/i, 'X', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAM1BMVEUAAAD////f398QEBAgICDv7+9wcHBAQECAgIC/v7/Pz8+vr6+QkJAwMDBQUFCfn59gYGBA/ZVxAAAA2ElEQVQ4jc1SSRLDIAzD7GuS/7+2yKQNENJrqwMMFiBbthB/gkSMrY/F/qik8d4bo69QJt9fd2SFOCh8An7k8aAIseMaw1KaslBSKig1kUL1NMFRxsIiWko98xBxWCx4s+CFihCJRquIqwsUJFZMvFKdYcFYmgvsEKAdHgQ4exOw3EucRfKXL2BoehSpBRo29EkkkbXN0LUIdzA1Q7clj3fvrt1FNoq8O9rhapj5Wv7ZoUzHWfDAXx1uUxHGjioJA07wVLCrQ4HdKdPufexD04jW/4Dl2PwELxkCBNiT/pD8AAAAAElFTkSuQmCC" alt="image">'],

    [/twitter\.com/i, 'Twitter', CONFIG.colors.other, '<img src="data:image/png;base64,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" alt="image">'],

    [/itch\.io/i, 'Itch', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEu0lEQVRYhcWXXWwUVRTHf2fuTmeBgqYFBUuIETVoAj5I8MlYXw0YTQRCO9sE1AcRP2Kij/jxBGhERTBiDAnbNiSNiVCIGlExPKqJAQKJhjS2FZSvKh9pZ2bvPT7sdjrdjwoS4bzszDn/e/7/c+7Mmbtwk02qHVFn52JRbUoBIrdiTIoTa/PW86bVS2acG1VjxlKHtaqqf43fqkgc9PQcrStAV65sSvL5j1Bd95/LuTr71I+i9dLXFwN4FXKTBEHxBpADPF0Kgj3a3p5LBSRBsANYdQPIAVB4Mmlr2wYgUWdnKCLFG0U+SYhqpycib9wMcgARedMDbr9ZAoC5HiLDDYI/X6P/v6wZ8gR6qr0Cvwu8BrjqmKq+BZyok+x4JVZtTjzvVYFTdXi6vdzQ0CZEPsn4B1BdraqXgPXAaMVfEtjoGXNWRVYB2YFyRI1Z5RlzVlRfB0oV/ygiz6m1l/G81cBAZs3O3PDwlolBFIbzYtXZ1pgLvrXbVeRRhfVNUbQ3yecX+daeTESeQuQ9YK9v7YsxzFZjNLD2fGLMNuBx4CXfuc8SYxb6cCJWfUJgh6h+lxjzvLG2pUnknHR3n650oWxJGD6s8C6wBJgYxXBK4SRwL5Mf2DFEjpTV6xIgn4n9CfyicLfAvIw/Ao4KvOJ3dx8GyKUdEFmI6tLqfVK4A2hG5G9ULWAqoTyqy6rxgEUkRvUBgVlVsQBYqqp3AYehMgkrVdxSJ9klVS34w8OtTcXiAt+5Oahur4Mbtw995+Y0FYsL/OHhVkS6gMs1KM9LudIOCMzSKpyDMN/Tsy/F9PaOABtGOzrezqteIQjKyaOoeUxkxrTe3t9S7KFDJaAYdXRcFM/7PJs325lUgBOZLqpZUH++u3sfdSxLVLEx4Fw9bNDbuzcqFPaL6vJMYTPGr7NbMPlsoPp+vYRpGCQOw21xofCB1jlXZM2DrVW5JROj9lrkQi6OD01FnoThTmADqi8khcLHU4nIjY19D5xvIG6CNqPwoPT12UYJkzDcCjyTwT+bhOE7jfDS12cROZjB13ZARLICvmqUTLu6WgVOC/RnlPcLnNGurtZG6xrlnJgDqqVxBSVrv25Yze7d54HNSWfnRkRWVBb/6Pf0bG5IDvjwZQIKCJ6XjPuzWxBXfo9N37NnaKpkADnf3+I71+I715Lz/S3/hq+M3mMAnmoqIO2AJxJr+TX8pl4CXbduZhLH/agOqsgfJWsvppVY60dhOEtU5yKywI+i5dLXVzuA4FtgMVArYPz4rPBDPQGlUukh4BFEkDI+o07LT3DlMSrl88sqZFVtkJ9QJXtUT7fAwSCAERmpqb69Paeqm+oJq2equnn81Js1z7mRCulgjQDPuUEA59yK7DsdFQr3JW1tX6D64NUKAJbG8+cfiNasWZSKAnEiywGsMamA7B+T5iQILgA+cAYYRrUFkTuvgbieDQAjwHzgNiD2RVqkWLwySQBAFIYHBB67TsIpTUX2B8XiivH7Sfukzr0sIgN43v2ieo+WFTfVZLk2iwXOqMivOHdcnZv0jZnyIwKga9fOiaJoplf+hBoxphkR35VKMzCmLM7a2MvlrqCaqLWXAevgYhAEl2TXrrPXWcD/a/8AI0z+nVQCIGYAAAAASUVORK5CYII=" alt="image">'],

    [/kemono\..*/i, 'Kemono', CONFIG.colors.other, '<img src="data:image/png;base64,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" alt="image">'],

    [/pixiv\.net/i, 'Pixiv', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAOVBMVEVHcEwAlvr///8Aj/oAl/sAlvoAlvoAlvoAlvoAlvoAk/qX0v1rvvxLsPzY7//x+v/A5f6y3f4po/sAsia0AAAACnRSTlMA////AbKM8jvqQmuaRwAAANdJREFUOI2dk4sOgyAMRUFAoRQE/v9jx2MMnWUku4kR7ekFa8sYk1oZTsgoLVnRflDhomPPYbnPwkW7ZHqaXz00U7/inCtGnm/IsPszQLuGOgAYEARHG6N1Ap6AsNuJ9tyqggMK2N7hrHMQV6DF2g2BBjw6jHVBA1EIAOGuFjfAJ/isI+VgWxa4fBBPAd02+eL2j4PnizPkt5A1/4pcZEyuFrzv8Kzkd60nAHLSwfv+NwUNYLIxBIuc7ocMiKpZR4VrnwygNy18pzaZddsvB2c5esvhXY3/Cw7CC6F4VgydAAAAAElFTkSuQmCC" alt="image">'],
    [/fantia\.jp/i, 'Fantia', CONFIG.colors.other, '<img src="data:image/png;base64,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" alt="image">'],

    [/google\.com/i, 'Google', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAsVBMVEVHcEz8/Pz7+/v+/v75+fr////5+vr4+Pj////8/Pz////29vb////////7+/v////s7e3r7O3/uEc1T/X///81qlXsTkFHiPXrRjf8wBHrPy/9/Po4gfUhpUlBhfT6vQDW4vyQs/j0pJ/72NJDrl/h8eT/9+cqe/SHrfhhuXfylpC1zPrtX0rR6NZ+w4794J6vtS6s1rDuc2/3u7f8zFZyofb3pABqrbhqouOHx5U8ooarLy5yAAAAE3RSTlMAYtPBeeCROqb4DUNE6EkWISHojGzqvgAAAWpJREFUOI2dk9l2gjAQhhURiEttz0kEwmYjKFC1rt3e/8GazBAFY2/6XyX5/kwmM0mv19bInUzcUe8PuR6hIOK5j/CQtjQ0LB69k9flw3sugzziS1HXYmk6BrAQCc4DKc5FBAsDzfu4ex7MG/EAo/QbA3KukNwvbcEGQ1DkthquAsD16VRzrjm1wTBVw6M0cIHLQnM6VXwGAT7kfmHedaZTfA/DzdHkkKajbngOwwwzL19R5ULNHF3kTBpWYFj7KWj9pgt+NWBqsY+Ku4bwFiGW6higDOcszA5Q74XSJfXjhS7EE9wi2zN2vT5NZQSYPelKR3uWs0rzMvXTy63W0MtDkucs36ph8SlzWMMJ2M8xhKiYVMKqKk/Yjx+XEGCM3bLAkTN5SqPkC7jVtPsFT94lV/6NK8/dF0OLHUuk2K64NaKRo/MvtttCj532q7XNRtp3/4Z0MTE/l92yENvAEMWxSBQRy3n0Nf+vX4gnPfthEwRuAAAAAElFTkSuQmCC" alt="image">'],

    [/vrmodels\.store/i, 'VRModels', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAk1BMVEUVFhz/MDA1NjvsLS5uICP///9AGyCYJCfXKywsLTJbW15WVlkZGiAxMjcpKi9PUFM9PkJub3JnHyPw8PExGR6kJSghIidlZWdGR0pYWVtoaGo+P0IlJStfYGNMHCB+f4KwJynj4+PPz9DfLC1THSHIKitaHSGVlZi9vb+lpaeIIyagoKO2trgmGB2gJSh3ISQeFx3QOexnAAABB0lEQVQ4jeWR63KDIBCFl6UkJYhijJdUiTVNNWnt5f2froCKOpPpC+T84Cw73xx2B4DHUSCxAkQMTcGBI0p78BnYKTxmmKtQKywijRgpvQLwKHQGIoCdpV+EiHkuhFokSC6kBXLkZWQoWa4TAlQSoDKFUhhYzxYz6DjwZBYL2D95nVyzZrEHapYDJbNeXX7TTBFVcgagb5tRl3fS2nbM6hHoWGSAZx94Ilvn56RyLpMrrIADSZ0L1jm/MbUG9uRjKK6JXUmxm71Quh2VUnoYgIJ9mrNn0gGLLdIpq2doqOGh+Ymvb/IzfQbr/ah3hnQLdtOy94EwYU32HwCFLseqvfjmb7uBB9EfSnoPSrUNjy0AAAAASUVORK5CYII=" alt="image">'],

    [/ko-fi\.com/i, 'Ko-fi', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFhElEQVRYhe2XbWhUZxbHf09yr5NJYhJbNw1L29zxJSXY7BYtRmatUqiYzW5ZdpEI2S0qiqQ7SmkDXd3FflFRlyq0mkCLL5QVghatscQUdmNYI2WX6rIgWlFjqBOIG5OYzTQzGeZm/vthJpkZ86qln7oH7ofzf55z///n5Z5zLvzQzcxmkqRswAvMmSFGgAuEjTHuEwmQZAElj8DPAnXA60kRWcknnXg0Sf4V8D5w45F3jAAPjTGjkwpIrtIHlAJ/m0pxPB7HdV3i8fg4lpWVhWVZZGVlTRUGcAn4A3AlfXdMkjwL8AMdsViMYDCI606+g5FIhPv37zM0NDSOzZ07l5KSEnJzc6dkLy0txePxALwGdAO3jTFxI8kAPxsjv3TpEjt37uTBgwfTreax7fDhw/h8PsrKyrBt+wHgM8YMm+SZR1zXtdra2qirqwMgLy+P8vLy70wcDoe5e/cuIyMjeL1ejhw5wqpVq7Asay3wVyOpGPjPw4cPWbp0KTk5OSxbtgyfz8fu3bu/s4Curi4aGxu5cOEC4XCY/Px8Ojo6KCoqAihE0nuu66q5uVmO42j16tX6PmzXrl0qLy+X4zhqbm6W67qS9HsjSeFwmCVLlgDgOA7t7e2pJQz1wc1/pPwfPQe+n0JPJwS/TuGlL8Izzrh79epVent7AfD7/RQWFrJmzRo6OzuRxPXr18nNzcVK3y7btlm8eHEmeetHcP7DFFbuh1d/C/9ugy/PpvBXauDX78AzPgAaGxu5ePEiAOfPn6eiooKqqiqOHz9OOBweD8v4cD0eD36/PwX0dGaSA3z9JTQGMskBOk4nxqax+vp6CgoKMrAMAfF4nL6+voQjweissumsrbW1lUgkkoFlHEEsFuPmzZtpiJ6YbO3atSxcuBCA+fPnA4lckJ7AJhXQ1dWVcIyBbPuJBdTU1GT4TU1N9PT0IIktW7Zg2zbA4QnJe3Q0rVZkZ4PHOzvGBS9BsTPp0MmTJzl48CCDg4Pk5eURCATGBLxnAViWxfr16zl16lRm5Pzn4dU34IuPZxbw0mvg+8m4e/r0ae7cuQPAuXPn6O/vB2Dbtm14vV6APwFDFvCWbdsfbN68eaKAomL4xZsQi0LbJ1OTv1wNy38J3rkAnD17loaGBu7duzc+JRAIUFxczLp16/B4PFHgQ2NM3AIajTEfjF2UgYEBGhoaCAQCicinfgy/egty8uDyp/DftCJV7oeXfw5LXoHnUnWjvb2dYDAIQF1dHY7jUFVVRWFhIcBfgL+T6A8SDYgkDQwMaMGCBfL5fKqurp6YSwd7pStfSB+/LdUWS3+ule78S4pFM6ZFo1Ft3bpVjuPowIED6uvrSx+ul7RYifLPmAAj6XfRaFRNTU1yHEcVFRXav3//5Em99xvpn59L31yfdPjo0aNavny5HMfR5cuXx+B3Ja2TlD/pEUrKl6RQKKS9e/fKcRxVVlbqzJkz01eYR+zEiRNasWKFHMfRoUOH1N/fL0lXJJVNfYESArIl/WZ0dFTXrl2T4zhatGiRVq5cqX379s1IfOvWLe3Zs0eVlZWPkkvSG5Km/JbTe8Knge5oNJrT0tJCfX09AAUFBZSVpRaQnZ2Nbdu4rjvetg0PD9Pd3U0oFGL79u1s2rSJefPmAbwNHDfGZKa/KQRYJPq11pGREW7fvs2NGzfYsWNHZoAxGGOQhJRK1ZZlEQgE2Lhx41izMSN5hoCkCBt4AVgEfBaJRAgGgwwPD9PS0sKxY8cmvGDDhg3U1tYCUFJSMlbt3gGOGmNC05FPEJAmZA5QDXwGiSoZCoUYHBycMLeoqGjs+wb4I/ApcN8Y8+1M5NOapDmSnk0+G6a5g1ck1STn5T0uz2x/zeYABVMMx0j8isUel/z/BvA/7FuX6wTwZcUAAAAASUVORK5CYII=" alt="image">'],

    [/discord\.com/i, 'Discord', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD30lEQVRYhbWXz09cVRTHP+d1hinMTB+0tdZKRVcscNMmkEIt4gKaKHZTYsywstFFSTDtosUF6cKwsKsu+Bs6IcYuLGgaupBUCylN6EKJYaWotYmGMK/MYzK/OC7ezMDMe29mQPgmk8zce+75fueee889Ry5eG6QemMm7zcAg0AecAdoAszBtAavAM2AOmLEiw4l6/EotAaYdbxd0DJWPERrrUqukEKYUblvh2MqeBJh2vEmUCUVHRSRQF3GlDtWcIJMqjFvh2KaXjeFD3o6yiHB9r+QAIhJAuI6yaNrx9roENCfjZ1F9LELHXondQuhA9XFzMn62qgDTjrcrOisix/aLfFuEHFN0tnInSgJMO96Ecu8gyHeKQLln2vEmlwBRJvZz2/1F0CHKRJmAwtaP1uMg3Oh86h33gqKjxVAEAER1jCqn3YwKvd1BeroCvPXGIeafZvnhpyzmEQHAeqm8906Qns4gv/2RZ34xx6OFLNaGevoTkYCiY8AV+ehTq1mQv/2SjBkVvrrVRIvpeWN9sW5t8cWXm74iUFKKnjKAwWoZbuhSw67JAVpMg6FLDf4GDueggZPbPXH8qNB7Lrhr8iJ6zwU5flSqmfQZOA+LJwb6goRCVR1URSgkDPRV/QNnDJxXzRMXut2L160t0ml3XNNpZd3aqsvHDrQZbD+p5TOthiv29x+kuXrDZuRmkucv8qXx5y/yjNxMcvWGzf0H6bI1LaZBW6vvGfI/Xadfd0/NzmUBsFPwZClXGn+ylMNOldvU8lWEgVNMuHDqpHvRa69uj5084f19p001XwVYAZxKxpX/oxH34Ru5cpjvH2Y58YrQ07kd257OIPam8s+/yvv97ph7+SpgNYBTRrmeSS+0mAbDQyHPuf53q9x5fzwzcGo4FzaS5Sc9nVbmn2Y9b0Atm0pfOzAXAGYKNVxZNvz62wypFAx92EAoJIRCQlurwa3bmxyJCm+ePkQ0UiSA3//M83JD+fyzw6XckU4r30xnmJ7NuKmVFDATsCLDiebk3SmQTyptpmczPFrI8kF/kAvdQTJZWP3Lues//5qvNAcgk3VyxY8LWb576P8gITplhYcTcvHaYKEG1F9q1X/hRkrXzQ9mVPxJC1DVHCJvW+HYigFghWMrgkxWd12bHKhJDiDIZLFcL11QFcZVWa5N8f+gyrIK48XfJQFWOLaJcFlV1w6OXNcQLu/sEcpSVCEUAwchQlXXBBmo7JRcOTIRiS0hcn4/w6HKMiLnE5HYUuWcZ5K2wrEVhC6UO6qa87Kpj1hzKHcQuvx6xPqaU9Ux2GVzik6pyN6bU5eQA2rP/wPBBIsfitP3ygAAAABJRU5ErkJggg==" alt="image">'],

    [/bsky\.app/i, 'Bluesky', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADWklEQVRYheWXTWhcVRTHf/87mQg2aTpNg6I7E9yKSAi1qIiCEXHXhYh2UzFQRd1ZMyk+rMlUK1IEv6AoViUSxYWQFhGLLdaFFcEq0mBXXYjYJDPWStu8zP27SUM+ZjLvhXajZ3fPOf8P7r3vnffg/x4C2PKKO2OdR4AuiWMzL+jktRDrrrjf5h5MrZDyyblEF9oA6vM+AmwDsKE0Gk9E67m/RvTD1RKO0Qdi9J1XcvNFdgB3a2PFfYXo3xrg6sJjs2lISBTXpZw4lIrxJdBuoLCyHIN6QzA3N4EXjPaU2jx5w35vyKvdk7ijVPRhULmROECoc1MowKU1mcTg3Jy/3pR4U1bxropLadFHgQfW6gsFLgdBNQPngIo+3JO4o1VjT+KOEH1E0N+qN4VqmJtjOoMBgK3zRU8w4YbbCcCEC/Nt/hQYyEIYLjMdzr9IFZjPaOLBzWfi682KpTPxAGIwI1daS1QLSAbOZQRh65nSqB9dJT7qx7CezsoD/AkQAAS/5wACfqdrr3uvrDZW3Ad+OxfDgmZYWJ/NZ4DOEPwBtkgcCtGHgJYXdEWcBWgDsD2FlBPPttIoQ7QRgK15wbKnFg2gMA7enZcEeS8L8yQ/NozDwhFUyzpl/N46aLYA3XlBxgerZf2yaACg1hl2IY+vw0S+kD+udYbFp2XV9m2ueNB1P4W4F8g9A5rEP8BRoTdny/pymZ9miE37fJtifALrfuAWoD2n6CXgtOwv0hDe+HtYM42aVhnoHvNAtPcDd+UUXCsuCr86m4YxEs01NdBV8R0h+gRw3VUUXxqT1WE9vPD2BZZcQgDFuCuneEr2OQLwUPfL3Lc0sdyAKeYgw/AN8F0eTBS3NjXgEL7KQybrc6TJPBgCPy/jWFZ818XStH8F+jJQpfVUN7YX6KkHn84kbn6qlnV70zvAkFJbO4HWH6HyZ+cTzU7v0RTwYxbDAT25VHy1AaA2ouOSnm9BZhz2LXqR3mqlbmloZkTfr8yvMgAwO6zXsMeas/lgtaxTi/3dHAKaHcNF0OO1Yb3fqNjQAEB1pFAW2glcWFE6tuH68OyyzJBS1bUd88eK3uOqq79a1kfNdFqO0o4x97SbHSb2WuFkbY4PSdTw2e+quFQw2zEdCnx7rX7x/lvxLyJqPrNHQqIiAAAAAElFTkSuQmCC" alt="image">'],

    [/furaffinity\.net/i, 'FurAffinity', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD0ElEQVRYhbVWPUvzbBS++uCiUKzYZnroVLgnP7a4OdRMTgWLfyD/wCXg5mQX3YRA0aGTGHAKODSDdmmlCiV0KGYxAZGmktZAlSznHbR52pqkrfpecA2l577Px33lnBPDFPA8j3q9Ht7f35FIJBCPx2NBNqZpotPp4OXlBcvLy9jY2PhiN46JBq7r0s3NDSqVCjqdDnq9HnZ3d7Gzs+OftSyLarUarq6u8Pj4iNfXV3S7XeRyORweHk70EQrbtqlUKpEgCATAJ8dxZFkWAYBhGFQoFIgxNmIzoGEY9C3nrusGOh9Q13UyDIMkSQp1DoBUVf1eAKqqhjoHQJqmRWYOgBhjZNt2ZAB/wrKvVCpoNBqBhxhjMAwD5XIZrVYr9PJcLodUKhUDPkRq27bPgc1c0MFms4m7uzu02+3Qi7vdLkzTDHXOGMP+/j4ODg6o1Wrh/PwczWbTF7KqqrS9vR0sUFmWieO4wLIKgkC2bZMgCKE2AKharZJt26SqKuXz+S+2jDHyPC/4eT6F84Ucx5Gu6wQAPM+HOpdlmSaJePhLCoQoiiMHeJ6narXqHyiVSl8u5XneV/0kETPGyHXdrwEMl0XXdVIUxc963G5QXlEUSVVVv6SGYdB4AuP8/P9fJ7Rtm56fn9FqtfxW+vfvXySTyRHHb29vaLfbeHp6Qr/fx9ra2kjL9TyPzs7OcHR0FPmFaJqGbDYbmxtkenp6inK5DNM04TgOlpaWkE6nkUgkRg52u12/1QJAIpGAJEkkiiIymUzMNE0oigLHcUKdy7KMbDYb8zOXJClS0ZPIcRzxPE+6rpNlWZF3FQqF0efUNI2iFD0LeZ4nz/MC339YoMOYq9frfjl/ilqthvv7exwfHyOZTOLy8hLpdBpbW1vY3Nz0E3YcB+vr68hkMrE5x3Ei32tWNBqNEVHW63W6vb3F9fU1Hh4e/HENfGgPiqL8SvkHlCTJL7PruqSqKomiGKiLfD5PcF331zQwHIDneaQoyoeTiGb0Jx6Px4rFIhhjP6//EAYD6OLiItRmdXX13w/DMCKjnZayLBPw0aqjdoXBXPHHcSaTiXmeR4uLiygWi9/OfGFhAQDQ7/cjxX1ycoKVlZXgcayq6rd18Tmk8Dm4At99qjVt0CFnDWC405VKJeJ5nhhjxPM8SZIUPYKDMGsQgiCMLBqe59GAMzkexqTROs6g8f0j2LY907D6bG5TIXArHkcqlYrt7e1NHfD8/PzUtlNj2o45cdf7CTRNm7oV/29QFCVUD/l8PnjR/G1YlkWFQoEEQSDGGAmCQKVS6Vuf2n+5BrDyUa80lQAAAABJRU5ErkJggg==" alt="image">'],

    [/vrcarena\.com/i, 'VRCArena', CONFIG.colors.other, '<img src="data:image/png;base64,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" alt="image">'],

    [/sketchfab\.com/i, 'Sketchfab', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAE2UlEQVRYhcWXf2xTVRTHP+f1rWvXua1jG5DhXB0ihIES3QgQg4OERGMI/rEYhRCDiYZsSPaXJP5FopF/IP7YYvxDYxCMQmICGv8hrMA0KDMOETfMNleWjCJ09Adbu3Xtu/7xXrdR+gsk4ZM0ad6995zvu+e8c8+Vl3Z3kI+RlnYdWAFsBjYCTwNVQIk1JQoEgIvAWeA0MODxdiby2ZZcAizH64DXgU1AHaDlsWkAo0A38CVwPpeQrMZGWtofAw4BJ4BdQH0BzlM26601J4BDlq3CBYy0tD8HHAf2AO4CnGbDbdk4btnML2Ckpf1F4DDQ9D8cp9MEHLZsZxdgqezC3MK8JJX5K5B6oCt9J2YFWHE6WIjzpAKnrrG1rlRtrStVTl0rVEg9cHB+TtiWNa1LZft+4OVcqw0FIvBMlUN1rK5k1/IK2VTrkoYyuwrEklyPJSQ1Jwe1gC3kaT7l9l0wdOvhOmBHPue1Lp3Wx8vUKw1lUm6fi96WJS5ZW+Pk2+GIOv5PRMYmE2i5RezATPIeWXlsWAc+w/xsMjp2FZnb/drSMhrK7TlND4fj6uuhCCdHJ2Ryxsgl5AvgLVl5bHgVcJIssV+zwKHaGt00VTvFlvutZkkq6L0ZU12Xg/SNT2Vb5QO2apjlta4w0w+UOmCzjlnbs1a43kBM9vwcv68Q3J5JomfPSA3YaKtp3XsAqEgfNRQsK7erDQtLGJmYkd8CMekLTKNrUOsqEkdaPMJxgx+u3lYHL92i2z8pxTaNF5aUKoDAVFKy6HDYalr3vgfY00eSKFZVOtjfVC1PVtiVLzLDQHhazvljMhiOq/qyIhY4dFFAf2hafdA3zuHBsASmkvJslVN1rK7k1aXl8suNGCMTcdEyK9B15o7UjDh1YcsSl6xyF6vvRyfU0cGweP2T0h+cZvsT5Qrg6GBY/p1K8Kir6I7PNG7krU4ler4ZKRa7dHljeQXrFzlV1+UgPdej8uGftwTMz3R7Q3lBOZKOhtlMFIRNoNFdLB9tWChvN1YqTaDaYeP9pmr1zpoFcq/OgaiG2cncE3ZNWL/IiVPXqCy2qbU1TrHnKX1ZCGiYbdTD4qKG2cMZD8G5AZzVMBvI0fRRDWEoEpcz16KqgGy+i7ihOHMtqoYicdHIGJ5R4LQODGA2kHccRprA2GSCd3tvyqbFJerNFRUFZXhSwUBoWn31d5hufzTXgdQNDNjWVtmMkKc5CGwDnPNniEDCUFwJx+Xc9ahMJZVaWm4Xh024MZXkhG9Cyoo0tnkeEYdN8E8m1DfDEQ70jcvv49OSVCpbbxAE9nm8nb5UHTgPHMFsIO/CZu3Gx5dvSY8/qtoa3ZQWzR0f0RlD/Xojxqd/BRmMxMVQ5pocHLF8mh2R23fBCHma+4ENmB3LXaTeZCyakNNjURmbSHAtmpCEoaQ/FOfIUET80YSI5O2IeoEOj7czOCsAwO27EA55mq8ALWQ4nFJoVlh8t2ckqRSx5Nz/PI7B7AF2e7ydl2btzR/1eDt7gDZrYk7mJ1aBNcgHtFk+5tamz/J4O38EdmJu1YOiF9hp2b6DjI2IpbIV+AQzY++XoGWjNf3NU8zmQDpWTpwCfgIU5jWrDDJXlXkYwFXgO2Af8Hkq4TKR83acIu16/jzwFJmv538AZ7iH6/l/ZYfuLkOS+sEAAAAASUVORK5CYII=" alt="image">'],

    [/deviantart\.com/i, 'DeviantArt', CONFIG.colors.other, '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHDgkIBwgKChULDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RDU4QzQtLi0BCgoKDQwNGg8QGjclHyU3Nzc3Nzc3NzcrNzc3NzcrNzc3NzI3Nzc3Nzc3NzU4Ny8sNzczNTc3NzI4LzcrNy02Lf/AABEIACAAIAMBEQACEQEDEQH/xAAZAAACAwEAAAAAAAAAAAAAAAAFBwABAgP/xAAmEAABAwQBAwQDAAAAAAAAAAACAQMEAAUREgYTYdExUZHBMkFC/8QAGAEBAAMBAAAAAAAAAAAAAAAAAAEFBgL/xAAmEQABAwMCBQUAAAAAAAAAAAAAAQIRAwQFITESE0HR8BRhgZGx/9oADAMBAAIRAxEAPwBWIVdnJaFQBoOPziirclJkBRtXNSVdlRO2PuklW7L26XPptVWY6RP2A9qFoc80Ax7Hb7DMYiojUR5zoAToiaKSLhM5TPvUGHv7rIUaz1lyN4ljeN9IMc5uQQYwwGMCUhNdU/ltPXx80Q6wVote45ztm/vmou81JtjFQBk8JtoW6Ks2RqJSB6ikS/i2np5oph85drc3PJZs3T569iuRTLFMYlOI9Cec6Ko2WRU8/rFCcdQv6NdjeFyNlJ3gW9DbkoA5I5POfijbNGQEWxb2FF2URRO/ahVU8RQZcrcyqrKr7SoDoWpKA//Z" alt="image">'],

    [/assetstore\.unity\.com/i, 'Unity Asset', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEPklEQVRYha2WT2xUVRTGf+e9mVb+pBqKpcaAxAVqQoz/okE0FDe6gIVKYUNwYUzjzJtnG2wqUeMQEykCM9P73pumJmrQFWWBMcSFEogbCZpgggbUGBITI0KtEBBrad87LmBwOn1vOiN8q8m53/m+b869c+dCk3jH99tLpfL9tXXP87qa1QKwGiWOjo7anhdkFiI/is2a2Ur2YeMFo6XSyLKbHqDoeV1nzo0dVyEA2uM4qioI3VZq+pQXBG8VCoV5NxygVCotM36wL2XZhwVmjT0B81XJp1paTxkTbJiLnIorFgqFeamWW/pBB64KaoPeM3AXFvuNHxwRjV7J5XLfxZFmTcCYYEOqpfUk6HZgfrx2+FdM8VJCkLUq1nHjBV6hUFhUuyiVD8PDwyunwnAI5KkEIYCfLaw+x3n5YO1CcWTkDntqaifI5mrdmdBx4M3Ojo73Nm7cGELVBKbC6NtEc+ES6GtE4co4c4C+np4zruNsCdHVwDfxAaQdpPzb2bGeSqV6C2LPA4BEssV1nJ2u604mca4HcZyj01cm1wA/JeohdlyARKjoqPGD3YODg7fOxTUmWJ9qaT0BrGhEO/Fb1yANbJ23YOFmzytvGx8/tzefz0fVhFKpfI+ktAQ806Am0MRNCCAiS1T0g0WLO46WfP8xAGNMmxcEu6wUJ6RJc6gzAVXOIKQFFsesPmohXw355c9AH1GlExLuCtUfELk3ySdxAiLsT1uyAsEHpuN6BV0HdCY4nyTi6WlL1id51A0AkMlkzrvZbC6y5GGUL+tx//PVC6L0fp9OP+C62c9TYVj3Gm3oEPZmMieALs8LNqmwC1gaQwsFfV81eiPnumMNhaXJQ5jLZfdNTvx9H3Ckdi1CV+ccp8dtwrxuAFWuxNX7+/svA7P+WHod51isgWVNA1HcWlKAi6r0nx8fez2pqRk4jvMLUbhKITZg9RmIgI9sYVvWyf5+M8wrcF33a1VdZYLhLYIOxgaI0MeTxngzICIK7DXGHEjZ9p2V+vUtSDI3xrTl8/mmDitAsfjhbXF113UvZrPZU7MC1KLyCMWyTy9qX3ys6PurGjE2xrQNGX+PnZ44a/xgf7E4vLweP/bhUPS8LlusIWa+AxXlY9tiIFS2AW51Ty6bsbxy+QWUHcy8HSdA90xOTAxe+wXVD1Aq+euslBwg+ZK6CEwCt1cXFU4L3J3QA6qfdi7peK7yEqpg1hb09joHRaOHNOayuYa2WnOAOubnFH3pz/E/nq01v9aXDM8bfl4l2g0sr8dLwBRIEE5Nbu/r67uQRKobAK4+0dPp1ldVGAAWNOKs6BcSRb2u656ciztngAqCIFgaqbyr6KakPoXTkUZb+3K5TxrVbThABcaUn0R0COHB68aql0F2nB8f25PP5/9pRq/pAADd3d32E2vWvmihb4Me0igacF331/+jdUMYGRlJ36jGvzJdmRpfa09dAAAAAElFTkSuQmCC" alt="image">'],

    [/greasyfork\.org/i, 'GreasyFork', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXElEQVQ4jZWTParCQBSFz+CgFoYkGCJoITKVexBclOAC3IKFnWV24A5cgK0QUEwsVEQ0YmEx5xUvb1Dyo+/CbYaZb849914gGwrADMAawDPNdXqmcu6/xXwwGLBerxNAUc5zXwohVgDY7/cZRRGllGWQVeZnAAzDkLVajUqpbyBGiRoOh+x2u2y1WtxsNgay2+0+QRQAzFzX5fF4ZLvdpu/73G63rFQqnEwm3O/3rFarRYAZUodpWZaBNJtNxnHMv1gsFkWAtQTQA4AkSaCUQhiGmE6n6HQ6xqDT6VTUtR7w22dDtSyL1+uVWmuj4HA4sNFo5Cl4mhJe07ZtLpdLBkHA8/lMrTUvlwtd182UgNSIQqcdx+H9fqfWmrfbjY7jUAjxZqIqAwCg53l8PB4G8lKOGe15GUAIQc/zmCQJSXI0GuWO9OqTEtu2OR6PKaXMjPJXSlC2TC/xr3X+AblgI7SP3jS+AAAAAElFTkSuQmCC" alt="image">'],

    [/tampermonkey\.net/i, 'Tampermonkey', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAATlBMVEVHcEwbHBomJyUjJCIxMzA9PzwcHhsbHBoeIB4wMi8sLSsbHRowMi9AQj8fIR93eXYVFxWtr6wfIB4gIiAbHBoiIyEjJSMZGhgdHhwsLSt7PHZlAAAAEnRSTlMAy1HXLw3tSLEkRY75422BgzTeRWmnAAABi0lEQVQ4jYVTiXKFIAxERRTf1UIg6f//aDcBj/ZNp1FmTLLZDUGcMxuXN5u9O2xdE/02GUp8tfQ8EaU3E9QMmwEmfk834y/jZ9TnknOtOZe2ai5FERSd80H5NaLxH08Sqt4tgnrU1ZxKf+CIItDIMLuFBPUVEqnkYgt+No4ksgAAhmwYgHThQ/GqkdgAUqsWFZg2CkUpSVXQpQEqXK1OyBkBCFufHZBKTQZQbRAjWyyfuwQqTb/tNdV07PNgUO0KVd1Ltan0fXZAN1UoqoAXaBv2YnPYrXUART0sCxsDiQilYegx5EhCGBDGqRuAOG/jPI9br6Jp8d49EiNjAL7H9mfEQpre/6MnGwNzjXsoglW2eXdvbBLhyDu3EU+nN4pK8N1fQzi/w+bBAKu7hsIF7lb6BxAN8Pj8S6IxyLVmY36cng9pdL7w7aTMTB8n4EYqH1mO/B2joefuPsn6e62hjzJWtlt3G+dW34cav5jDCgstz7iX6jJP+0xjFY23B18GlPWyf/9++5expb4B5uAyTKQ6UvsAAAAASUVORK5CYII=" alt="image">'],

    [/rule34\.xxx/i, 'Rule34', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEVHcEw1nzUAAAD///8AFQA0ZzQznDN5ynkzmTMAFAAymTIyZjIANQA1ajUoXSgNIw22t7bq7OogPSA3cTcBGwEznDON6401nzURNBHj4+MCEAI2SjZenV4ALQCVmJUPTg8Zkxmn/6dWVVZhiGFttW3u9u6esZ7M1cxjaWOq/6pHcEwK3PYhAAAAK3RSTlMAzv//0dDO/tHO//////////////////////////////////////////8AL7qUDwAAAc9JREFUOI11k+t6gyAMhl3c1oMYKlKrIFKnPez+b3AJ0FW7Z98PifCSo2ZZtt3/q23G+jyeg46n0+n4q/OZXt/oePtZ46UiXe6ubYs6CS833bbq4yPbH1HkQRYAsCxY6EXe0assi2x/vuQQVB3ooZjAifgncKsi0D2Aehq67gmcNKwBvPLhAmhfAH/Nq1dA1mWJMYdiynl99YCI/sIHrV8DJZVZ+Cr2QQH4obpZWjGWCVISUOMhaLiTA4o/kK5pBWCgXPRhyJfqVkD3F6gCUOKyzK8om3eWFgbO1wMYpZQMIWQblKqAANAsNDXYp1mwcAXcKnufSLfhMawCZQQMl0mziBl9wS9QlP7ZKOpk7AN/D60tIoCHAaRSq2HB84MhIuZTMKBkOqWImESAkYqtRaP4RuuCyKIJI5svgNPCGSlUWQPUHrQQFMLDdwMwjmicaWbA7xmM471+bLgPJ2+ajbVNY7XQm9nWTU83Ne31zQYMAwLsOFLRQoh4S7BlyRPEELLv57nvlRNuYqvWTvBe309ahhzGlAM5nskb5SDM+Mhh+8Y5AKQcerCPHGBqNrt3+jt3gmMBOI4MwqUcQGjYhb/7XZDLBBjqA1cveE8C3f8B/aI3sSJ/N5EAAAAASUVORK5CYII=" alt="image">'],

    [/e621\.net/i, 'e621', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD8UlEQVRYhe2Xf0hdZRjHP+8595dXTac3jWbqMmOkTdqkFnMsXSz6wYoSoYjSrOE/WwvZihbEqkFtNfqBMNY0o0DyOoa19Y+gEkJS6aox3NLFliv6Mef1151evefpD390vR71eDT6pwcO3POc5/1+v+/7vM/zvleJCP+lacuHUGo5ox22Rzatv1GcxhHI86h72GYXRtlKQUe+Mzw40abgTgDNCOex9cyPdgTYS8Ed7bv0DR+7uC5nDCCsa7fawgEQkaU+ySISEBERMUbkt8afws35L9jAQURsCXh/klx2isibIiIy1v+6XQFL3YQ60AUUAS1AJpCGKzFgNwP2NuEK2oIroAr3O/Clfw1kW8R7WvxljXO8rSVx0O3gmtvg/vbByE8LV8H16RVAPpBg6RHeUSV+F+AEKoEGoFfyD3QZqH7DMzpgtOTVWhKgSqqTEPYvPOHoQWQhw7uBcWAIuA9IU3HZacpXOB0UY0kAor8GJC1JwKS9op44lgocBeKBNCaGu3EkAIQ0Cb8cGWy6B9Sj1bnoWkW0P8HrZMttqazPTOLK0Bht5//k+4v90WHxhBwHgGen3nNwxN3McJcgVFJ05sKiAtDVu0yW3IxtzPZRt6uAGxL/WUFDhCNN3ez5tBMjspqUlKni6ippKD8NxABK3Ck7taK6qjmTjS5DVVz7CEpORPrSfbH8cPBBPE6d3r4gNS09FKxNoTAnFU0pXqo7zXtfnotCVl9JfemWaf1Au9lcZwlQD3zgxht3FkVWZFBNxd08vimTY809VH7SQWjCAGBdeiKtr25DKcjbe4pfroxEw5eIv9RvRjxts1MQG78bJCs6aPPaFGByyQ8/tYFEr4sEr4tVsS7ChhDncZB7U6KJADmkymq/kI9KR60JQLZHByjFTN53bJ2/H6UmeMzcGYyE1wHfWBSg9oG0zJIkcPZygLyMVXzY3MN3F/roHwkxEAwRCI4TmPo9eG3cDL9R6svnJYeoPiD+0lYmu9csO/FtLwB33eLj4l/DnOy8TPfvQzxZsIbkeDcDwXFMjpQQula5EDmYVcFj1Rlo2jlgZk0duqJp371szPYB8OvVIMnxbjxOnb6hMXL3nCQwEorGPij+shcXEzCnE8rx8kugDkX6JsLCQ2+1cPhUF6PjYVYneXE5NDp+vsrDb7eakf9BSN5YjBzmOY7V9qNe3M7zQNocxUqxOimGodEJM+KpWahyaSitsSLA9CyQz3cEEfaafTNE6O0Lzk+u6OT2S7VWyGGRC4kqqWlD1CarYFOIm6X+mTar4YvcivXnAcMyufDZUsjBwpVMFdc+h5rbHU3NQZXUlfWuqIB/21bgv+H/ApZnfwNt1hwO8fIUiwAAAABJRU5ErkJggg==" alt="image">'],

    [/blender\.org/i, 'Blender', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAER0lEQVRYhe2WfWjUdRzHX5/fPW6ee9DdTY+GFlN8wAecDHtAKSrIwtQemCaIEVFCprm70AxHRLitVBINMjIR01AZZQa6hqVLSgQ1E4URMoltd7fc4+223f1+3/7Ybrv77c7NLeqPev/3fb8/D+/P9/fAB/7HvwwZS3LQxzY0fEpxR1O84a7k23/MgAIJ+ekAxvUTCmGH+xbvyjH0kdbRRmtAQCnFWiDcTwiwJXQ/1YGt5N9DnbEh6GM+QhUwNYFuQFHiqeT8cPmjvoE4PJVc0SMUo/ghgfaKUBPwUaqGGTKlqMpwBsMsFoMsLImCKV4bPIvCpmA7MC2plqLKprFuQjltIzLQVIrHolGrTIXGiDolvJBfzlWzMOQRWIRlf3NzgGmiuBAsZZ1ZSHkDmsZBYHqaYtnAxJSK1Yll/CSUEcNobwSV4msU9rgz2ChlGCkN3A2BUh4XjcOAJ5G3z15OxsMbsE19BLHYAFDd7fTe/I6uHyuINVxO9qD4xF3J+hEbUGVooQhbUZTB4GspVifjSw7hmPP8QGxLRxdKwYSszD7C0Amf2UbX2R3JJmCNu4LDwxr4YwsT7TqHgKfMWtaa4zjmPAfAN7XX2XfiJ2413gFgyqRcXlvxECuXzAGg8+sNRC7sSUxvMgwKLdwFjX6KrQbfi7DQrNlnLWPck+8BcODURbZ/dprWzsiA3tbZTc2lOhw2C0UzCrA9sITuS5+jejvjIS4N6tP+iJr9rLfAORGmpNKdC18GoLUjwq6j59IO8fGxWprbwog9E8e8kiRNCSuGGAiuxxXycdiAvYAjXWGrdz4Av/7eSE80ltZANKZzta6hL2fyXLO8wJp4avYzU1ccR5hlCmwFcpIY6Xt9xmXY0zaPI9NpiyeZJffADTT5WGXARRna/GeLUGLi0JuuATCv0EuBJ8csD8Cbl0XRjAIAYv05AzNATFOvYgv62aMJXwIuU/7BcJBHUVw3F45cOgCA1aKxe+Oz5I7PGNI82+Vk95vLsVstqGiEnitHknQF9RLwsU+E102KLhp+dzk741TQx22EgkH7Qvbak9hnPg3An21hjlRf5kpdA6CYW+hl9RMLyMvp21c6T24iUrvb7PGABPyEBTITrqXFEFbll3M6MTLoZwfwdiInDhdZq7/CPmPpkOkHhzEIV2+nq+b9IZLAY5oobiQQN3piLDI3B1BWdonQnsT1dNL2xTO0H3mJ6O1fwBj896tohJ7fqmjZuyhlc+C8u4Kz0r6ZvG6NVUpo1Vs54f2UrnTDhHy8ooT96XSxu9By7gM9ht5aD3o0XWhHTKfY+xE373klC/jYKcKme82LQ0GvYbBy8oecglGsZPmVvIXwDmrkm28CQgqWxpuPygCAp5wPdIMHYfilEwBFVMF+3cLsSRXUJEpj3opDmynCyouGYjGK6SLkAgYQAK4pgzOaxlF3BQ1j7TUi9G/BYx7sv4O/AIYkcLyFAEp9AAAAAElFTkSuQmCC" alt="image">'],

    [/smutba\.se/i, 'Smutbase', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAIVBMVEVHcExfOX1fOX1fOX1fOX1fOX0yMzFfOX1QN2ZhOYE8NESTwwA/AAAABnRSTlMAH8s7nl3/fO5pAAAAs0lEQVQ4jd2SyxLDIAhF4yO5wv9/cEF8oOmk000XZcX1nDFqOI5fVYh0+pxiTAsGESiteSgawVfpS5oLM3qOgGCtqhjWyAWIIggvVytROtYkKFSBaCoOUxe0n8rAslSacO1Kj1NYlNnCCWPdb9Z2WM/lT1x3CHm9mb9zDvaWTgHd8aKYwBuuin3ahEJxw/KD4QWcO/8TIdjkqcDSpptQXxMyqjqt+Q3uygM25RHXs3zg39cLHiYNnjtYb1oAAAAASUVORK5CYII=" alt="image">'],

    [/sfmlab\.com/i, 'SFMLab', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAeCAMAAAB61OwbAAAAJFBMVEVHcExfX18KCgoKCgoKCgomJiZNTU1TU1NZWVlDQ0NbW1tdXV1XztvlAAAADHRSTlMA/hYLITBukrJOzuZZKX0oAAABMUlEQVQokcVSSXLDMAwT90X//29BJW0dT5prNT54SAgACa31X6f0c98pl3wkIPJPAAmi4DtCy92N8WdN2/UG0KRztosTdZnySx/tDjDkPiBVlSW/FJIomqqpVT6uy+LMH0RQG4rRcxXt6fseo99D7SliNjJmnjZ8ZlDag6PJ55JB3YRFhDGmm9VzEqNWS6A9XatTuOEDYszHJ7YSPFMMu0HZde+xBCp5jhAKzuRKm3U4DNRlTyAMNQ+DsosFxAG47HEkIMd2PmUdHbsCauTFdytb75px+gXAm0rWuJsUW9ehvIbrqMIqhP14nb1doxRYT4U7OIHX2nTPWuAKqmdyRgbxSjAuDALpVRUTkd4fCxA64Z2Tj7DXHcHm2d1xMnjzXiGODZ3zrv0LeQb4F0TedL8AUTQI8L1grPcAAAAASUVORK5CYII=" alt="image">'],

    [/open3dlab\.com/i, 'Open3DLab', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAARVBMVEVHcEzd3d3l5OArRqk+VK0/Va0+Va1AVq1AVq1AVq3f3941Tqs+VK1EWa7h4d5SZLHY2duLlMGwtc1ndba4vNCYn8VDWK799/aOAAAACnRSTlMA////yLPtKg7ZVA2rxQAAAOVJREFUOI3N0tEWgiAMgGHZQpABVorv/6htI6I65l2duFL/T50ch+F3yx+eDt7GyfVTN0XrXzPORKc7cSeiGTtxklc4XyqRfDnDKkTv8DFuKwRjmCBZS8jZmABrjNFXQPMVslGid/NhhutMDSyFkElQojlwRipLA/LwRlqWF3VgshIdJch8nLN5BnVEIfeslxpIjxERl6VlHjMpkA//8BVt5zoJkJLM8ZofRDaLAbv3rGTEDnB8z7ymZzDt/BD/AaAB2AUjFd4gBtkUGneAs0RFQCGyO9tQyZbS9ilXEuNBVnKcv7JuymMPkUGt3F8AAAAASUVORK5CYII=" alt="image">'],

    [/open3dlab\.social/i, 'Open3DLab', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEl0lEQVRYhbWXW2xUZRDHf3PO6W7FFrsghgcklFCgQqAYEoNgJQoqiU8kRSAYQYjxQRJDNNEXg4mJJkR9IMZL5KKSKBIT5QVJWiAICI3cXFAUoYFik6Zcum23pXuZ8WG7t3N2SwGZ5EvOnv+Zmf838818s0IZWb3aKl2X50DngdQDk4H7RAibMQoIAfcDCsSG1HqBFHBThA6wDnB+N2Pvli1yppQf8b9Yv97C/f36phkbgEg5grcvsg9kw9atcrosgTVr7GGH1G6Dhv/PcZEMGrJu2zZvR4DAKyvswVQo0QrU3iPnWUmDLd36VeVuACf7Nlkx+LmZ1ZoZ93i5Zny5cqVFADyAl1+MzzXTpaXohkLQuNBjer1DZaVw/ZrReizNmWi66LvayQ4LnnAZ95CgabhwQdm/L01fr5UyO67SG1gHbPIAVGUZBD8cO1Z44+1Kxo8vPqsLGl32Naf4ZnsCgMXPeqxYFUIKPpvV4LL4GY+PNg3SdlEDtg17HtjkAJil52BK4RKUV18LBZxn5alFHo1PutRNlYDzrFRVC+tfDxMOG377YFNhKAWoTvIX5NRpLlPq3NzvgQHj1IkUrguz53iEw0LT8hCxbkMEVOH0yRTxuDFzlkdNTcZgZIww73GXAy0JfwhCeQLYBPNlYPojeedm8P67cS5fyuS9drLLO+9VUVUtVFVnHH3xST9HjyQBGD1a+ODjakaNymDT6132NwdSHIJsFahWoErhikTyIem4kuZyWzKHtf2T5PxfqRzeE1OOHR7M4T3daf6IJnN4ZIzgt49qnkCpcvG8PNXe3iDe1Zmvgqtdimox3tOT33FFRUkfFfkUmKaBfMz9YkOHqECSSSt+9uEU5tQI4pDME1AdZCgnJQ1gmbCVxbkFXkIfBnMEzGwAqKZYpciW+Rya74PhcCOoDyRyBDC96UcDOwyE8C5xK0iBqvUG+0hxCE2LDf78Ux/HDg0A0B/XAO4PgR+XwjMgqrHAZOBLoX8HHe0JOtoDrMsb8Omb0Z4jYFgseBX4c1ryUinvPhAQXwQkMyFlq6Dbb+DowTj/Xsq0z6td6VKneFg5/ms/1zozzejGjaC+GT/mCVi63T+dnWqNc6r1tnwWSfR4nOjxMqBwuu6x2r3syUWANpPbC/FdSJ+j8tLGjaKQqwL9s9R1eg/kiihNO5rrcoOpB+B4nNSUKQUj2v8r8jei21OJ1OZdB2b0FSHZhxWLzkWBmWVNCMsw6zGT2YhNQSSCWQ1IjWR6YQyjFyQO0mkObQ60JS11ZlfzjMvl7ObuPCH9g5mUJWAmE79tqf8Q2DvSfY9EciH3zNlsatdNM13Pv1R1VVPT2dBwxu6KwNct9dfE0mtR1cD8ZoqYNjhdurtp4dkpwxlcvfBkzQuN0fqREgic/eWN0bUm9ilQUUZHgUPAOaAbqEHsATMZIzANmIjRsvPgrEV3RABg+fwTc81xvufO/yW17PylYUQESpbdd4cf/c1L3GwQ07cw7SyVkvLLLgr62UiZ3rL9LFlyPlwV65kvwtMGcwWbBDKBoYlGoNOMc4hEzdiz68icozDytvofZSPkFRuJc68AAAAASUVORK5CYII=" alt="image">'],

    [/fanbox\.cc/i, 'Fanbox', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAw1BMVEX////68Yr//5P99Izr6un//ZH/+JD//JL6+/ozKyb19fTx8O9qYVn/+pGjmVWmm1g3LSrb03cvIxTX19XGxMVQRCPu54S8tGSLgkRjWC1NQiJYTSSHfkPLxnFMQBqSjori4eRIPiWrpFo8MBtHPjZuaWeZlZSzsa29u7lcVU8SBAB4c2iWjU+UkYrLysY4MCZIQD4fFhB0azvc29c/NSm2rmCCfHhMRDknHBCnpKJuZ15fV0qhnZZEOy2MiIEzKxpcUkA0kaM3AAABfElEQVQ4jX2R22KCMAyGQzkK6JyKB3STKbOoA0URFJDt/Z9qLYgTKctFmvb/mqQpcCxD6B4CS293XqT/ANTpdjviE8DLSJQkXpJkhF57/YEhVQARDUfjiWGYpjmdvb3Pwfrg+DvAI24xWdqfK+w4Dl6tN1sAbP5lELmvpT3vQ2mCC+DtFnIJoNFy70DVsG/IfAkcdit4NuEYj8tJwP5Y0ykSnG4EKCyddLEcFm0ytLlFfTiRG4BL5AlkUYORyASEcxDlr7pM2xwv8nUg8t28L8U+kfkOq2JIJawLRS1jaLRMEhxj11NA6GuxEwiPfNLbqmuyaq6/weBFq14QqBWAqClNnMQ7DEp2xsoaP+juleD0QO17Nr0ZO5CGD0Dm0UbyMEzzg1jT3edHbXNfVNaSbqI9P/q7srVqU4kPtaNH00J/dosGTCC1vZ+ikt3SWYBazAHUKFNtzCJIbup0cj1lpigsi0gqZ92op1f6Q4NGwIksulwaAav44WTeoP8Ck5Mhx5fwA1AAAAAASUVORK5CYII=" alt="image">'],

    [/subscribestar\.com/i, 'SubscribeStar', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAt1BMVEVHcEyz1NVYurA8qp+g2tVTtKr7+/wkoZR5ysKs39u24t5IrqQyppv/8fdauK78YpZGrqTa7+xmvLSph51ZtqxOs6n////9aJr9cKFGr6UAloj/P4H///8AlYf/O4ABlIYFmIktpZlEhYf/NHwCkYNqvLQYnZAJnI7tRIH2o7/X7+zH6OTeUYjzsccljYbm9fOFb4OsYYO019X71+RneoZErKH4gqmYxsP/R4e/nbLDUYDrLnrywNSPUHO6AAAAGnRSTlMALnLrPsf+/kcgTejw96390KWo67y0xc+At5YRSPcAAAHQSURBVDiNZZNpY6JADIZBilOsWnvtEZgD5HJVwNt29///rp1JgtX2/QTk4c0xGc+7KBo9/Zi0aRWEA9/7rvunYeLUHnJpRHj/JRw9i7RFIBkWWkopptF1/O4BZF4krJOUICG4u4qnGgA+ia2WADod3cbB6GLIWbY5OII9ogeMO4/DH/YokAiojme4KBdLAtpKuvcpJcBnidIVEyfnKoXrNsQ4iNSqMlBRlrZ0n2Xoeb7AJ2O/D5NT13UvOyz1QBa+NzDQA0mymCurx7VFCups4L3CDRBbqfmR+3A5qEcG1gjE8XzBDnLsUQ8AWP1urpiYEAClVxIgt1j88RwrsuAfqx7I32nIk93CEWplgAEmm1V/UsOjI+oGOAUVabK/656gVjLgIl8lAareMbDGKjLMoUMelMls2n902tirIsAOikYNTR3bES62y+XyXV0AWfr9YekVdveRW2VONKeZW2e0MB1mVhu4kixxuaf4omua4UabT+CNVi5wdmZPQPzR9IQeR/3SYpINn8OKGuB9Qo0EbgcTKt47QovL2ttCA3A3ofeo90bC+Ob2RW+lvU7QcR11U/6+uXrOZFYaafbUy/nl6+V18gczUWX1+fHnr6vr/x8mRF0uqQFiFgAAAABJRU5ErkJggg==" alt="image">'],

    [/boosty\.to/i, 'Boosty', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAS1BMVEVHcEzxaCrxZyrxZSrxaCrxZyrwaSrxZyrxYyvxZivxYCvwcCnxYCvwcSnxZyvwZSrwaSrwbirxYCvvcynxYivxXizxXSzveCnxXyyl6vYyAAAAD3RSTlMAETJEIpL1ecperbDj3wcExjacAAABR0lEQVQ4jXWT25KEIAxEAbmLgoDO/v+XLiTBUXftqXmwcirdCcDYkPuQVva/ZqrPL3W2EGBe6qKUAoB8AUzp+pTlzcEWJNRLXWoEyvQCuK2rlIUDLvgTUBsKh5zLosStzhcC3NfvlmZKKQEAQ5puVsp1ZSaBNtv3vFK74i5DIpCg7UR223JGlToiAEPOabsmgiFjV0p6BTqNRGdO38vtP1/ydJ0xQ0TBSVqt9QMQVI99yFVKORHhx5C11l4P51CQKKVxN2wFxZHJYKKY6OR4zUjQt9DkGGgPIoNqVcYYjgbQYDi4n0Hk2nY9EkXNT6AJmeyYoEAxfve47zsxga82U6IxYzucHdUYxRS1qvZ7qeRxELLLabjZ66XzR1erWxagUc7+9gDFgdqdQq/g2F0GgeDQzP99XZPtgA+detznE1Hetp8Rj9f/C3OmIUW3oXxtAAAAAElFTkSuQmCC" alt="image">'],

    [/carrd\.co/i, 'Carrd', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEaElEQVRYha2WP4xVRRTGf9/kQSCraCFQk6UjwQ3NNjSExqAdJoKC7PuDwKooDUu5JdCILLuw7D4WAggm0hglNIRGpSNkEyqWUBsb5X9Ccj+Le+e+++7e93Y1TPPumzkz5zfnfHNmVK9PYIQBIxKR/U/7EuXfbyXoQ0vbDEMJ2mDxjhEJ+sfw2NJ9ozuGXxP0zIDVWZvs1wIDIGqySccEmGCRKB0GEeyNiRgD7Q54IDEgETCJBTIB1iVoHfYw4gDoecDXEnQCewGlaxulW7FAaU8QIMc9gzDBILxK+LjgQbBbwgMCAs7s43c2J85P1xoQtAJ+IDghe1Vcm+xX2b6DuidGiMFg7gqPCa8UEArjy4RAsDLgo4K7sjdWQYTYWZi4RfC78FAWiY7j/wdBwEOC32RvKUNkADnERtm3hNeX0vEmINYLbpUjEeICgtXCPwnWljVxuT36piDWCm7IXp1vkNyIccH7VZoAuNwe5Ur70JuA2CwYz+2zUAzKPlJKRz4RYKQxyb7GFFfah7jaPrgsiJ/PN3pBHMnSTS0rD8eMVqQ1Ie7ZKY6jYNK+fY0pEsHV9igAnzbPkWR1JK0NcGN2P7HFMCdxrdR2RcDHEtSqAW8LdueFogoiF02nWO1tTmHED+1DAOxqTfPj7AEAPm7NYIkbMy1kYrEqQ+wK+EgAdgADXcejI8xS36JixZ7mWQCuzx5gV2uaT1rn83TQXxMDgh01YHsnXOmuF0ciGyuV7cvtLwHY0zyLUbrLQjqiZalsFyOxvQZsptB6QcQUtC98lduONCZTO3VmBzuHiJGruDsixOYADFJq5dAX+1r1CZr1ia6+XsWqk4KeR3SwBqwpA5Qj0fkPM3NfF2y6hXnpQirIz5rnCC6ngKpIrKlVOS9DFEP5Rf00BmbmDhOv8rksLfGIKpdwB7IXRA14ArzXH4IcYvriN91jNo3GGYy4dCEV5d7mVG5T1EQFxJMa8KgfQMdZmo6DI99jiem5w4uEWW9MYiDQedRETfSAeFQD5oHhpQGg63QU+lr1CQzMZfoYaUwSXEpBNcR8DbgN7GcZbSlhNusTWMo00Lnz4+wKiNs14CbwHBjo5Xh05BRTF7/Nv3sJU92lFoBgym/MCPEi4Js14ClwHWj2233qmBwkj0qvC0zRZeVDl0RcAz2Nx/A48Dmwoh9EGURUX2Czc4cBaDTO5OkoQbwO9olEEAEWgO+Ao0sBdO0+cxgh4hHdXz+d9uVGiyBOgR4Gm2IhGgc+oHQ3LAfibOY4HtFOAStrwgRrPpHH4+wiwEtgJ/AHsHY5zovCjJrvrwn9Bd4ZrBcxHeVSvEAahZvA+qUAoh7i/vo+auw/ETtAC8V0hIp17wFbSQvUspwXIXo8auYFW2XfKz9qqgAgjcQwcBJ43Q9kCYjXgpOCYeGF8uM0f5b3aK+AMWATMAu8+A8QL4XbwCbZY4JXVa9tsVgDVe0haak+AnwEbAOGgA3Au5nN38Bj4D5wB/hF8GwJTYDgX8tCy4cPreUKAAAAAElFTkSuQmCC" alt="image">'],

    [/newgrounds\.com/i, 'Newgrounds', CONFIG.colors.other, '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Ljc3Nzc3NTc3NTU3ODc3NzctLzc3NTU1Ny03NTctNTc3NTU3L//AABEIACAAIAMBEQACEQEDEQH/xAAZAAACAwEAAAAAAAAAAAAAAAAEBgECBQP/xAAqEAACAQIEBQMFAQAAAAAAAAABAgMEEQASEyEFBjEyYSJBcRYlUXKRB//EABoBAAEFAQAAAAAAAAAAAAAAAAUAAQIDBgT/xAAoEQABBAEDAgUFAAAAAAAAAAABAAIEEgMRITFRkRRSYdHwBRMjQUL/2gAMAwEAAhEDEQA/AGXV84xlVr6qDUKGClwGPQX3OFQ8pqjhTq+cKqeiqlSki5o3Vh+VN8OcZGxTAA7hB6vnF9V1VSjwnjlZxznMU9DQzPSqHTMrWlbIrdoJsQWy32JAF9sHI8RowUf+1mZU1xk3Z/O3ut/iz1yyililFKJJNNJ5QrNmAuQE9zs3da1uhxW6IyP+RrS4/N1ayY+UftPcGDr19ETRqIIQolaU23drb/zbA2RkfmfZ/KMxorMGOrNwsTmKtq6fh328ETyuseoFzaQPVreMdUXAMmTR3Cf6lndGwWbydkH/AJty3QtzPDUyx1MlNHFI9SK1UsoBOVs4NhmJX0/tuQMG1jtdd1Xlfj3FuZeeDwaGsk+moqySpZEAQRRI5dWEg9SXbLuG6nrhJJh1lZ5HR3ZXkZ1aRmLEEki+Yk9LdcAZBvlLgttBwHHHY086ICQpKpSRVdT1DC4xMAjhEXYmuGjhqqacGm8ekmR+5cos3yPfErP6lV+Ew+Qdgoiip4s2lBEmbuyoBf5wtXdU/hcQOtR2XbV84hVW1X//2Q==" alt="image">'],

    [/baraag\.net/i, 'Baraag', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD+UlEQVRYhbXXz29UVRQH8M+bmZa0pVZEfmgpVmIAIxhiIjHCwrVLkIUoK8S4VRMT/wCjicbEPWxMlKgJcYEkSjAuWBSRBRhUFLXQFlEiNRRKh/64Lu57nTfTeTOtP77JS2beu/ec7/l17zmJBux/IWQ/e/EwHsOjeAj9uAfLsQwVJJhBFTdxHb/hIr7FGXyHGwiHDiZ1+ub/5RTfhT3Yi62pwnIIhHRJksRnEZjFXziPj3AY45ARSRqUr8HbeDa1DlFxRwd9fVHx1BQTEzUyi8QsPsXLGMlIVHILluMt7GvcGQL338eBA3R3MzTEx58sWnGGMnYj4IDoGaWc9XvxXLOdIVCpBCtWBH19dPcsWXkeu7Cf6PlS+nINXkRH0a4luLodSimB9dkfeFLM9GIkiVzO/ltswlN5Aju0sP5/QAk7kVTEet70TyWFsPDdIsO1ET0VdOHepSitlIOuLmohCUJgZobpaUJIFnNW9KGrItb7sqUQ2LKFV1+pfzc3x+RtLg3z9elgbKxGpAAJtaO0rdPCvK8Tvb2J3t4FssDWLcH27Xx4mHPnAlqSmE/CJSK0eBKrVyd272LlyuY5kkd2Erb1QH7BL79yaqj+e7nC4ANs2xZ0dib6+9m8mZMnC0XOIWQE2vCsZ3BljC+OL1zS2cm+fezcEZRKrF1LkgQF9lUxXUqZzLUl0IBSiVIpUS7XnmqVkcs1xh3pyVIQhgncqYi31HR7lTUrQohZnyShTnhPDxs2zK9SnUp3Ng/weEYgaybaoObKwUGe2V3/tVJh3To2boxkZ2aC0bQUC3AVsxmBW23VhxqJgQEGBpqtqh1MFy7ww/cxVAUYJlbBHend3I5AmFdQZFVQrXL+PEeOMHEzKSIwKbZrKmIC/t5KeZIwPp44epSOSlHJBLcnGR1jeJipqULl8BmGMgJwqRWBUonxcY4da0lTFqJYIYULj+N1sYGdJ/CjGIrOQvEJ5XIrAhmJQvyJ9/EOrlDfE/6EP7CuYPMNsVT7cqQXg0lcxgl8gG8wnW/NM2EjONuCwOd4Aw+mzwBW427xOk/EUp5ILb2KUfyMC6lxs40zQZ7AlJgYT2vux1XioHE2966U7i+le+bEkp7TZAApQn4uWI9jeKTJur/ElvpLakPFf4HGweQlvKd5Mp4Wh4pToqV1RHIyKmJ/OWURnmgczXrwrjg4NNt5DV+JyTSqlpwd4gg3KHbXZ/BmI8lmaMzoW2KNVsXevbvh+ypxbtwjFv2sGPOSOPkkOTnZwdAS88fFoYNJxvY6XsPzYk5c0/y6TlIDOtVaO6JXRtopzgtZgFw8l4tJ+QQeF8f1frH8lokW3k5JXhSP1xNiiCYyw1rhbzSXMl7brR8kAAAAAElFTkSuQmCC" alt="image">'],

    [/linktr\.ee/i, 'Linktree', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFM0lEQVRYha2WWWxUZRTHf+f2Di1dFFpIS1mkbSpJkYpAAw8g0MbIg4mSCEqo0hjwgRcT1BcfBB/UmJC4JSYmLhBERZ9cCFADFBRDFFpo1RC6MKADhbbpQktLlzk+3OW7dzrTwcQzyeTe73/u2b7v/M8nOCIAy8+9aOcV390sSK2orgAKAEEAZbJ46+K+awLmr0q3wnlrIn5wYl7p1w2yZyKoZgE82l5XFsnSQ8AjCKhrTMR4MmvuO4Kgfgw+DqiAuEAIF1rktvXU8UWfR/0Aqlu3lUgOp1CZ61hQx0JiNl6G6fBklQrjsbER1p8u29cui79ZPK1oddU5VJekLHWi/5Bls6rus4S+MvsUxBWa+n/MXCk115+v1TgHgiULOVX3EIiaKBJ0guvhIyGIY2AyrqBibbXjKltF3MUkhj1MPdB9v9s1zPjtMUDJKs7FysqYlL14BgKABEBRrbVFdYWvlCLDxF1RoPX137n5/RUAlh58jPx1xUnKktKkJ1UWQoEHOhGbfQp+1HvmBoN/9SbvODG6179sZXxg1GA4bZY0AKHAAhH13Rl1xSu70PfbLVpeOMmFLfUMXu7Ho4ZghogQfbeZS6/+yoXan9ztce34+5uYloglrppx6T176xB97wLjg2OMdo/QtKWe4SsD4UREiH7QTMfeJgAGGnvorr9m3Pnl8nyIqXJNrE7DdVVzcBQQZfz2GM3bT9L78w0AMouzySzKYaCxC4CC6rn0nIg5Ri2hfHcV83dUuDaMPVUvMbNnUh2rMxXyOs0NW1yyUSA+MsafO0/TdexvUonYFg++tYp5W8v98vsd6D5oAFHAxqOH4KlyOXP4n0GG2vr95TnPljPUOsCdjn6SSdGmMrLm5tDdcB2vxvkrC5GsDJ+mjSv1KrDNkHmCXPvoD9rePJ8y47QisKblGSIzs1KqWJ5iQnhTUvJ/kjR27KB/p53MGLxv2WwWvlQZMnbzuw6Grw4mNVa4sZTsB3KNuoCVZeP5CE8NMQEQaAv8JlRmrCpi5qoiR12E6IfNvnMRoey1ZfScitH7SycA/Wc7Kdn1ODml9/su1JsjGgzB8hAsc87V/xkecDARIfr+RdrfbvSdl79RxYKdS3h4Xw35a+YAMHLjDo2bjjLU1udbFdQvaqiU7r+lIi6XCuJRhHiXDmcrOvY20f5Ok3G+p4r52ysQgYxsm8rPapi52glitHOYxqePMXSpL2QjgQAdfyLYwSEfoAn/niNK6CCV7V7B/B0V3Doc5U67046FG0up/LSai9uO03e2E1RRMaPccJFHcGl4wPSsg5W8shSAjNwIC3ZUoKrc/OEqt9xpmFc5i4K1uVTuq+bSy2dYuGspuYtm+N+TaD/wbyfuTirxgkglCth5ER76eF1STknVjda9aEoSPOl4TccdOllncgDpok9xswhN3CTfp8JtQCV0JZp8vU9k6uSJGs3wpd0MokQcQS2UnuQ84HGVWzcJ4lOJqbNnS4KTKLQP0i3rY3VHLGGDSU1NvcJXXJfRnJRGu4YZHxwDVabNzsbOi5h5L2YUJ70PGPyobcX1CyzZMBUP+HNdDWVPmzWdyKzpIY5P1udT8QDx+EFrou3qIRVtdKJzt0LVr4Zi1oO4t+7jwe8DpU6Jx7Ulf94TX1kN6xvGMzPYjBJLt7P/G67E7In4k9/K5gkLkCOF+9ttO7IGpeVeeSCll3Q4NBJhbf3CA1fA8IDUF30S7T+cuVzUek6QoyBduCdyah4IHLjkPKCq0iXKEUtla/xyycoThfvbPfBfpplkt77offgAAAAASUVORK5CYII=" alt="image">'],

    [/cults3d\.com/i, 'Cults3D', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEgElEQVRYhe2XTWhcVRTHf+fNmPomQ12EunAxaYpIsDMPB61thUBNReqiFEFxEVSESsFFVBqrMc3HzDDSSP1o7cKFm5R2Iy7UShcuipiCoug0MykoVNrJTkI2YWZebd/c42LehJnJe5NYdaVndd89973/79577jn3wX/d5E5emnW057a6DxnRtKrWUK5sp/fqi4ti/lWArFM9IHBCYScQ7XDXQL5XY16bXoyX/lGAXGq1D4mcUhhpiilcFVgQsBUeBgZ9n4eS3yKxd94syq2/DZBLrfapRH4GEkANNLOF3g87P55Pu3HP0xkRPep3zfeb2L6NtmVDgGyq+iXCQYEFtL5/srR1pTuwO6RizgEJlMxUqXfmjgFyqdqoip4ClsDaN1W0r28E3ALxLeCJWsOTJXs+bKwV5phLqqWix33O0c2KA0yW7HlVeQ+IquhMt7GhADeo7gS2AUv9xr6wWfGmRaMyA3ige2Yd7QkdF0om1uOKovD7klU7m3GqDwhsB1ygjHIJsebCVmaiYFeyTvUaMHhLb+4GArchEGAuqVZZ3EMAArsUdnUESwJhCMwrwL1hkxD4SWHQiHkmDGDdFsw62lOW2uegwwAKP6JkgKfA2mGp7BaVV8NEW83Aig8ymknVTs4ldZ1e2wrMJdUqa+0zhINATVTG+9U+03GWr+dSq79B5PgmGPY2GyJ69AYuwFjrgDaislUda4qr0T2TpdjpoEQyWdq6EolYO0BPhinPOtojkAbwVxARPZp1qgcCAXKp1T6QaQBRGd8on08U7MpUMf5umP+mVp8DoiCX/GT0ie/6IBAAoiNADJhPqH2mm/hGlnXcARH52H88DRCJWK8Dy8BgLuUOrak2Gyoc8ltf3UlZ9WvGCYWaYvYKxAQWEn4OmSjYlZxT/VphpPVUtKyA3g8gGvmum1A+7cazTvVIZ78hOg4cFhiVxrFdQOv72yaj8kNDg4FmlwWN6AfuA7CiFIKE55JqZVKV5+t1cxV4u9M/XYqNqVEH5FJ44ZICgAiJNgCf8hqAd7s+QICVrdqTInKWRlkuB42ZXoyXpoqx/T3EHg2qmob6dr+52AYAjawFIJY8FvTxSMS6DHjA8hZiTwSNaVroRUTkEQBFi+sA1vYHhta9SCOIFArAtj+oDXcD6GJ7oT3O1gBU5ALgKYy0HpO2CSgX/dYbf1U561SPCOwCllvjbA1gqmhfR8k3YMy5fNqNrwOg/hGwBDqcTVVnNi/uDgDv+195eaJgV9YBAPRrLCuwACRM3VzOJCupVv9kaeuKpfIs4CFMZ53KsaAC0zHzA2C+oZEXzk8VY1+0T6rDMslKSiz5lMYt10PJW8jFu8S+0gyurFM5BjLrv/ILyFui3uVm5OfTbtx4pFXMC8Bhf9y8aP3pztMReCfMp924Vzd5gdGWbo/GUe2jcVMKsmUaJXiwpc8Dneg3vSeDMuwGl1J3CDFHgGTAz8g8cB70HpCXOkUVCgILavR0t8K26T+jfNqNq6cPGpHlSITl1kBq+ptJ7G6r99fN/JT8bwB/AtOT3yvf9GGjAAAAAElFTkSuQmCC" alt="image">'],

    [/twitch\.tv/i, 'Twitch', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABF0lEQVRYhe2VOxKCMBRF73PcmA1uwxWoWYG4AgdsbBntbRUbx46OJbgEa4vEwnH4hXwIkMZbAclwTl7Cg6DIPhAbDoSqOV2zSokAYOIDXs7UFL689gOM59X7RgXGWrlUYGx4RcAHvCLQFR7Pm/uqet4q4Ct/gb+AdwFpK7ZJW4s2bd3eKzCqwOPgUSA7Afn5e82B7agC2QnIjgWcpRT+xpwPoRVcIGK3Ag4MXIHayhN2o3V9jnEF6n+28mdWBsnCgYSltJCNOVdABweQ443GynsRMIHzN2bsTq/eBQzgTx3cTUAHn+jhTgJa+IWeJpPJ9K1RIIQrTBaXCuSucKB7J9Se7sEEVE2lS6y2gAtEfcKtsgtEOMR7P4Aje3zreLJtAAAAAElFTkSuQmCC" alt="image">'],

    [/github\.com/i, 'GitHub', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAb1BMVEX///8kKS74+PgsMTY+Q0f8/Pzw8PGRk5ZNUVXr6+xDSEzCxMUpLjLV1tYuMzc/Q0i3ubuUl5lbX2NTV1uLjpFKTlKxs7Xh4uOFh4rn5+h3en1vcnZ9gIPc3d6/wcJzd3o3PECmqKpjZ2rOz9CfoaRP5W5KAAABU0lEQVQ4jW1T7aKCIAzdEEUTSUuzLMtuvf8zXtiQVDx/lJ3DvgEIEPU9l1rL/F4LiFEMCQYkj2JDC6VxhaRJl3x2xgin7MePMuYRD7cQftrjrcL7ELk9nNt8yXXtyRm5mquzXADKA6LpzgZRlgBPZ1WO7yn/yrmqyGdGn95Zjav2hbNghZHMV3uNG3DZCigEmhRq+pmyrUAcifj4CO+49xciWqAe6p3pCJrNH1ATp5gH6KghQLrDnoB8GxaYPYFkhpONipjrl+wI61hQ8tB5EvYb4eTL5Ebhd8u/2f4EYfhvvWJpw9ZJ0LQfb5uxbMKi3pp5xVp30miexZHbRriHvUnojrKt7isXqWRBFQQNF2w3aYCPUl8/EREWzxtuE+pVI/wbkSGp0a51Nyg1F+L5/nel56xXgt+zcMgeC0FK27gdTzVoPXtI9DBuO2tRhJj94m3/A1GiDZXoM3d5AAAAAElFTkSuQmCC" alt="image">'],

    [/onlyfans\.com/i, 'OnlyFans', CONFIG.colors.other, '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADDklEQVRYhe3WzWtcVRgG8N+ZmWo+iKSECFLdKhSrRvoHuBDEr01xU1cFxY21FKlCN4rdlGqhKoKgiCC0uqoKpUV0K26EIrQNFqFIDc1HbWdiJjPJ3Nzr4tzpjMmdycSNLvLAgXPufd/zvN8ctrGN/xih55+zt9u7Cu7BaH6uYxEJ2Lezo3PyYnt3N0YwnOsX8SSY2/ijQ7wLT+NJPITx/HsVv+IHXMCM6zO0WhXsxfOYwn250TtQQtbFUsbPOFApIB/GfhzG7lx4PR7DC7iCU0ZHzqnWDuJVTBTIF+EL1DoRiOTjeAevYGjAi+rmF6Yt1feIoR8E18RIXa50kQ/n5AfFkA2GRmPUcmPvwPI08C4uQ6Ur5y+Kng9OnmXUFllL+5VzN+o4gc/BkSntGrgfhwwe9ohGM67NyTNcxXF8iVVHpuCOAU/h4T4XrOHPfD+hXZj1OmnWy4AUSznx1ziN36FNTgx3RWy1XqG/iaN4Il9HcVMr2cz7GTHUx/Gj2KLPoNQ1L1TEyn+wxyWZWDDvdfzKppVLNJsnJEm/4O8Sa+rl3LkhHMP5bqESxnSGzHos3FHYtzOudI0kOW+pvtCHvH33sDiMhjGLb/GPFFQKFPtjdh4ZrWQrWik+wy9FVv4ljtciTIp5i7Pimyqv7SaUnpVlkwOSZ/gKp5B2e0+MQFWs1McLlAPezPfnpBlnq8+plN+wMlDnL4ien8StIoGQD6KX8InendBpwySZcGOurNUqkkvFVruGn8ScX0Sy3vM22jXwHS7hkR4GlHGvEFhZJemZ/ytC2C/LfkMTehGvN+APfIiP9JuGWUaj0Wv4rAjhA63kkpEhDvWbax2Uuh4UZ8Q0pD2l19ZorhSRp0L4VKl02l07BiaPBtB+1TTwthiF5gbJgNVVBblfEcLHQnhLmja8/ujA5O1rO9jsQXLrNtVa+5RiWgjvC+EMlrdKvtGAjhGsf5Kl6bjZeZrNRVwVwveCC8bGrqsvc3jPlsmLDdhoSHyUJsmoG3NBktSVSzWtJDE5yYEH/hXxNrbxv8Hf1b79Q60C7fYAAAAASUVORK5CYII=" alt="image">'],
    ];

  const getOtherLinkInfo = (url) => {
    if (!url)
      return {
        label: i18n("unknownLink"),
        color: CONFIG.colors.other,
        icon: "",
      };
    const lower = url.toLowerCase();
    for (const [regex, label, color, icon] of OTHER_LINK_LABELS) {
      if (regex.test(lower)) return { label, color, icon };
    }
    return { label: i18n("unknownLink"), color: CONFIG.colors.other, icon: "" };
  };

  const extractTargetFloor = (url) => {
    if (!url || !url.includes("/topic/")) return null;
    const parts = url.split("/");
    const last = parts[parts.length - 1];
    return /^\d+$/.test(last) && parts.length >= 5 ? last : null;
  };

  const getCurrentFloor = () => {
    const m = location.pathname.match(/\/topic\/\d+(?:\/[^/]+)?\/(\d+)$/);
    return m ? parseInt(m[1], 10) || 1 : 1;
  };

  class LRUCache {
    constructor(maxSize) {
      this.maxSize = maxSize;
      this.map = new Map();
    }
    get(key) {
      return this.map.get(key);
    }
    set(key, value) {
      if (this.map.has(key)) this.map.delete(key);
      else if (this.map.size >= this.maxSize) {
        const first = this.map.keys().next().value;
        this.map.delete(first);
      }
      this.map.set(key, value);
    }
    has(key) {
      return this.map.has(key);
    }
    clear() {
      this.map.clear();
    }
  }

  const apiCache = new LRUCache(CONFIG.cacheMaxSize);
  const pendingRequests = new Map();
  let currentScanController = null;

  const abortAllPending = () => {
    for (const [key, req] of pendingRequests) {
      if (req && typeof req.abort === "function") req.abort();
    }
    pendingRequests.clear();
  };

  const fetchTopicData = (tid, page) => {
    return new Promise((resolve, reject) => {
      const cacheKey = `${tid}:${page}`;
      if (apiCache.has(cacheKey)) {
        resolve(apiCache.get(cacheKey));
        return;
      }

      const req = GM_xmlhttpRequest({
        method: "GET",
        url: `https://forum.ripper.store/api/topic/${tid}?page=${page}`,
        responseType: "json",
        timeout: CONFIG.requestTimeout,
        onload: (r) => {
          pendingRequests.delete(cacheKey);
          try {
            const d =
              typeof r.response === "string"
                ? JSON.parse(r.response)
                : r.response;
            apiCache.set(cacheKey, d);
            resolve(d);
          } catch (e) {
            reject(new Error("分页解析失败: " + e.message));
          }
        },
        onerror: () => {
          pendingRequests.delete(cacheKey);
          reject(new Error("网络错误"));
        },
        ontimeout: () => {
          pendingRequests.delete(cacheKey);
          reject(new Error("超时"));
        },
      });

      pendingRequests.set(cacheKey, req);
    });
  };

  const getAllTopicData = async (
    tid,
    limit = 0,
    centerFloor = 1,
    signal,
    onProgress,
  ) => {
    const allPosts = [];
    let startFloor, endFloor;

    if (limit > 0) {
      startFloor = Math.max(1, centerFloor);
      endFloor = startFloor + limit - 1;
    } else {
      startFloor = 1;
      endFloor = Infinity;
    }

    const firstData = await fetchTopicData(tid, 1);
    if (signal && signal.aborted) throw new Error("扫描已取消");

    const posts = firstData.posts || [];
    const totalPosts =
      firstData.topic && firstData.topic.postcount
        ? firstData.topic.postcount
        : 0;
    const pagination = firstData.pagination || {};
    const pageCount = pagination.pageCount || 1;

    const filtered = posts.filter((p) => {
      const idx = (p.index || 0) + 1;
      return idx >= startFloor && idx <= endFloor;
    });
    allPosts.push(...filtered);

    if (onProgress) onProgress({ current: 1, total: pageCount });

    const lastPostIndex =
      posts.length > 0 ? (posts[posts.length - 1].index || 0) + 1 : 0;
    if (!(limit > 0 && lastPostIndex >= endFloor) && pageCount > 1) {
      const CONCURRENT_PAGES = CONFIG.concurrentPages;
      for (let p = 2; p <= pageCount; p += CONCURRENT_PAGES) {
        if (signal && signal.aborted) throw new Error("扫描已取消");

        if (limit > 0 && allPosts.length > 0) {
          const lastCollectedIdx =
            (allPosts[allPosts.length - 1].index || 0) + 1;
          if (lastCollectedIdx >= endFloor) break;
        }

        const batch = [];
        for (let i = 0; i < CONCURRENT_PAGES && p + i <= pageCount; i++) {
          batch.push(fetchTopicData(tid, p + i));
        }

        const results = await Promise.all(batch);

        for (const d of results) {
          if (signal && signal.aborted) throw new Error("扫描已取消");
          const psts = d.posts || [];
          allPosts.push(
            ...psts.filter((p) => {
              const idx = (p.index || 0) + 1;
              return idx >= startFloor && idx <= endFloor;
            }),
          );
        }

        if (onProgress)
          onProgress({
            current: Math.min(p + CONCURRENT_PAGES - 1, pageCount),
            total: pageCount,
          });

        if (limit > 0 && allPosts.length > 0) {
          const lastCollectedIdx =
            (allPosts[allPosts.length - 1].index || 0) + 1;
          if (lastCollectedIdx >= endFloor) break;
        }
      }
    }

    allPosts.sort((a, b) => (a.index || 0) - (b.index || 0));
    const actualEndFloor =
      totalPosts > 0 ? Math.min(endFloor, totalPosts) : endFloor;

    return {
      posts: allPosts,
      topic: firstData.topic || {},
      startFloor,
      endFloor: actualEndFloor,
    };
  };

  const resolvePostLink = (url) => {
    return new Promise((resolve) => {
      const full = url.startsWith("/")
        ? `https://forum.ripper.store${url}`
        : url;
      GM_xmlhttpRequest({
        method: "HEAD",
        url: full,
        redirect: "follow",
        timeout: CONFIG.resolveTimeout,
        onload: (r) => resolve(r.finalUrl || full),
        onerror: () => resolve(full),
        ontimeout: () => resolve(full),
      });
    });
  };

  const resolveAllLinks = async (items, signal) => {
    const results = [];
    for (let i = 0; i < items.length; i += CONFIG.resolveConcurrency) {
      if (signal && signal.aborted) throw new Error("扫描已取消");
      const batch = items.slice(i, i + CONFIG.resolveConcurrency);
      const batchResults = await Promise.all(
        batch.map(async (item) => {
          try {
            const finalUrl = await resolvePostLink(item.originalLink);
            return { ...item, finalUrl };
          } catch {
            return item;
          }
        }),
      );
      results.push(...batchResults);
      if (i + CONFIG.resolveConcurrency < items.length) {
        await new Promise((r) => setTimeout(r, CONFIG.resolveDelay));
      }
    }
    return results;
  };

  const scrollToPost = (pid, targetFloor) => {
    const anchor = document.getElementById(String(targetFloor));
    if (anchor) {
      anchor.scrollIntoView({ behavior: "smooth", block: "center" });
      const postEl = anchor.closest('[component="post"]');
      if (postEl) highlightElement(postEl);
      return;
    }

    const el =
      document.querySelector(`[component="post"][data-pid="${pid}"]`) ||
      document.getElementById(`pid_${pid}`) ||
      document.getElementById(`content-${pid}`) ||
      document.querySelector(`[data-pid="${pid}"]`);
    if (el) {
      el.scrollIntoView({ behavior: "smooth", block: "center" });
      highlightElement(el);
      return;
    }

    const m = location.pathname.match(/\/topic\/(\d+)\/([^/]+)/);
    if (m) {
      window.location.href = `https://forum.ripper.store/topic/${m[1]}/${encodeURIComponent(m[2])}/${targetFloor}`;
    } else {
      const tid = (location.pathname.match(/\/topic\/(\d+)/) || [])[1];
      if (tid) {
        window.location.href = `https://forum.ripper.store/topic/${tid}/${targetFloor}`;
      }
    }
  };

  const highlightElement = (el) => {
    el.style.transition = "background 0.3s";
    el.style.background = "rgba(114,240,168,0.15)";
    setTimeout(() => {
      el.style.background = "";
    }, CONFIG.highlightDuration);
  };

  const HIGHLIGHT_CLASS = "ah-dl-floor-highlight";

  const clearFloorHighlights = () => {
    document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach((el) => {
      el.classList.remove(HIGHLIGHT_CLASS);
    });
  };

  const clearFloorBorders = () => {
    document.querySelectorAll(".ah-floor-card").forEach((el) => {
      el.classList.remove("ah-floor-card");
    });
  };

  const persistHighlightFloor = (pid, postIndex) => {
    if (!getHighlightEnabled()) return;
    const el =
      document.querySelector(`[component="post"][data-pid="${pid}"]`) ||
      document.getElementById(String(postIndex)) ||
      document.getElementById(`pid_${pid}`) ||
      document.getElementById(`content-${pid}`) ||
      document.querySelector(`[data-pid="${pid}"]`);
    if (el) {
      el.classList.add(HIGHLIGHT_CLASS);
    }
  };

  const scanPostForDL = (postEl) => {
    if (!getHighlightEnabled()) return false;
    const content = postEl.querySelector('[component="post/content"]');
    if (!content) return false;
    const raw = (content.textContent || "").trim();
    const html = (content.innerHTML || "").trim();
    if (!raw && !html) return false;
    if (hasDLPattern(raw)) return true;
    if (hasDLPattern(stripHTML(html))) return true;
    const temp = document.createElement("div");
    temp.innerHTML = html;
    for (const a of temp.querySelectorAll("a[href]")) {
      if (hasDLPattern(a.getAttribute("href") || "")) return true;
    }
    return false;
  };

  const applyHighlightsToAllPosts = () => {
    if (!getHighlightEnabled()) return;
    document
      .querySelectorAll(
        '[component="post"]:not([component="post/replies/container"] [component="post"])',
      )
      .forEach((postEl) => {
        if (scanPostForDL(postEl)) {
          postEl.classList.add(HIGHLIGHT_CLASS);
        }
      });
  };

  const observeNewPostsForHighlight = () => {
    if (window.__ahHighlightObserver) return;
    const applyToPost = (node) => {
      if (node.nodeType !== 1) return;
      if (
        node.matches?.('[component="post"]') &&
        !node.closest('[component="post/replies/container"]')
      ) {
        if (scanPostForDL(node)) node.classList.add(HIGHLIGHT_CLASS);
      }
      node
        .querySelectorAll?.(
          '[component="post"]:not([component="post/replies/container"] [component="post"])',
        )
        ?.forEach((postEl) => {
          if (scanPostForDL(postEl)) postEl.classList.add(HIGHLIGHT_CLASS);
        });
    };
    window.__ahHighlightObserver = new MutationObserver((mutations) => {
      for (const m of mutations) {
        for (const node of m.addedNodes) applyToPost(node);
      }
    });
    const container =
      document.querySelector('[component="topic"]') ||
      document.querySelector("#content") ||
      document.body;
    window.__ahHighlightObserver.observe(container, {
      childList: true,
      subtree: true,
    });
  };

  const injectStyles = () => {
    if (document.getElementById("ah-dl-styles")) return;
    const style = document.createElement("style");
    style.id = "ah-dl-styles";
    style.textContent = `
      #ah-dl-finder { position: fixed; bottom: 20px; right: 20px; width: 480px; max-width: calc(100vw - 40px); max-height: 650px; background: var(--ah-bg); border: 1px solid var(--ah-border); border-radius: 8px; color: var(--ah-text); font-family: system-ui, -apple-system, sans-serif; font-size: 12px; z-index: 999999; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,.8); display: flex; flex-direction: column; opacity: var(--ah-panel-opacity); }
      #ah-dl-finder .ah-titlebar { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--ah-border); }
      #ah-dl-finder .ah-title { font-size: 16px; font-weight: bold; color: var(--ah-primary); }
      #ah-dl-finder .ah-btngroup { display: flex; gap: 6px; }
      #ah-dl-finder .ah-btn { background: var(--ah-bg-elevated); border: 1px solid #444; color: var(--ah-text); font-size: 14px; padding: 2px 8px; border-radius: 4px; cursor: pointer; transition: all 0.15s; }
      #ah-dl-finder .ah-btn:hover { background: #444; color: #fff; }
      #ah-dl-finder .ah-content { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
      #ah-dl-finder .ah-tabs { display: flex; border-bottom: 1px solid var(--ah-border); padding: 0 12px; }
      #ah-dl-finder .ah-tab { background: none; border: none; border-bottom: 2px solid transparent; color: var(--ah-text-muted); font-size: 11px; font-weight: bold; padding: 8px 12px; cursor: pointer; transition: all 0.15s; }
      #ah-dl-finder .ah-tab:hover { color: #ccc; }
      #ah-dl-finder .ah-tab.active { color: var(--ah-primary); border-bottom-color: var(--ah-primary); }
      #ah-dl-finder .ah-tabarea { padding: 12px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; min-height: 0; }
      #ah-dl-finder .ah-list { display: flex; flex-direction: column; gap: 8px; }
      #ah-dl-finder .ah-entry { background: var(--ah-bg-card); border: 1px solid var(--ah-border); border-radius: 6px; padding: 10px 12px; display: flex; justify-content: space-between; align-items: stretch; }
      #ah-dl-finder .ah-entry-left { display: flex; flex-direction: column; gap: 4px; }
      #ah-dl-finder .ah-user { color: #aaa; font-size: 12px; }
      #ah-dl-finder .ah-floor { color: var(--ah-text-muted); font-size: 11px; }
      #ah-dl-finder .ah-entry-right { display: flex; flex-direction: column; gap: 8px; align-items: flex-end; }
      #ah-dl-finder .ah-tag img { width: 16px; height: 16px; display: block; flex-shrink: 0; }
      #ah-dl-finder .ah-tag { display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 4px 12px; border-radius: 4px; border: 1px solid; font-size: 12px; font-weight: bold; text-decoration: none; white-space: nowrap; transition: all 0.15s; gap: 4px; }
      #ah-dl-finder .ah-tag:hover { filter: brightness(1.2); }
      #ah-dl-finder .ah-btnrow { display: flex; gap: 8px; }
      #ah-dl-finder .ah-nodata { color: var(--ah-text-muted); text-align: center; padding: 20px; }
      #ah-confirm-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.7); z-index: 10000000; display: flex; align-items: center; justify-content: center; font-family: system-ui, -apple-system, sans-serif; }
      #ah-confirm-modal .ah-modal-box { background: var(--ah-bg-elevated); border: 1px solid var(--ah-border-light); border-radius: 8px; padding: 20px; max-width: 400px; text-align: center; }
      #ah-confirm-modal .ah-modal-text { color: #ffaa33; font-size: 14px; margin-bottom: 12px; }
      #ah-confirm-modal .ah-modal-btns { display: flex; gap: 8px; justify-content: center; }
      #ah-confirm-modal .ah-btn-yes { background: #ff424d; border: none; color: white; padding: 6px 20px; border-radius: 4px; cursor: pointer; }
      #ah-confirm-modal .ah-btn-no { background: var(--ah-bg-elevated); border: 1px solid #444; color: #ccc; padding: 6px 20px; border-radius: 4px; cursor: pointer; }
      #ah-settings-panel { background: var(--ah-bg-elevated); border: 1px solid var(--ah-border-light); border-radius: 8px; padding: 10px 12px; z-index: 2147483647; box-shadow: 0 8px 28px rgba(0,0,0,.8); overflow-y: auto; font-family: system-ui, -apple-system, sans-serif; font-size: 12px; }
      #ah-settings-panel .ah-lang-row { margin-bottom: 6px; padding-bottom: 5px; border-bottom: 1px solid var(--ah-border-light); }
      #ah-settings-panel .ah-lang-label { color: var(--ah-text-muted); font-size: 11px; margin-right: 4px; }
      #ah-settings-panel .ah-lang-btn, #ah-thumb-settings-panel .ah-lang-btn { background: rgba(255,255,255,0.03); border: 1px solid var(--ah-border-light); color: #555; padding: 2px 8px; margin-right: 3px; border-radius: 3px; cursor: pointer; font-size: 11px; transition: all 0.15s; }
      #ah-settings-panel .ah-lang-btn.active, #ah-thumb-settings-panel .ah-lang-btn.active { background: var(--ah-primary); color: #0a0a0c; border-color: var(--ah-primary); font-weight: 700; box-shadow: 0 0 12px color-mix(in srgb, var(--ah-primary) 60%, transparent), inset 0 1px 0 rgba(255,255,255,0.3); transform: scale(1.05); }
      #ah-settings-panel .ah-floor-opt { display: block; width: 100%; background: none; border: 1px solid var(--ah-border-light); color: #ccc; padding: 4px 8px; margin-bottom: 3px; border-radius: 4px; cursor: pointer; font-size: 11px; text-align: left; white-space: nowrap; }
      #ah-settings-panel .ah-floor-opt.active { color: var(--ah-primary); border-color: var(--ah-primary); }
      #ah-settings-panel .ah-floor-opt:hover { background: var(--ah-border-light); }
            #ah-thumb-settings-panel { background: var(--ah-bg-elevated); border: 1px solid var(--ah-border-light); border-radius: 8px; padding: 10px 12px; z-index: 2147483647; box-shadow: 0 8px 28px rgba(0,0,0,.8); overflow-y: auto; font-family: system-ui, -apple-system, sans-serif; font-size: 12px; }
      #ah-thumb-settings-panel .ah-lang-btn { background: rgba(255,255,255,0.03); border: 1px solid var(--ah-border-light); color: #666; padding: 2px 6px; margin-right: 3px; border-radius: 3px; cursor: pointer; font-size: 11px; transition: all 0.15s; }
      #ah-thumb-settings-panel .ah-lang-label { color: var(--ah-text-muted); font-size: 11px; margin-right: 4px; }
      #ah-thumb-settings-panel .ah-lang-btn.active { background: var(--ah-primary); color: #0a0a0c; border-color: var(--ah-primary); font-weight: bold; box-shadow: 0 0 8px color-mix(in srgb, var(--ah-primary) 40%, transparent); }
#ah-error-toast { position: fixed; top: 20px; right: 20px; background: var(--ah-bg-elevated); border: 1px solid #ff424d; color: #ff424d; padding: 12px 16px; border-radius: 6px; z-index: 10000001; font-family: system-ui, sans-serif; font-size: 13px; box-shadow: 0 4px 12px rgba(0,0,0,.6); animation: ahSlideIn 0.3s ease; }
      @keyframes ahSlideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

      :root {
        --ah-thumb-max-width: min(96%, calc(11rem * var(--ah-thumb-scale, 2.4)));
        --ah-thumb-max-height: min(52vh, calc(6.875rem * var(--ah-thumb-scale, 2.4)));
      }
      :root[data-ah-home-page="true"] .top_topics .topic-thumb,
      :root[data-ah-home-page="true"] .top_topics .topic-thumbs,
      .top_topics .topic-thumb,
      .top_topics .topic-thumbs {
        width: auto !important; max-width: none !important; height: auto !important; max-height: none !important; aspect-ratio: auto !important;
      }
      .top_topics [component="category/topic"],
      :root[data-ah-home-page="true"] [component="category/topic"] {
        padding: revert !important; margin-bottom: revert !important; border: revert !important; border-radius: revert !important; background: revert !important; box-shadow: none !important; transform: none !important;
      }
      .top_topics [component="category/topic"]:hover,
      :root[data-ah-home-page="true"] [component="category/topic"]:hover {
        transform: none !important; box-shadow: none !important;
      }
      :root.ah-large-thumbs [component="category/topic"] {
        position: relative; margin-bottom: 1.15rem !important; padding: 1.15rem !important; border: 1px solid rgba(255,255,255,0.06) !important; border-radius: 1rem !important; background: rgba(255,255,255,0.02) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.12); overflow: hidden;
      }
      :root.ah-large-thumbs [component="category/topic"]:hover {
        transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0,0,0,0.18) !important; border-color: rgba(192,132,252,0.25) !important;
      }
      :root.ah-large-thumbs [component="category/topic"].ah-layout-ready:not(.ah-has-thumb) {
        display: flex !important; flex-direction: column !important; align-items: stretch !important; gap: 0.55rem !important;
      }
      :root.ah-large-thumbs [component="category/topic"].ah-layout-ready:not(.ah-has-thumb) .ah-topic-main { width: 100%; min-width: 0; }
      :root.ah-large-thumbs [component="category/topic"].ah-layout-ready.ah-has-thumb {
        display: grid !important; grid-template-columns: minmax(9.5rem, var(--ah-thumb-max-width)) minmax(0, 1fr); grid-template-areas: "thumb main" "stats stats"; column-gap: 1rem !important; row-gap: 0.7rem !important; align-items: start !important;
      }
      :root.ah-large-thumbs [component="category/topic"].ah-has-thumb .ah-topic-thumb-slot { grid-area: thumb; width: 100% !important; max-width: 100% !important; margin: 0 !important; align-self: start; }
      :root.ah-large-thumbs [component="category/topic"].ah-has-thumb .ah-topic-main { grid-area: main; width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 0.45rem; }
      :root.ah-large-thumbs [component="category/topic"].ah-has-thumb .ah-topic-stats-slot { grid-area: stats; }
      :root.ah-large-thumbs [component="category/topic"] .ah-topic-main .d-flex.flex-lg-row,
      :root.ah-large-thumbs [component="category/topic"] .ah-topic-main > .d-flex {
        flex-direction: column !important; align-items: stretch !important; width: 100% !important; max-width: 100% !important;
      }
      :root.ah-large-thumbs [component="category/topic"] .ah-topic-stats-slot {
        width: 100% !important; flex-direction: row !important; justify-content: flex-start !important; gap: 0.65rem !important; margin-top: 0 !important; padding-top: 0.65rem !important; border-top: 1px solid rgba(255,255,255,0.05);
      }
      :root.ah-large-thumbs [component="category/topic"] .ah-topic-main *,
      :root.ah-large-thumbs [component="category/topic"] .topic-title,
      :root.ah-large-thumbs [component="category/topic"] a.text-body { max-width: 100%; min-width: 0; overflow-wrap: anywhere; }
      :root.ah-large-thumbs [component="category/topic"] .topic-title,
      :root.ah-large-thumbs [component="category/topic"] a.text-body { font-size: 1.04rem; font-weight: 700; line-height: 1.4; }
      :root.ah-large-thumbs [component="category/topic"] .stats-postcount,
      :root.ah-large-thumbs [component="category/topic"] .stats-viewcount { border-radius: 0.85rem !important; border: 1px solid rgba(255,255,255,0.06) !important; background: rgba(255,255,255,0.03) !important; min-width: 4.5rem; }
      :root.ah-large-thumbs [component="category/topic"] .topic-thumbs,
      :root.ah-large-thumbs .topic-thumbs {
        display: inline-flex !important; align-items: center; justify-content: center; overflow: hidden; border-radius: 1rem; border: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.03); padding: 0.35rem; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 8px 20px rgba(0,0,0,0.15); flex-shrink: 0;
      }
      :root.ah-large-thumbs [component="category/topic"] .topic-thumbs.d-none,
      :root.ah-large-thumbs .topic-thumbs.d-none { display: inline-flex !important; }
      :root.ah-large-thumbs [component="category/topic"] .topic-thumb {
        display: block; width: 100% !important; max-width: 100% !important; height: auto !important; max-height: var(--ah-thumb-max-height) !important; aspect-ratio: 16 / 10; object-fit: cover !important; object-position: center; border-radius: 0.85rem; cursor: zoom-in;
      }
      :root.ah-large-thumbs [component="category/topic"].ah-has-thumb .topic-thumb { max-height: min(36vh, var(--ah-thumb-max-height)) !important; }
      :root.ah-hover-preview .topic-thumb:hover { outline: 2px solid color-mix(in srgb, #c084fc 45%, transparent); outline-offset: 2px; }
      .ah-thumb-preview {
        position: fixed; z-index: 2100; padding: 0.45rem; border-radius: 1rem; border: 1px solid rgba(255,255,255,0.08); background: rgba(18,18,24,0.92); box-shadow: 0 28px 60px rgba(0,0,0,0.45); pointer-events: auto;
      }
      .ah-thumb-preview img { display: block; width: 100%; max-height: min(52vh, 420px); object-fit: contain; border-radius: 0.8rem; background: #0f1420; }
      .ah-thumb-preview-label { display: block; margin-top: 0.35rem; font-size: 0.72rem; font-weight: 700; color: #888; text-align: center; }
      @media (max-width: 991px) {
        :root.ah-large-thumbs [component="category/topic"].ah-layout-ready.ah-has-thumb { grid-template-columns: 1fr !important; grid-template-areas: "thumb" "main" "stats" !important; }
        :root.ah-large-thumbs [component="category/topic"] .topic-thumb,
        :root.ah-large-thumbs .topic-thumb { max-height: min(42vh, var(--ah-thumb-max-height)) !important; }
      }
      @media (prefers-color-scheme: dark) {
        .ah-thumb-preview { background: rgba(18,18,24,0.92); }
      }
    `;
    document.head.appendChild(style);
  };

  const createTag = (href, text, color, icon = "") => {
    const a = document.createElement("a");
    a.href = href;
    a.target = "_blank";
    a.className = "ah-tag";
    a.style.cssText = `border-color: ${color}40; background: ${color}15; color: ${color};`;
    a.title = href;
    if (icon) {
      const iconSpan = document.createElement("span");
      iconSpan.innerHTML = icon;
      a.appendChild(iconSpan);
    }
    const textSpan = document.createElement("span");
    textSpan.textContent = text;
    a.appendChild(textSpan);
    return a;
  };

  const createBtn = (text, onClick) => {
    const btn = document.createElement("button");
    btn.textContent = text;
    btn.className = "ah-btn";
    btn.addEventListener("click", (e) => {
      e.preventDefault();
      onClick();
    });
    return btn;
  };

  const buildEntry = (username, postIndex, tagElement, buttonElements) => {
    const entry = document.createElement("div");
    entry.className = "ah-entry";
    entry.setAttribute("data-post-index", postIndex);

    const leftPart = document.createElement("div");
    leftPart.className = "ah-entry-left";

    const usernameSpan = document.createElement("div");
    usernameSpan.className = "ah-user";
    usernameSpan.textContent = `👤 ${username}`;

    const floorSpan = document.createElement("div");
    floorSpan.className = "ah-floor";
    floorSpan.textContent = `${i18n("floorLabel")} #${postIndex}`;

    leftPart.appendChild(usernameSpan);
    leftPart.appendChild(floorSpan);

    const rightPart = document.createElement("div");
    rightPart.className = "ah-entry-right";

    const tagRow = document.createElement("div");
    tagRow.appendChild(tagElement);
    rightPart.appendChild(tagRow);

    const buttonRow = document.createElement("div");
    buttonRow.className = "ah-btnrow";
    buttonElements.forEach((el) => buttonRow.appendChild(el));
    rightPart.appendChild(buttonRow);

    entry.appendChild(leftPart);
    entry.appendChild(rightPart);
    return entry;
  };

  const showConfirmModal = (message, onConfirm) => {
    const existing = document.getElementById("ah-confirm-modal");
    if (existing) existing.remove();

    const modal = document.createElement("div");
    modal.id = "ah-confirm-modal";

    const box = document.createElement("div");
    box.className = "ah-modal-box";

    const text = document.createElement("div");
    text.className = "ah-modal-text";
    text.textContent = message;

    const btns = document.createElement("div");
    btns.className = "ah-modal-btns";

    const yesBtn = document.createElement("button");
    yesBtn.className = "ah-btn-yes";
    yesBtn.textContent = i18n("confirmYes");
    yesBtn.addEventListener("click", () => {
      modal.remove();
      onConfirm();
    });

    const noBtn = document.createElement("button");
    noBtn.className = "ah-btn-no";
    noBtn.textContent = i18n("confirmNo");
    noBtn.addEventListener("click", () => modal.remove());

    btns.appendChild(yesBtn);
    btns.appendChild(noBtn);
    box.appendChild(text);
    box.appendChild(btns);
    modal.appendChild(box);
    document.body.appendChild(modal);

    modal.addEventListener("click", (e) => {
      if (e.target === modal) modal.remove();
    });
  };

  const showErrorToast = (message) => {
    const existing = document.getElementById("ah-error-toast");
    if (existing) existing.remove();
    const toast = document.createElement("div");
    toast.id = "ah-error-toast";
    toast.textContent = message;
    document.body.appendChild(toast);
    setTimeout(() => {
      if (toast.parentNode) toast.remove();
    }, 5000);
  };

  let globalSettingsPanel = null;
  let settingsClickHandler = null;

  function closeGlobalSettings() {
    if (globalSettingsPanel) {
      globalSettingsPanel.remove();
      globalSettingsPanel = null;
    }
    if (settingsClickHandler) {
      document.removeEventListener("click", settingsClickHandler);
      settingsClickHandler = null;
    }
  }

  function createColorRow(labelKey, colorKey, onChange) {
    const row = document.createElement("div");
    row.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:6px;";

    const label = document.createElement("span");
    label.className = "ah-lang-label";
    label.style.minWidth = "60px";
    label.textContent = i18n(labelKey);

    const picker = document.createElement("input");
    picker.type = "color";
    picker.value = getThemeColor(colorKey);
    picker.style.cssText =
      "width:28px;height:20px;padding:0;border:none;background:none;cursor:pointer;";

    const text = document.createElement("input");
    text.type = "text";
    text.value = getThemeColor(colorKey);
    text.style.cssText =
      "width:70px;background:#222;border:1px solid #333;color:#ccc;padding:2px 4px;border-radius:3px;font-size:11px;";

    picker.addEventListener("input", (e) => {
      text.value = e.target.value;
      if (onChange) onChange(e.target.value);
    });
    text.addEventListener("input", (e) => {
      let val = e.target.value.trim();
      if (!val.startsWith("#")) val = "#" + val;
      if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
        picker.value = val;
        if (onChange) onChange(val);
      }
    });

    row.appendChild(label);
    row.appendChild(picker);
    row.appendChild(text);
    return { row, picker, text };
  }

  let globalThumbPanel = null;
  let thumbSettingsClickHandler = null;
  let thumbSettingsBtn = null;

  function closeThumbSettings() {
    if (globalThumbPanel) {
      globalThumbPanel.remove();
      globalThumbPanel = null;
    }
    if (thumbSettingsClickHandler) {
      document.removeEventListener("click", thumbSettingsClickHandler);
      thumbSettingsClickHandler = null;
    }
  }

  function openThumbSettings(triggerBtn) {
    closeThumbSettings();

    const panel = document.createElement("div");
    panel.id = "ah-thumb-settings-panel";

    const btnRect = triggerBtn.getBoundingClientRect();
    const panelWidth = 260;
    const panelHeight = 320;

    let left = btnRect.left + btnRect.width / 2 - panelWidth / 2;
    if (left < 10) left = 10;
    if (left + panelWidth > window.innerWidth - 10)
      left = window.innerWidth - panelWidth - 10;

    const bottom = window.innerHeight - btnRect.top + 6;
    panel.style.cssText = `position:fixed;bottom:${bottom}px;left:${left}px;width:${panelWidth}px;max-height:${panelHeight}px;background:var(--ah-bg-elevated);border:1px solid var(--ah-border-light);border-radius:8px;padding:10px 12px;z-index:2147483647;box-shadow:0 8px 28px rgba(0,0,0,.8);overflow-y:auto;font-family:system-ui, -apple-system, sans-serif;font-size:12px;`;

    const titleDiv = document.createElement("div");
    titleDiv.style.cssText =
      "color:#aaa;font-size:11px;margin-bottom:8px;font-weight:bold;border-bottom:1px solid var(--ah-border-light);padding-bottom:6px;";
    titleDiv.textContent = i18n("thumbTitle");
    panel.appendChild(titleDiv);

    const largeThumbsRow = document.createElement("div");
    largeThumbsRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const largeThumbsLabel = document.createElement("span");
    largeThumbsLabel.className = "ah-lang-label";
    largeThumbsLabel.textContent = i18n("largeThumbs") + ":";
    largeThumbsRow.appendChild(largeThumbsLabel);
    const currentLargeThumbs = getEnableLargeThumbs();
    [true, false].forEach((val) => {
      const btn = document.createElement("button");
      btn.textContent = i18n(
        val ? "defaultCollapsedOn" : "defaultCollapsedOff",
      );
      btn.className =
        "ah-lang-btn" + (currentLargeThumbs === val ? " active" : "");
      btn.addEventListener("click", () => {
        setEnableLargeThumbs(val);
        applyThumbSettings();
        restructureTopicCards();
        closeThumbSettings();
      });
      largeThumbsRow.appendChild(btn);
    });
    panel.appendChild(largeThumbsRow);

    const hoverPreviewRow = document.createElement("div");
    hoverPreviewRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const hoverPreviewLabel = document.createElement("span");
    hoverPreviewLabel.className = "ah-lang-label";
    hoverPreviewLabel.textContent = i18n("hoverPreview") + ":";
    hoverPreviewRow.appendChild(hoverPreviewLabel);
    const currentHoverPreview = getEnableHoverPreview();
    [true, false].forEach((val) => {
      const btn = document.createElement("button");
      btn.textContent = i18n(
        val ? "defaultCollapsedOn" : "defaultCollapsedOff",
      );
      btn.className =
        "ah-lang-btn" + (currentHoverPreview === val ? " active" : "");
      btn.addEventListener("click", () => {
        setEnableHoverPreview(val);
        applyThumbSettings();
        closeThumbSettings();
      });
      hoverPreviewRow.appendChild(btn);
    });
    panel.appendChild(hoverPreviewRow);

    const scaleRow = document.createElement("div");
    scaleRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const scaleLabel = document.createElement("span");
    scaleLabel.className = "ah-lang-label";
    scaleLabel.textContent = i18n("thumbScale");
    scaleRow.appendChild(scaleLabel);
    const scaleRange = document.createElement("input");
    scaleRange.type = "range";
    scaleRange.min = "1";
    scaleRange.max = "8";
    scaleRange.step = "0.1";
    scaleRange.value = getThumbnailScale();
    scaleRange.style.cssText = "flex:1;cursor:pointer;";
    scaleRow.appendChild(scaleRange);
    const scaleValue = document.createElement("span");
    scaleValue.style.cssText =
      "color:#ccc;font-size:11px;min-width:36px;text-align:right;";
    scaleValue.textContent = getThumbnailScale().toFixed(1) + "x";
    scaleRow.appendChild(scaleValue);
    scaleRange.addEventListener("input", (e) => {
      scaleValue.textContent = parseFloat(e.target.value).toFixed(1) + "x";
    });
    panel.appendChild(scaleRow);

    const saveThumbBtn = document.createElement("button");
    saveThumbBtn.textContent = i18n("apply");
    saveThumbBtn.style.cssText =
      "margin-top:6px;width:100%;background:#333;border:1px solid #444;color:#ccc;padding:3px 0;border-radius:4px;cursor:pointer;font-size:11px;";
    saveThumbBtn.addEventListener("click", () => {
      setThumbnailScale(parseFloat(scaleRange.value));
      applyThumbSettings();
      restructureTopicCards();
      closeThumbSettings();
    });
    panel.appendChild(saveThumbBtn);

    document.body.appendChild(panel);
    globalThumbPanel = panel;

    thumbSettingsClickHandler = (e) => {
      if (
        globalThumbPanel &&
        !globalThumbPanel.contains(e.target) &&
        e.target !== triggerBtn &&
        !triggerBtn.contains(e.target)
      ) {
        closeThumbSettings();
      }
    };
    setTimeout(
      () => document.addEventListener("click", thumbSettingsClickHandler),
      0,
    );
  }

  function createThumbSettingsButton() {
    if (thumbSettingsBtn) return;
    if (detectPageType() === "search") return;

    const btn = document.createElement("button");
    btn.id = "ah-thumb-settings-btn";
    btn.textContent = "⚙︎";
    btn.title = i18n("thumbTitle");
    btn.style.cssText =
      "position:fixed;bottom:20px;right:20px;width:40px;height:40px;border-radius:50%;background:var(--ah-bg-elevated);border:1px solid var(--ah-border-light);color:var(--ah-primary);font-size:18px;cursor:pointer;z-index:999998;box-shadow:0 4px 12px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;transition:all 0.15s;";
    btn.addEventListener("mouseenter", () => {
      btn.style.background = "var(--ah-border-light)";
      btn.style.transform = "scale(1.05)";
    });
    btn.addEventListener("mouseleave", () => {
      btn.style.background = "var(--ah-bg-elevated)";
      btn.style.transform = "scale(1)";
    });
    btn.addEventListener("click", (e) => {
      e.stopPropagation();
      openThumbSettings(btn);
    });
    document.body.appendChild(btn);
    thumbSettingsBtn = btn;
  }

  function removeThumbSettingsButton() {
    if (thumbSettingsBtn) {
      thumbSettingsBtn.remove();
      thumbSettingsBtn = null;
    }
    closeThumbSettings();
  }

  function showWelcomeModal() {
    if (hasWelcomed()) return;

    const getCurrentTopicId = () => {
      const m = location.pathname.match(/\/topic\/(\d+)/);
      return m ? m[1] : null;
    };

    const modal = document.createElement("div");
    modal.id = "ah-welcome-modal";
    modal.style.cssText =
      "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.75);z-index:2147483647;display:flex;align-items:center;justify-content:center;font-family:system-ui,-apple-system,sans-serif;";

    const box = document.createElement("div");
    box.style.cssText =
      "background:var(--ah-bg-elevated);border:1px solid var(--ah-border-light);border-radius:10px;padding:28px 24px;max-width:420px;width:90%;text-align:center;box-shadow:0 16px 48px rgba(0,0,0,.6);";

    const title = document.createElement("div");
    title.style.cssText =
      "font-size:18px;font-weight:bold;color:var(--ah-primary);margin-bottom:12px;";
    title.textContent = "Thanks for installing Ripper.Store Assets Hunter!";

    const body = document.createElement("div");
    body.style.cssText =
      "font-size:13px;color:var(--ah-text);line-height:1.6;margin-bottom:20px;";
    body.innerHTML =
      "This script auto-scans download links, related topics, and external URLs in Ripper.Store threads.<br><br>Your support keeps it alive. Enjoy!";

    const hint = document.createElement("div");
    hint.style.cssText =
      "font-size:11px;color:var(--ah-text-muted);margin-bottom:18px;";
    hint.textContent =
      'Choose an action below. "Got it" simply closes this dialog.';

    const btnWrap = document.createElement("div");
    btnWrap.style.cssText =
      "display:flex;justify-content:center;gap:10px;flex-wrap:wrap;";

    const baseBtnStyle =
      "padding:8px 18px;border-radius:6px;cursor:pointer;font-size:13px;font-weight:bold;transition:filter .15s;border:none;";
    const primaryStyle =
      baseBtnStyle + "background:var(--ah-primary);color:#0a0a0c;";
    const secondaryStyle =
      baseBtnStyle +
      "background:var(--ah-bg-card);color:var(--ah-text);border:1px solid var(--ah-border-light);";

    const gotItBtn = document.createElement("button");
    gotItBtn.textContent = "Got it";
    gotItBtn.style.cssText = primaryStyle;
    gotItBtn.addEventListener(
      "mouseenter",
      () => (gotItBtn.style.filter = "brightness(1.15)"),
    );
    gotItBtn.addEventListener("mouseleave", () => (gotItBtn.style.filter = ""));
    gotItBtn.addEventListener("click", () => {
      setWelcomed();
      modal.remove();
    });

    const bumpBtn = document.createElement("button");
    bumpBtn.textContent = "Bump";
    bumpBtn.style.cssText = secondaryStyle;
    bumpBtn.addEventListener(
      "mouseenter",
      () => (bumpBtn.style.filter = "brightness(1.15)"),
    );
    bumpBtn.addEventListener("mouseleave", () => (bumpBtn.style.filter = ""));
    bumpBtn.addEventListener("click", () => {
      setWelcomed();
      modal.remove();
      doBump();
    });

    const bumpUpvoteBtn = document.createElement("button");
    bumpUpvoteBtn.textContent = "Bump + Upvote";
    bumpUpvoteBtn.style.cssText = secondaryStyle;
    bumpUpvoteBtn.addEventListener(
      "mouseenter",
      () => (bumpUpvoteBtn.style.filter = "brightness(1.15)"),
    );
    bumpUpvoteBtn.addEventListener(
      "mouseleave",
      () => (bumpUpvoteBtn.style.filter = ""),
    );
    bumpUpvoteBtn.addEventListener("click", () => {
      setWelcomed();
      modal.remove();
      doBump();
      doUpvote();
    });

    function doBump() {
      const tid = getCurrentTopicId();
      if (!tid) {
        showErrorToast("Cannot bump: not in a topic page");
        return;
      }
      const BUMP_TEXTS = [
        "Bump",
        "bump",
        "BUMP",
        "lol bump",
        "Bump! :3",
        "bumppppppppppppppppp",
      ];
      const BUMP_TEXT =
        BUMP_TEXTS[Math.floor(Math.random() * BUMP_TEXTS.length)];
      postReply(tid, BUMP_TEXT, (err) => {
        if (err) showErrorToast("Bump failed: " + err);
        else showErrorToast("Bumped successfully!");
      });
    }

    function doUpvote() {
      const tid = getCurrentTopicId();
      if (!tid) {
        showErrorToast("Cannot upvote: not in a topic page");
        return;
      }
      if (!hasAutoUpvoted(tid)) {
        upvoteTopic(tid, (err) => {
          if (!err) {
            setAutoUpvoted(tid);
            showErrorToast("Upvoted successfully!");
          } else {
            showErrorToast("Upvote failed: " + err);
          }
        });
      } else {
        showErrorToast("Already upvoted this topic");
      }
    }

    btnWrap.appendChild(bumpBtn);
    btnWrap.appendChild(bumpUpvoteBtn);
    btnWrap.appendChild(gotItBtn);

    box.appendChild(title);
    box.appendChild(body);
    box.appendChild(hint);
    box.appendChild(btnWrap);
    modal.appendChild(box);
    document.body.appendChild(modal);
  }
  function openGlobalSettings(triggerBtn, onApply) {
    closeGlobalSettings();

    const panel = document.createElement("div");
    panel.id = "ah-settings-panel";

    const btnRect = triggerBtn.getBoundingClientRect();
    const panelWidth = 260;
    const panelHeight = Math.min(420, window.innerHeight * 0.7);

    let left = btnRect.left + btnRect.width / 2 - panelWidth / 2;
    if (left < 10) left = 10;
    if (left + panelWidth > window.innerWidth - 10)
      left = window.innerWidth - panelWidth - 10;

    const bottom = window.innerHeight - btnRect.top + 6;
    panel.style.cssText = `position:fixed;bottom:${bottom}px;left:${left}px;width:${panelWidth}px;max-height:${panelHeight}px;`;

    const langDiv = document.createElement("div");
    langDiv.className = "ah-lang-row";
    const langLabel = document.createElement("span");
    langLabel.className = "ah-lang-label";
    langLabel.textContent = i18n("lang") + ":";
    langDiv.appendChild(langLabel);

    const langNames = {
      zh: "中文",
      en: "English",
      ja: "日本語",
      ko: "한국어",
      ru: "Русский",
      es: "Español",
      fr: "Français",
    };

    Object.keys(STRINGS).forEach((lang) => {
      const btn = document.createElement("button");
      btn.textContent = langNames[lang] || lang.toUpperCase();
      btn.className = "ah-lang-btn" + (currentLang === lang ? " active" : "");
      btn.addEventListener("click", () => {
        currentLang = lang;
        currentStrings = STRINGS[lang] || STRINGS.zh;
        GM_setValue("ah-lang", lang);

        closeGlobalSettings();
        if (onApply) onApply();
      });
      langDiv.appendChild(btn);
    });
    panel.appendChild(langDiv);

    const collapsedDiv = document.createElement("div");
    collapsedDiv.className = "ah-lang-row";
    const collapsedLabel = document.createElement("span");
    collapsedLabel.className = "ah-lang-label";
    collapsedLabel.textContent = i18n("defaultCollapsed") + ":";
    collapsedDiv.appendChild(collapsedLabel);

    const currentCollapsed = getDefaultCollapsed();
    [true, false].forEach((val) => {
      const btn = document.createElement("button");
      btn.textContent = i18n(
        val ? "defaultCollapsedOn" : "defaultCollapsedOff",
      );
      btn.className =
        "ah-lang-btn" + (currentCollapsed === val ? " active" : "");
      btn.addEventListener("click", () => {
        setDefaultCollapsed(val);
        closeGlobalSettings();
        if (onApply) onApply();
      });
      collapsedDiv.appendChild(btn);
    });
    panel.appendChild(collapsedDiv);

    const highlightDiv = document.createElement("div");
    highlightDiv.className = "ah-lang-row";

    const hlTitle = document.createElement("div");
    hlTitle.style.cssText =
      "color:#aaa;font-size:11px;margin-bottom:4px;font-weight:bold;cursor:pointer;user-select:none;";
    hlTitle.textContent = "▶ " + i18n("highlightTitle");
    highlightDiv.appendChild(hlTitle);

    const highlightContent = document.createElement("div");
    highlightContent.style.display = "none";

    const enabledRow = document.createElement("div");
    enabledRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const enabledLabel = document.createElement("span");
    enabledLabel.className = "ah-lang-label";
    enabledLabel.textContent = i18n("highlightEnabled") + ":";
    enabledRow.appendChild(enabledLabel);

    const currentEnabled = getHighlightEnabled();
    [true, false].forEach((val) => {
      const btn = document.createElement("button");
      btn.textContent = i18n(
        val ? "defaultCollapsedOn" : "defaultCollapsedOff",
      );
      btn.className = "ah-lang-btn" + (currentEnabled === val ? " active" : "");
      btn.addEventListener("click", () => {
        setHighlightEnabled(val);
        updateHighlightStyles();
        if (!val) clearFloorHighlights();
        closeGlobalSettings();
        if (onApply) onApply();
      });
      enabledRow.appendChild(btn);
    });
    highlightContent.appendChild(enabledRow);

    const colorRow = document.createElement("div");
    colorRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:6px;";
    const colorLabel = document.createElement("span");
    colorLabel.className = "ah-lang-label";
    colorLabel.textContent = i18n("highlightColor");
    colorRow.appendChild(colorLabel);

    const colorPicker = document.createElement("input");
    colorPicker.type = "color";
    colorPicker.value = getHighlightColor();
    colorPicker.style.cssText =
      "width:32px;height:22px;padding:0;border:none;background:none;cursor:pointer;";
    colorRow.appendChild(colorPicker);

    const colorText = document.createElement("input");
    colorText.type = "text";
    colorText.value = getHighlightColor();
    colorText.style.cssText =
      "width:70px;background:#222;border:1px solid #333;color:#ccc;padding:2px 4px;border-radius:3px;font-size:11px;";
    colorRow.appendChild(colorText);

    colorPicker.addEventListener("input", (e) => {
      colorText.value = e.target.value;
    });
    colorText.addEventListener("input", (e) => {
      let val = e.target.value.trim();
      if (!val.startsWith("#")) val = "#" + val;
      if (/^#[0-9A-Fa-f]{6}$/.test(val)) colorPicker.value = val;
    });

    highlightContent.appendChild(colorRow);

    const opacityRow = document.createElement("div");
    opacityRow.style.cssText = "display:flex;align-items:center;gap:6px;";
    const opacityLabel = document.createElement("span");
    opacityLabel.className = "ah-lang-label";
    opacityLabel.textContent = i18n("highlightOpacity");
    opacityRow.appendChild(opacityLabel);

    const opacityRange = document.createElement("input");
    opacityRange.type = "range";
    opacityRange.min = "0";
    opacityRange.max = "100";
    opacityRange.value = getHighlightOpacity();
    opacityRange.style.cssText = "flex:1;cursor:pointer;";
    opacityRow.appendChild(opacityRange);

    const opacityValue = document.createElement("span");
    opacityValue.style.cssText =
      "color:#ccc;font-size:11px;min-width:36px;text-align:right;";
    opacityValue.textContent = getHighlightOpacity() + "%";
    opacityRow.appendChild(opacityValue);

    opacityRange.addEventListener("input", (e) => {
      opacityValue.textContent = e.target.value + "%";
    });

    highlightContent.appendChild(opacityRow);

    const saveHlBtn = document.createElement("button");
    saveHlBtn.textContent = i18n("apply");
    saveHlBtn.style.cssText =
      "margin-top:6px;width:100%;background:#333;border:1px solid #444;color:#ccc;padding:3px 0;border-radius:4px;cursor:pointer;font-size:11px;";
    saveHlBtn.addEventListener("click", () => {
      let colorVal = colorText.value.trim();
      if (!colorVal.startsWith("#")) colorVal = "#" + colorVal;
      if (!/^#[0-9A-Fa-f]{6}$/.test(colorVal)) colorVal = DEFAULT_HL_COLOR;
      setHighlightColor(colorVal);
      setHighlightOpacity(parseInt(opacityRange.value, 10));
      updateHighlightStyles();
      closeGlobalSettings();
      if (onApply) onApply();
    });
    highlightContent.appendChild(saveHlBtn);

    hlTitle.addEventListener("click", () => {
      if (highlightContent.style.display === "none") {
        highlightContent.style.display = "";
        hlTitle.textContent = "▼ " + i18n("highlightTitle");
      } else {
        highlightContent.style.display = "none";
        hlTitle.textContent = "▶ " + i18n("highlightTitle");
      }
    });

    highlightDiv.appendChild(highlightContent);

    panel.appendChild(highlightDiv);

    const floorBorderDiv = document.createElement("div");
    floorBorderDiv.className = "ah-lang-row";

    const floorBorderTitle = document.createElement("div");
    floorBorderTitle.style.cssText =
      "color:#aaa;font-size:11px;margin-bottom:4px;font-weight:bold;cursor:pointer;user-select:none;";
    floorBorderTitle.textContent = "▶ " + i18n("floorBorderTitle");
    floorBorderDiv.appendChild(floorBorderTitle);

    const floorBorderContent = document.createElement("div");
    floorBorderContent.style.display = "none";

    const floorBorderRow = document.createElement("div");
    floorBorderRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const floorBorderLabel = document.createElement("span");
    floorBorderLabel.className = "ah-lang-label";
    floorBorderLabel.textContent = i18n("floorBorderEnabled") + ":";
    floorBorderRow.appendChild(floorBorderLabel);
    const currentFloorBorder = getEnableFloorBorder();
    [true, false].forEach((val) => {
      const btn = document.createElement("button");
      btn.textContent = i18n(
        val ? "defaultCollapsedOn" : "defaultCollapsedOff",
      );
      btn.className =
        "ah-lang-btn" + (currentFloorBorder === val ? " active" : "");
      btn.addEventListener("click", () => {
        setEnableFloorBorder(val);
        applyFloorBorderStyles();
        if (!val) clearFloorBorders();
        else {
          document
            .querySelectorAll(
              '[component="post"]:not([component="post/replies/container"] [component="post"])',
            )
            .forEach((el) => {
              el.classList.add("ah-floor-card");
            });
        }
        closeGlobalSettings();
        if (onApply) onApply();
      });
      floorBorderRow.appendChild(btn);
    });
    floorBorderContent.appendChild(floorBorderRow);

    const borderRadiusRow = document.createElement("div");
    borderRadiusRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const borderRadiusLabel = document.createElement("span");
    borderRadiusLabel.className = "ah-lang-label";
    borderRadiusLabel.textContent = i18n("borderRadius");
    borderRadiusRow.appendChild(borderRadiusLabel);
    const radiusRange = document.createElement("input");
    radiusRange.type = "range";
    radiusRange.min = "0";
    radiusRange.max = "32";
    radiusRange.step = "1";
    radiusRange.value = getFloorBorderRadius();
    radiusRange.style.cssText = "flex:1;cursor:pointer;";
    borderRadiusRow.appendChild(radiusRange);
    const radiusValue = document.createElement("span");
    radiusValue.style.cssText =
      "color:#ccc;font-size:11px;min-width:36px;text-align:right;";
    radiusValue.textContent = getFloorBorderRadius() + "px";
    borderRadiusRow.appendChild(radiusValue);
    radiusRange.addEventListener("input", (e) => {
      radiusValue.textContent = e.target.value + "px";
    });
    floorBorderContent.appendChild(borderRadiusRow);

    const borderWidthRow = document.createElement("div");
    borderWidthRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const borderWidthLabel = document.createElement("span");
    borderWidthLabel.className = "ah-lang-label";
    borderWidthLabel.textContent = i18n("borderWidth");
    borderWidthRow.appendChild(borderWidthLabel);
    const widthRange = document.createElement("input");
    widthRange.type = "range";
    widthRange.min = "0";
    widthRange.max = "4";
    widthRange.step = "1";
    widthRange.value = getFloorBorderWidth();
    widthRange.style.cssText = "flex:1;cursor:pointer;";
    borderWidthRow.appendChild(widthRange);
    const widthValue = document.createElement("span");
    widthValue.style.cssText =
      "color:#ccc;font-size:11px;min-width:36px;text-align:right;";
    widthValue.textContent = getFloorBorderWidth() + "px";
    borderWidthRow.appendChild(widthValue);
    widthRange.addEventListener("input", (e) => {
      widthValue.textContent = e.target.value + "px";
    });
    floorBorderContent.appendChild(borderWidthRow);

    const borderColorRow = document.createElement("div");
    borderColorRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-bottom:8px;";
    const borderColorLabel = document.createElement("span");
    borderColorLabel.className = "ah-lang-label";
    borderColorLabel.textContent = i18n("borderColor");
    borderColorRow.appendChild(borderColorLabel);
    const borderColorPicker = document.createElement("input");
    borderColorPicker.type = "color";
    borderColorPicker.value = getFloorBorderColor();
    borderColorPicker.style.cssText =
      "width:28px;height:20px;padding:0;border:none;background:none;cursor:pointer;";
    borderColorRow.appendChild(borderColorPicker);
    const borderColorText = document.createElement("input");
    borderColorText.type = "text";
    borderColorText.value = getFloorBorderColor();
    borderColorText.style.cssText =
      "width:70px;background:#222;border:1px solid #333;color:#ccc;padding:2px 4px;border-radius:3px;font-size:11px;";
    borderColorRow.appendChild(borderColorText);
    borderColorPicker.addEventListener("input", (e) => {
      borderColorText.value = e.target.value;
    });
    borderColorText.addEventListener("input", (e) => {
      let val = e.target.value.trim();
      if (!val.startsWith("#")) val = "#" + val;
      if (/^#[0-9A-Fa-f]{6}$/.test(val)) borderColorPicker.value = val;
    });
    floorBorderContent.appendChild(borderColorRow);

    const saveFloorBorderBtn = document.createElement("button");
    saveFloorBorderBtn.textContent = i18n("apply");
    saveFloorBorderBtn.style.cssText =
      "margin-top:6px;width:100%;background:#333;border:1px solid #444;color:#ccc;padding:3px 0;border-radius:4px;cursor:pointer;font-size:11px;";
    saveFloorBorderBtn.addEventListener("click", () => {
      let bcVal = borderColorText.value.trim();
      if (!bcVal.startsWith("#")) bcVal = "#" + bcVal;
      if (!/^#[0-9A-Fa-f]{6}$/.test(bcVal)) bcVal = "#2a2a2e";
      setFloorBorderColor(bcVal);
      setFloorBorderRadius(parseInt(radiusRange.value, 10));
      setFloorBorderWidth(parseInt(widthRange.value, 10));
      applyFloorBorderStyles();
      closeGlobalSettings();
      if (onApply) onApply();
    });
    floorBorderContent.appendChild(saveFloorBorderBtn);

    floorBorderTitle.addEventListener("click", () => {
      if (floorBorderContent.style.display === "none") {
        floorBorderContent.style.display = "";
        floorBorderTitle.textContent = "▼ " + i18n("floorBorderTitle");
      } else {
        floorBorderContent.style.display = "none";
        floorBorderTitle.textContent = "▶ " + i18n("floorBorderTitle");
      }
    });

    floorBorderDiv.appendChild(floorBorderContent);
    panel.appendChild(floorBorderDiv);
    const themeDiv = document.createElement("div");
    themeDiv.className = "ah-lang-row";

    const themeTitle = document.createElement("div");
    themeTitle.style.cssText =
      "color:#aaa;font-size:11px;margin-bottom:4px;font-weight:bold;cursor:pointer;user-select:none;";
    themeTitle.textContent = "▶ " + i18n("themeTitle");
    themeDiv.appendChild(themeTitle);

    const themeContent = document.createElement("div");
    themeContent.style.display = "none";

    const bgRow = createColorRow("themeBg", "bg", applyThemeCSS);
    themeContent.appendChild(bgRow.row);

    const cardRow = createColorRow("themeBgCard", "bgCard", applyThemeCSS);
    themeContent.appendChild(cardRow.row);

    const elevRow = createColorRow(
      "themeBgElevated",
      "bgElevated",
      applyThemeCSS,
    );
    themeContent.appendChild(elevRow.row);

    const borderRow = createColorRow("themeBorder", "border", applyThemeCSS);
    themeContent.appendChild(borderRow.row);

    const borderLightRow = createColorRow(
      "themeBorderLight",
      "borderLight",
      applyThemeCSS,
    );
    themeContent.appendChild(borderLightRow.row);

    const textRow = createColorRow("themeText", "text", applyThemeCSS);
    themeContent.appendChild(textRow.row);

    const textMutedRow = createColorRow(
      "themeTextMuted",
      "textMuted",
      applyThemeCSS,
    );
    themeContent.appendChild(textMutedRow.row);

    const primaryRow = createColorRow("themePrimary", "primary", applyThemeCSS);
    themeContent.appendChild(primaryRow.row);

    const opacityPanelRow = document.createElement("div");
    opacityPanelRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-top:4px;";
    const opacityPanelLabel = document.createElement("span");
    opacityPanelLabel.className = "ah-lang-label";
    opacityPanelLabel.textContent = i18n("themePanelOpacity");
    opacityPanelRow.appendChild(opacityPanelLabel);

    const opacityPanelRange = document.createElement("input");
    opacityPanelRange.type = "range";
    opacityPanelRange.min = "20";
    opacityPanelRange.max = "100";
    opacityPanelRange.value = getThemeColor("panelOpacity");
    opacityPanelRange.style.cssText = "flex:1;cursor:pointer;";
    opacityPanelRow.appendChild(opacityPanelRange);

    const opacityPanelValue = document.createElement("span");
    opacityPanelValue.style.cssText =
      "color:#ccc;font-size:11px;min-width:36px;text-align:right;";
    opacityPanelValue.textContent = getThemeColor("panelOpacity") + "%";
    opacityPanelRow.appendChild(opacityPanelValue);

    opacityPanelRange.addEventListener("input", (e) => {
      opacityPanelValue.textContent = e.target.value + "%";
      setThemeColor("panelOpacity", parseInt(e.target.value, 10));
      applyThemeCSS();
    });

    themeContent.appendChild(opacityPanelRow);

    const saveThemeBtn = document.createElement("button");
    saveThemeBtn.textContent = i18n("apply");
    saveThemeBtn.style.cssText =
      "margin-top:6px;width:100%;background:#333;border:1px solid #444;color:#ccc;padding:3px 0;border-radius:4px;cursor:pointer;font-size:11px;";
    saveThemeBtn.addEventListener("click", () => {
      setThemeColor("bg", bgRow.text.value.trim());
      setThemeColor("bgCard", cardRow.text.value.trim());
      setThemeColor("bgElevated", elevRow.text.value.trim());
      setThemeColor("border", borderRow.text.value.trim());
      setThemeColor("borderLight", borderLightRow.text.value.trim());
      setThemeColor("text", textRow.text.value.trim());
      setThemeColor("textMuted", textMutedRow.text.value.trim());
      setThemeColor("primary", primaryRow.text.value.trim());
      setThemeColor("panelOpacity", parseInt(opacityPanelRange.value, 10));
      applyThemeCSS();
      closeGlobalSettings();
      if (onApply) onApply();
    });
    themeContent.appendChild(saveThemeBtn);

    const resetBtn = document.createElement("button");
    resetBtn.textContent = i18n("themeReset");
    resetBtn.style.cssText =
      "margin-top:4px;width:100%;background:none;border:1px solid #555;color:#888;padding:4px 0;border-radius:4px;cursor:pointer;font-size:11px;";
    resetBtn.addEventListener("click", () => {
      resetThemeColors();
      applyThemeCSS();
      closeGlobalSettings();
      if (onApply) onApply();
    });
    themeContent.appendChild(resetBtn);

    themeTitle.addEventListener("click", () => {
      if (themeContent.style.display === "none") {
        themeContent.style.display = "";
        themeTitle.textContent = "▼ " + i18n("themeTitle");
      } else {
        themeContent.style.display = "none";
        themeTitle.textContent = "▶ " + i18n("themeTitle");
      }
    });

    themeDiv.appendChild(themeContent);

    panel.appendChild(themeDiv);

    const currentLimit = getFloorLimit();
    const floorRow = document.createElement("div");
    floorRow.style.cssText =
      "display:flex;align-items:center;gap:6px;margin-top:8px;";
    const floorLabel = document.createElement("span");
    floorLabel.className = "ah-lang-label";
    floorLabel.textContent = i18n("settingsTitle") + ":";
    floorRow.appendChild(floorLabel);
    const floorSelect = document.createElement("select");
    floorSelect.style.cssText =
      "flex:1;background:#1a1a1e;border:1px solid #333;color:#ccc;padding:4px 6px;border-radius:4px;font-size:12px;cursor:pointer;";
    FLOOR_OPTIONS.forEach((opt) => {
      const option = document.createElement("option");
      option.value = opt.value;
      option.textContent = i18n(opt.labelKey);
      if (opt.value === currentLimit) option.selected = true;
      floorSelect.appendChild(option);
    });
    floorSelect.addEventListener("change", (e) => {
      const val = parseInt(e.target.value, 10);
      if (val === 0) {
        closeGlobalSettings();
        showConfirmModal(i18n("confirmTitle"), () => {
          setFloorLimit(0);
          if (onApply) onApply();
        });
      } else {
        setFloorLimit(val);
        closeGlobalSettings();
        if (onApply) onApply();
      }
    });
    floorRow.appendChild(floorSelect);
    panel.appendChild(floorRow);

    document.body.appendChild(panel);
    globalSettingsPanel = panel;

    settingsClickHandler = (e) => {
      if (globalSettingsPanel && !globalSettingsPanel.contains(e.target)) {
        closeGlobalSettings();
      }
    };
    setTimeout(
      () => document.addEventListener("click", settingsClickHandler),
      0,
    );
  }

  let currentPanel = null;

  const createPanel = (
    foundLinks,
    relatedTopics,
    otherLinks,
    tid,
    slug,
    startFloor,
    endFloor,
  ) => {
    if (currentPanel) {
      currentPanel.remove();
      currentPanel = null;
    }

    injectStyles();
    applyThemeCSS();

    const panel = document.createElement("div");
    panel.id = "ah-dl-finder";
    currentPanel = panel;

    const titleBar = document.createElement("div");
    titleBar.className = "ah-titlebar";

    const total = foundLinks.length + relatedTopics.length + otherLinks.length;
    const limit = getFloorLimit();
    const titleText =
      limit > 0
        ? i18n("panelTitle", startFloor, endFloor, total)
        : i18n("panelTitleAll", total);
    const title = document.createElement("div");
    title.className = "ah-title";
    title.textContent = titleText;
    titleBar.appendChild(title);

    const btnGroup = document.createElement("div");
    btnGroup.className = "ah-btngroup";

    const refreshBtn = document.createElement("button");
    refreshBtn.textContent = i18n("refresh");
    refreshBtn.title = i18n("refreshTitle");
    refreshBtn.className = "ah-btn";
    refreshBtn.addEventListener("click", () => {
      panel.remove();
      currentPanel = null;
      scanTopic();
    });

    const settingsBtn = document.createElement("button");
    settingsBtn.textContent = i18n("settings");
    settingsBtn.title = i18n("settingsTitle");
    settingsBtn.className = "ah-btn";
    settingsBtn.addEventListener("click", (e) => {
      e.stopPropagation();
      openGlobalSettings(settingsBtn, () => {
        panel.remove();
        currentPanel = null;
        scanTopic();
      });
    });

    const minimizeBtn = document.createElement("button");
    minimizeBtn.textContent = i18n("minimize");
    minimizeBtn.className = "ah-btn";

    const closeBtn = document.createElement("button");
    closeBtn.textContent = i18n("close");
    closeBtn.className = "ah-btn";
    closeBtn.addEventListener("click", () => {
      panel.remove();
      currentPanel = null;
    });

    btnGroup.appendChild(refreshBtn);
    btnGroup.appendChild(settingsBtn);
    btnGroup.appendChild(minimizeBtn);
    btnGroup.appendChild(closeBtn);
    titleBar.appendChild(btnGroup);
    panel.appendChild(titleBar);

    const contentWrapper = document.createElement("div");
    contentWrapper.className = "ah-content";

    const tabBar = document.createElement("div");
    tabBar.className = "ah-tabs";

    const tabDefs = [
      { key: "dl", label: i18n("tabDL") },
      { key: "topic", label: i18n("tabTopic") },
      { key: "other", label: i18n("tabOther") },
    ];
    const tabButtons = {};
    const tabLists = {};

    tabDefs.forEach((tab) => {
      const btn = document.createElement("button");
      btn.textContent = tab.label;
      btn.className = "ah-tab";
      btn.addEventListener("click", () => switchTab(tab.key));
      tabButtons[tab.key] = btn;
      tabBar.appendChild(btn);
    });

    const contentArea = document.createElement("div");
    contentArea.className = "ah-tabarea";

    const ITEM_HEIGHT = 76;
    const VIRTUAL_BUFFER = 10;

    const buildEntryFromItem = (item, type) => {
      let tag, buttons;
      if (type === "dl") {
        tag = createTag(
          item.url,
          getLinkLabel(item.url),
          CONFIG.colors.download,
        );
        buttons = [
          createBtn(i18n("jumpLink"), () => window.open(item.url, "_blank")),
          createBtn(i18n("jumpFloor"), () =>
            scrollToPost(item.pid, item.postIndex),
          ),
        ];
      } else if (type === "topic") {
        let display = `${i18n("topicNumber")} #${item.topicId}`;
        if (item.targetFloor)
          display += ` · ${i18n("floorNumber")} #${item.targetFloor}`;
        tag = createTag(
          item.resolvedUrl || item.url,
          display,
          CONFIG.colors.topic,
        );
        buttons = [
          createBtn(i18n("jumpTopic"), () =>
            window.open(item.resolvedUrl || item.url, "_blank"),
          ),
          createBtn(i18n("jumpFloor"), () =>
            scrollToPost(item.pid, item.postIndex),
          ),
        ];
      } else {
        const info = getOtherLinkInfo(item.url);
        tag = createTag(item.url, info.label, info.color, info.icon || "");
        buttons = [
          createBtn(i18n("jumpLink"), () => window.open(item.url, "_blank")),
          createBtn(i18n("jumpFloor"), () =>
            scrollToPost(item.pid, item.postIndex),
          ),
        ];
      }
      return buildEntry(item.username, item.postIndex, tag, buttons);
    };

    const buildList = (items, type) => {
      if (!items.length) {
        const list = document.createElement("div");
        list.className = "ah-list";
        const nodata = document.createElement("div");
        nodata.className = "ah-nodata";
        nodata.textContent = i18n("noData");
        list.appendChild(nodata);
        return list;
      }

      if (items.length <= 30) {
        const list = document.createElement("div");
        list.className = "ah-list";
        items.forEach((item) => {
          list.appendChild(buildEntryFromItem(item, type));
        });
        return list;
      }

      const wrapper = document.createElement("div");
      wrapper.className = "ah-list ah-virtual-scroll";
      wrapper.style.cssText = "overflow-y:auto;flex:1;min-height:0;";

      const list = document.createElement("div");
      list.className = "ah-list";
      list.style.cssText = "display:flex;flex-direction:column;gap:8px;";

      const render = () => {
        const scrollTop = wrapper.scrollTop;
        const viewHeight = wrapper.clientHeight;
        const startIdx = Math.max(
          0,
          Math.floor(scrollTop / ITEM_HEIGHT) - VIRTUAL_BUFFER,
        );
        const endIdx = Math.min(
          items.length,
          Math.ceil((scrollTop + viewHeight) / ITEM_HEIGHT) + VIRTUAL_BUFFER,
        );

        list.style.paddingTop = `${startIdx * ITEM_HEIGHT}px`;
        list.style.paddingBottom = `${(items.length - endIdx) * ITEM_HEIGHT}px`;

        list.innerHTML = "";
        for (let i = startIdx; i < endIdx; i++) {
          list.appendChild(buildEntryFromItem(items[i], type));
        }
      };

      wrapper.addEventListener("scroll", render, { passive: true });
      requestAnimationFrame(render);

      wrapper._itemHeight = ITEM_HEIGHT;
      wrapper._items = items;
      wrapper._type = type;

      wrapper.appendChild(list);
      return wrapper;
    };

    tabLists.dl = buildList(foundLinks, "dl");
    tabLists.topic = buildList(relatedTopics, "topic");
    tabLists.other = buildList(otherLinks, "other");

    let currentTab = "dl";

    const switchTab = (key) => {
      currentTab = key;
      Object.entries(tabButtons).forEach(([k, btn]) => {
        if (k === key) {
          btn.classList.add("active");
        } else {
          btn.classList.remove("active");
        }
      });
      contentArea.innerHTML = "";
      const list = tabLists[key];
      if (list.classList.contains("ah-virtual-scroll")) {
        contentArea.style.overflowY = "hidden";
      } else {
        contentArea.style.overflowY = "";
      }
      contentArea.appendChild(list);
    };

    switchTab("dl");

    contentWrapper.appendChild(tabBar);
    contentWrapper.appendChild(contentArea);
    panel.appendChild(contentWrapper);

    let minimized = getDefaultCollapsed();
    if (minimized) {
      contentWrapper.style.display = "none";
      minimizeBtn.textContent = "+";
    }

    minimizeBtn.addEventListener("click", () => {
      minimized = !minimized;
      if (minimized) {
        contentWrapper.style.display = "none";
        minimizeBtn.textContent = "+";
      } else {
        contentWrapper.style.display = "flex";
        minimizeBtn.textContent = i18n("minimize");
      }
    });

    document.body.appendChild(panel);
  };

  const scrollToFloorInPanel = (floor) => {
    if (!currentPanel) return;

    const floorEntry = currentPanel.querySelector(
      `[data-post-index="${floor}"]`,
    );
    if (floorEntry) {
      floorEntry.scrollIntoView({ behavior: "smooth", block: "center" });
      highlightElement(floorEntry);
      return;
    }

    const vList = currentPanel.querySelector(".ah-virtual-scroll");
    if (!vList || !vList._items) return;

    const idx = vList._items.findIndex(
      (item) => String(item.postIndex) === String(floor),
    );
    if (idx >= 0) {
      vList.scrollTop = idx * (vList._itemHeight || 76);
      requestAnimationFrame(() => {
        const entry = currentPanel.querySelector(
          `[data-post-index="${floor}"]`,
        );
        if (entry) highlightElement(entry);
      });
    }
  };

  let scanTimer = null;
  let currentScannedTid = null;
  let scanId = 0;

  let loadingPanel = null;

  const showLoadingPanel = (text) => {
    hideLoadingPanel();
    injectStyles();
    applyThemeCSS();
    const panel = document.createElement("div");
    panel.id = "ah-dl-finder-loading";
    panel.style.cssText =
      "position:fixed;bottom:20px;right:20px;width:480px;max-width:calc(100vw - 40px);background:var(--ah-bg);border:1px solid var(--ah-border);border-radius:8px;color:var(--ah-text);font-family:system-ui,sans-serif;font-size:12px;z-index:999999;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.8);display:flex;flex-direction:column;opacity:var(--ah-panel-opacity);";
    panel.innerHTML = `
      <div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ah-border);">
        <div style="font-size:14px;font-weight:bold;color:var(--ah-primary);">${text}</div>
        <div style="display:flex;gap:6px;">
          <button style="background:var(--ah-bg-elevated);border:1px solid var(--ah-border-light);color:var(--ah-text);font-size:14px;padding:2px 8px;border-radius:4px;cursor:pointer;">✕</button>
        </div>
      </div>
      <div style="padding:20px;text-align:center;color:var(--ah-text-muted);">
        <div style="margin-bottom:10px;">⏳ ${i18n("loading")}</div>
        <div style="width:100%;height:4px;background:var(--ah-border);border-radius:2px;overflow:hidden;">
          <div id="ah-loading-bar" style="width:0%;height:100%;background:var(--ah-primary);transition:width 0.3s;"></div>
        </div>
      </div>
    `;
    panel.querySelector("button").addEventListener("click", () => {
      if (currentScanController) currentScanController.aborted = true;
      hideLoadingPanel();
    });
    document.body.appendChild(panel);
    loadingPanel = panel;
  };

  const hideLoadingPanel = () => {
    if (loadingPanel) {
      loadingPanel.remove();
      loadingPanel = null;
    }
  };

  const updateLoadingProgress = (current, total) => {
    if (!loadingPanel) return;
    const bar = loadingPanel.querySelector("#ah-loading-bar");
    const title = loadingPanel.querySelector("div > div:first-child");
    if (bar) bar.style.width = `${Math.min(100, (current / total) * 100)}%`;
    if (title) title.textContent = `↓ ${i18n("loading")} (${current}/${total})`;
  };

  const INTERNAL_PATHS = [
    "/user",
    "/login",
    "/settings",
    "/admin",
    "/api",
    "/assets",
    "/uploads",
    "/plugins",
    "/vendor",
  ];
  const isInternalPath = (path) => {
    const lower = path.toLowerCase();
    return INTERNAL_PATHS.some((p) => lower.startsWith(p));
  };

  const scanTopic = async () => {
    const m = location.pathname.match(/\/topic\/(\d+)/);
    if (!m) return;
    const currentTid = m[1];
    const limit = getFloorLimit();
    const centerFloor = getCurrentFloor();

    scanId++;
    const thisScanId = scanId;
    if (currentScanController) {
      currentScanController.aborted = true;
    }
    const controller = { aborted: false };
    currentScanController = controller;

    abortAllPending();

    clearFloorHighlights();
    updateHighlightStyles();

    const showProgress = limit === 0;
    if (showProgress) showLoadingPanel(i18n("loading"));

    try {
      const data = await getAllTopicData(
        currentTid,
        limit,
        centerFloor,
        controller,
        (progress) => {
          if (showProgress)
            updateLoadingProgress(progress.current, progress.total);
        },
      );

      hideLoadingPanel();

      if (controller.aborted || thisScanId !== scanId) return;

      const posts = data.posts || [];
      const startFloor = data.startFloor || 1;
      let endFloor = data.endFloor || posts.length;

      const actualPostFloors = posts.map((p) => (p.index || 0) + 1);
      if (actualPostFloors.length > 0) {
        endFloor = Math.max(...actualPostFloors);
      }
      const foundLinks = [];
      const relatedMap = new Map();
      const otherMap = new Map();
      const pendingResolves = [];
      const seenDLUrls = new Set();
      const seenOtherUrls = new Set();
      const seenRelatedUrls = new Set();

      for (const post of posts) {
        if (!post || !post.pid) continue;
        const raw = (post.rawContent || "").trim();
        const html = (post.content || "").trim();
        if (!raw && !html) continue;

        const pid = post.pid;
        const username =
          (post.user && (post.user.displayname || post.user.username)) ||
          i18n("unknownUser");
        const postIndex = (post.index || 0) + 1;

        let dlUrls = [];

        if (hasDLPattern(raw)) {
          dlUrls.push(...extractURLs(raw));
        }
        const strippedHtml = stripHTML(html);
        if (hasDLPattern(strippedHtml)) {
          dlUrls.push(...extractURLs(strippedHtml));
        }
        if (html) {
          const t = document.createElement("div");
          t.innerHTML = html;
          const as = t.querySelectorAll("a[href]");
          for (const a of as) {
            const href = a.getAttribute("href") || "";
            if (hasDLPattern(href)) {
              dlUrls.push(href);
            }
          }
        }

        for (let dlUrl of [...new Set(dlUrls)]) {
          if (!dlUrl) continue;
          if (!/^https?:\/\//i.test(dlUrl))
            dlUrl = "https://forum.ripper.store/" + dlUrl;
          const normalizedDlUrl = dlUrl
            .replace(/\?.*$/, "")
            .replace(/[.,;!?]+$/, "");
          if (!seenDLUrls.has(normalizedDlUrl)) {
            seenDLUrls.add(normalizedDlUrl);
            foundLinks.push({ url: dlUrl, postIndex, pid, username });
          }
        }

        if (dlUrls.length > 0) {
          persistHighlightFloor(pid, postIndex);
        }

        const allRawUrls = new Set();
        const textData = raw + " " + strippedHtml;

        for (const match of textData.matchAll(GENERIC_URL_REGEX)) {
          allRawUrls.add(match[0]);
        }

        if (html) {
          const tempDiv = document.createElement("div");
          tempDiv.innerHTML = html;
          const anchors = tempDiv.querySelectorAll("a[href]");
          for (const a of anchors) {
            const href = a.getAttribute("href") || "";
            if (href.startsWith("//")) {
              allRawUrls.add("https:" + href);
              continue;
            }
            if (!/^https?:\/\//i.test(href) && href.startsWith("/")) {
              if (isInternalPath(href)) continue;
            }
            if (href.startsWith("http") || href.startsWith("/")) {
              allRawUrls.add(href);
            }
          }
        }

        for (let rawUrl of allRawUrls) {
          if (rawUrl.startsWith("//")) rawUrl = "https:" + rawUrl;
          else if (rawUrl.startsWith("/"))
            rawUrl = `https://forum.ripper.store${rawUrl}`;

          rawUrl = rawUrl.replace(/[.,;!?]+$/, "");
          const cleanUrl = rawUrl.replace(/\?.*$/, "");

          if (hasDLPattern(cleanUrl)) continue;

          const isTopic = /ripper\.store\/topic\//i.test(cleanUrl);
          const isPost = /ripper\.store\/post\//i.test(cleanUrl);

          if (isTopic) {
            const tidMatch = cleanUrl.match(/\/topic\/(\d+)/);
            if (!tidMatch) continue;
            const tid = tidMatch[1];
            if (!tid || tid === currentTid) continue;

            const targetFloor = extractTargetFloor(cleanUrl);

            if (!seenRelatedUrls.has(cleanUrl)) {
              seenRelatedUrls.add(cleanUrl);
              relatedMap.set(cleanUrl, {
                url: cleanUrl,
                postIndex,
                pid,
                username,
                topicId: tid,
                targetFloor,
                resolvedUrl: cleanUrl,
              });
            } else if (targetFloor) {
              const existing = relatedMap.get(cleanUrl);
              if (existing && !existing.targetFloor)
                existing.targetFloor = targetFloor;
            }
            continue;
          }

          if (isPost) {
            if (!seenRelatedUrls.has(cleanUrl)) {
              seenRelatedUrls.add(cleanUrl);
              const entry = {
                url: cleanUrl,
                postIndex,
                pid,
                username,
                topicId: "???",
                targetFloor: null,
                resolvedUrl: cleanUrl,
                isPostLink: true,
              };
              relatedMap.set(cleanUrl, entry);
              pendingResolves.push({ originalLink: cleanUrl, entryRef: entry });
            }
            continue;
          }

          if (!seenOtherUrls.has(cleanUrl)) {
            seenOtherUrls.add(cleanUrl);
            otherMap.set(cleanUrl, { url: cleanUrl, postIndex, pid, username });
          }
        }
      }

      if (controller.aborted || thisScanId !== scanId) return;

      if (pendingResolves.length > 0) {
        const resolved = await resolveAllLinks(pendingResolves, controller);
        if (controller.aborted || thisScanId !== scanId) return;
        for (const item of resolved) {
          if (item.finalUrl && item.finalUrl !== item.originalLink) {
            const m2 = item.finalUrl.match(/\/topic\/(\d+)/);
            if (m2 && item.entryRef) {
              item.entryRef.topicId = m2[1];
              item.entryRef.resolvedUrl = item.finalUrl;
              const floor = extractTargetFloor(item.finalUrl);
              if (floor) item.entryRef.targetFloor = floor;
            }
          }
        }
      }

      if (controller.aborted || thisScanId !== scanId) return;

      createPanel(
        foundLinks,
        Array.from(relatedMap.values()),
        Array.from(otherMap.values()),
        currentTid,
        "",
        startFloor,
        endFloor,
      );

      if (getEnableFloorBorder()) {
        document
          .querySelectorAll(
            '[component="post"]:not([component="post/replies/container"] [component="post"])',
          )
          .forEach((el) => {
            el.classList.add("ah-floor-card");
          });
      }

      applyHighlightsToAllPosts();
      observeNewPostsForHighlight();

      const floorMatch = location.href.match(/\/(\d+)$/);
      if (floorMatch) scrollToFloorInPanel(floorMatch[1]);
    } catch (err) {
      hideLoadingPanel();
      if (thisScanId === scanId) {
        console.error("[Ripper.Store 资源猎手] 扫描失败:", err);
        showErrorToast(i18n("loadError"));
      }
    }
  };

  const detectPageType = () => {
    const path = location.pathname;
    if (/^\/topic\/\d+/.test(path)) return "thread";
    if (
      /^\/category\//.test(path) ||
      /^\/tags\//.test(path) ||
      /^\/recent\b/.test(path) ||
      /^\/popular\b/.test(path) ||
      /^\/unread\b/.test(path) ||
      /^\/notifications\b/.test(path) ||
      path === "/" ||
      path === ""
    )
      return "topic-list";
    if (/^\/search/.test(path)) return "search";
    if (/^\/user\/.*\/bookmarks/.test(path)) return "bookmarks";
    return "unknown";
  };

  const isForumHomePage = () => {
    return location.pathname === "/" || location.pathname === "";
  };

  const applyThumbSettings = () => {
    const scale = getThumbnailScale();
    document.documentElement.style.setProperty(
      "--ah-thumb-scale",
      String(scale),
    );
    document.documentElement.classList.toggle(
      "ah-large-thumbs",
      getEnableLargeThumbs(),
    );
    document.documentElement.classList.toggle(
      "ah-hover-preview",
      getEnableHoverPreview() && getEnableLargeThumbs(),
    );
    document.documentElement.dataset.ahHomePage = String(isForumHomePage());
  };

  const markTopicCards = () => {
    const cards = document.querySelectorAll("[component='category/topic']");
    for (const card of cards) {
      const thumb = card.querySelector(".topic-thumb");
      card.dataset.ahHasThumb = String(Boolean(thumb));
    }
  };

  const restructureTopicCards = () => {
    if (
      !document.documentElement.classList.contains("ah-large-thumbs") ||
      isForumHomePage()
    )
      return;
    for (const card of document.querySelectorAll(
      "[component='category/topic']",
    )) {
      if (card.closest(".top_topics")) continue;
      if (card.classList.contains("ah-layout-ready")) continue;
      const thumbImg = card.querySelector(".topic-thumb");
      card.classList.add("ah-layout-ready");
      card.classList.toggle("ah-has-thumb", Boolean(thumbImg));
      const thumb = card.querySelector(
        ":scope > .topic-thumbs, :scope .topic-thumbs",
      );
      const stats =
        card.querySelector(":scope > .stats") ||
        [...card.children].find((child) =>
          child.querySelector?.(".stats-postcount"),
        );
      if (thumb) {
        thumb.classList.add("ah-topic-thumb-slot");
        if (thumb.parentElement !== card) card.prepend(thumb);
      }
      if (stats) {
        stats.classList.add("ah-topic-stats-slot");
        if (stats.parentElement !== card) card.appendChild(stats);
      }
      let body = card.querySelector(".ah-topic-main");
      if (!body) {
        body = document.createElement("div");
        body.className = "ah-topic-main";
        for (const child of [...card.children]) {
          if (
            child === thumb ||
            child === stats ||
            child.classList.contains("ah-topic-main")
          )
            continue;
          body.appendChild(child);
        }
        if (thumb?.nextSibling) {
          card.insertBefore(body, thumb.nextSibling);
        } else if (thumb) {
          card.appendChild(body);
        } else {
          card.prepend(body);
        }
      }
    }
  };

  const setupThumbnailHoverPreview = () => {
    if (
      document.querySelector(".ah-thumb-preview") ||
      document.body.dataset.ahHoverPreviewBound === "true"
    )
      return;
    document.body.dataset.ahHoverPreviewBound = "true";
    const preview = document.createElement("div");
    preview.className = "ah-thumb-preview";
    preview.hidden = true;
    preview.innerHTML = `<img alt=""><span class="ah-thumb-preview-label">Preview</span>`;
    document.body.appendChild(preview);
    const image = preview.querySelector("img");
    let hideTimer = null;
    let pinned = false;
    const show = (thumb) => {
      clearTimeout(hideTimer);
      if (!image) return;
      image.src = thumb.currentSrc || thumb.src;
      preview.hidden = false;
      const rect = thumb.getBoundingClientRect();
      const previewWidth = Math.min(420, window.innerWidth - 24);
      const previewHeight = Math.min(320, window.innerHeight - 24);
      let left = rect.right + 14;
      let top = rect.top;
      if (left + previewWidth > window.innerWidth - 12) {
        left = Math.max(12, rect.left - previewWidth - 14);
      }
      if (top + previewHeight > window.innerHeight - 12) {
        top = Math.max(12, window.innerHeight - previewHeight - 12);
      }
      preview.style.width = `${previewWidth}px`;
      preview.style.left = `${left}px`;
      preview.style.top = `${top}px`;
    };
    const hide = () => {
      hideTimer = setTimeout(() => {
        if (pinned) return;
        preview.hidden = true;
        image?.removeAttribute("src");
      }, 120);
    };
    preview.addEventListener("mouseenter", () => clearTimeout(hideTimer));
    preview.addEventListener("mouseleave", hide);
    document.addEventListener("mouseover", (event) => {
      const thumb = event.target.closest(".topic-thumb");
      if (
        !thumb ||
        !document.documentElement.classList.contains("ah-hover-preview")
      )
        return;
      show(thumb);
    });
    document.addEventListener("mouseout", (event) => {
      const thumb = event.target.closest(".topic-thumb");
      if (!thumb) return;
      const related = event.relatedTarget;
      if (related && (preview.contains(related) || thumb.contains(related)))
        return;
      hide();
    });
    document.addEventListener(
      "click",
      (event) => {
        const thumb = event.target.closest(".topic-thumb");
        if (
          !thumb ||
          !document.documentElement.classList.contains("ah-hover-preview")
        )
          return;
        if (window.matchMedia("(hover: hover)").matches) return;
        event.preventDefault();
        pinned = !pinned;
        if (pinned) {
          show(thumb);
        } else {
          preview.hidden = true;
          image?.removeAttribute("src");
        }
      },
      true,
    );
  };

  const enhanceTopicList = () => {
    injectStyles();
    applyThemeCSS();
    applyThumbSettings();
    markTopicCards();
    restructureTopicCards();
    if (getEnableHoverPreview() && getEnableLargeThumbs()) {
      setupThumbnailHoverPreview();
    }

    if (!window.__ahTopicObserver) {
      window.__ahTopicObserver = new MutationObserver(() => {
        if (detectPageType() === "topic-list") {
          markTopicCards();
          restructureTopicCards();
        }
      });
      const container =
        document.querySelector("#content") ||
        document.querySelector(".topics-list") ||
        document.body;
      window.__ahTopicObserver.observe(container, {
        childList: true,
        subtree: true,
      });
    }
  };

  const debouncedScan = () => {
    if (scanTimer) clearTimeout(scanTimer);
    scanTimer = setTimeout(() => {
      const m = location.href.match(/\/topic\/(\d+)/);
      const newTid = m ? m[1] : null;
      const pageType = detectPageType();
      if (pageType === "thread") {
        if (newTid !== currentScannedTid) {
          currentScannedTid = newTid;
          if (currentPanel) {
            currentPanel.remove();
            currentPanel = null;
          }
          removeThumbSettingsButton();
          scanTopic();
        } else {
          const floorMatch = location.href.match(/\/(\d+)$/);
          if (floorMatch) scrollToFloorInPanel(floorMatch[1]);
        }
      } else if (pageType === "topic-list") {
        currentScannedTid = null;
        if (currentPanel) {
          currentPanel.remove();
          currentPanel = null;
        }
        enhanceTopicList();
        createThumbSettingsButton();
      }
    }, CONFIG.debounceDelay);
  };

  setTimeout(() => {
    currentScannedTid =
      (location.href.match(/\/topic\/(\d+)/) || [])[1] || null;
    updateHighlightStyles();
    applyFloorBorderStyles();
    applyThemeCSS();
    applyThumbSettings();
    showWelcomeModal();
    const pageType = detectPageType();
    if (pageType === "thread") {
      scanTopic();
    } else if (pageType === "topic-list") {
      enhanceTopicList();
      createThumbSettingsButton();
    }
  }, CONFIG.scanDelay);

  let lastUrl = location.href;
  const urlObserver = new MutationObserver(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      debouncedScan();
    }
  });
  urlObserver.observe(document.body, { childList: true, subtree: true });

  window.addEventListener("beforeunload", () => {
    removeThumbSettingsButton();

    if (urlObserver) urlObserver.disconnect();
    if (scanTimer) clearTimeout(scanTimer);
    if (currentPanel) {
      currentPanel.remove();
      currentPanel = null;
    }
    closeGlobalSettings();
    abortAllPending();
    if (window.__ahHighlightObserver) {
      window.__ahHighlightObserver.disconnect();
      window.__ahHighlightObserver = null;
    }
  });
})();