Ripper.Store Assets Hunter

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).

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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