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