From 78d03fba90cfff7a93ee77ee7246372c177c78c9 Mon Sep 17 00:00:00 2001 From: Jack Adam Date: Sun, 25 Jan 2026 08:07:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Chaturbate/chaturbate-thumbnails-2x.user.js | 233 ++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 Chaturbate/chaturbate-thumbnails-2x.user.js diff --git a/Chaturbate/chaturbate-thumbnails-2x.user.js b/Chaturbate/chaturbate-thumbnails-2x.user.js new file mode 100644 index 0000000..d2a2d1c --- /dev/null +++ b/Chaturbate/chaturbate-thumbnails-2x.user.js @@ -0,0 +1,233 @@ +// ==UserScript== +// @name Chaturbate 缩略图放大 2 倍 +// @namespace https://chaturbate.com/ +// @version 0.1.1 +// @description 将房间列表缩略图按宽高 2 倍放大 +// @match https://chaturbate.com/* +// @match https://*.chaturbate.com/* +// @run-at document-start +// @grant none +// ==/UserScript== + +(function () { + "use strict"; + + const SCALE = 2; + const SCALED_ATTR = "data-tm-scaled"; + const BASE_W_ATTR = "data-tm-base-w"; + const BASE_H_ATTR = "data-tm-base-h"; + const DEBUG = true; + + // 提前注入 CSS,避免闪烁 + const injectCSS = () => { + const style = document.createElement("style"); + style.id = "tm-thumb-scale-style"; + style.textContent = ` + img.room_thumbnail:not([${SCALED_ATTR}="1"]) { + opacity: 0; + transition: opacity 0.1s; + } + img.room_thumbnail[${SCALED_ATTR}="1"] { + opacity: 1; + } + ul.list, + ul.list.endless_page_template { + display: flex !important; + flex-wrap: wrap !important; + gap: 0 !important; + } + ul.list li.roomCard, + ul.list.endless_page_template li.roomCard { + position: relative !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; + margin: 0 !important; + padding: 0 !important; + } + `; + if (document.head) { + if (!document.getElementById("tm-thumb-scale-style")) { + document.head.appendChild(style); + } + } else { + document.addEventListener("DOMContentLoaded", () => { + if (!document.getElementById("tm-thumb-scale-style")) { + document.head.appendChild(style); + } + }); + } + }; + injectCSS(); + + const relaxBox = (el) => { + if (!el) return; + el.style.overflow = "visible"; + el.style.maxWidth = "none"; + }; + + const relaxAncestors = (el, depth = 6) => { + let current = el?.parentElement; + let steps = 0; + while (current && steps < depth && current !== document.body) { + relaxBox(current); + current = current.parentElement; + steps += 1; + } + }; + + const relaxKnownOuter = () => { + const selectors = [ + "#roomlist_content_wrapper", + "#roomlist_root", + ".BaseRoomContents", + "[data-testid='room-list']", + "ul.list", + "ul.list.endless_page_template", + ]; + selectors.forEach((selector) => { + document.querySelectorAll(selector).forEach((el) => { + relaxBox(el); + // 如果是列表容器,确保布局能容纳更大的卡片 + if (el.matches?.("ul.list, ul.list.endless_page_template")) { + el.style.width = "100%"; + el.style.maxWidth = "none"; + el.style.boxSizing = "border-box"; + el.style.display = "flex"; + el.style.flexWrap = "wrap"; + el.style.gap = "0"; + } + }); + }); + }; + + const scale = () => { + if (DEBUG) console.log("[tm] running on", location.href); + relaxKnownOuter(); + const items = document.querySelectorAll("a.room_thumbnail_container"); + if (DEBUG) console.log("[tm] containers:", items.length); + items.forEach((container) => { + const img = container.querySelector("img.room_thumbnail"); + if (DEBUG && !img) console.log("[tm] no img in container", container); + if (!img) return; + + const attrW = parseFloat(img.getAttribute("width")); + const attrH = parseFloat(img.getAttribute("height")); + const baseW = parseFloat(img.getAttribute(BASE_W_ATTR)) || attrW; + const baseH = parseFloat(img.getAttribute(BASE_H_ATTR)) || attrH; + if (!baseW || !baseH) return; + + if (!img.getAttribute(BASE_W_ATTR) || !img.getAttribute(BASE_H_ATTR)) { + img.setAttribute(BASE_W_ATTR, String(baseW)); + img.setAttribute(BASE_H_ATTR, String(baseH)); + } + + const scaledW = baseW * SCALE; + const scaledH = baseH * SCALE; + if (DEBUG) console.log("[tm] img size:", baseW, baseH, img); + + img.setAttribute("width", String(scaledW)); + img.setAttribute("height", String(scaledH)); + img.setAttribute(SCALED_ATTR, "1"); + img.style.width = `${scaledW}px`; + img.style.height = `${scaledH}px`; + img.style.maxWidth = "none"; + img.style.maxHeight = "none"; + img.style.display = "block"; + img.style.opacity = "1"; + + container.style.width = `${scaledW}px`; + container.style.height = `${scaledH}px`; + container.style.minWidth = `${scaledW}px`; + container.style.maxWidth = `${scaledW}px`; + container.style.minHeight = `${scaledH}px`; + container.style.maxHeight = "none"; + container.style.overflow = "visible"; + container.style.display = "block"; + container.style.position = "relative"; + container.style.boxSizing = "border-box"; + container.style.flexShrink = "0"; + container.style.margin = "0"; + + const card = container.closest("li.roomCard"); + if (card) { + card.style.width = `${scaledW}px`; + card.style.minWidth = `${scaledW}px`; + card.style.maxWidth = `${scaledW}px`; + card.style.minHeight = `${scaledH}px`; + card.style.height = "auto"; + card.style.overflow = "visible"; + card.style.position = "relative"; + card.style.flexShrink = "0"; + card.style.flexGrow = "0"; + card.style.flexBasis = `${scaledW}px`; + card.style.boxSizing = "border-box"; + card.style.margin = "0"; + card.style.padding = "0"; + relaxAncestors(card); + } + relaxAncestors(container); + }); + }; + + const init = () => { + if (DEBUG) { + const thumbs = document.querySelectorAll("img.room_thumbnail"); + console.log("[tm] thumbnails:", thumbs.length, Array.from(thumbs)); + } + scale(); + }; + + // 立即执行(如果 DOM 已就绪) + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + // 延迟重试,处理动态加载 + window.setTimeout(scale, 100); + window.setTimeout(scale, 300); + window.setTimeout(scale, 600); + window.setTimeout(scale, 1000); + window.setTimeout(scale, 2000); + + // MutationObserver 监听动态添加的内容 + const observer = new MutationObserver((mutations) => { + let shouldScale = false; + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + if ( + node.matches?.("a.room_thumbnail_container") || + node.matches?.("li.roomCard") || + node.querySelector?.("a.room_thumbnail_container") + ) { + shouldScale = true; + } + } + }); + }); + if (shouldScale) { + if (DEBUG) console.log("[tm] new content detected, scaling..."); + scale(); + } + }); + + // 开始观察 + const startObserving = () => { + const target = document.body || document.documentElement; + if (target) { + observer.observe(target, { + childList: true, + subtree: true, + }); + if (DEBUG) console.log("[tm] MutationObserver started"); + } + }; + + if (document.body) { + startObserving(); + } else { + document.addEventListener("DOMContentLoaded", startObserving); + } +})();