Sleazy Fork is available in English.
Injects bidirectional cross-site resource search into South Plus and DLsite, featuring detailed metadata preview cards and one-click redirection to ASMR ONE.
// ==UserScript==
// @name RJ Warp Gate
// @name:zh RJ号折跃门
// @name:zh-CN RJ号折跃门
// @name:zh-TW RJ號折躍門
// @namespace Leovikii
// @version 1.3.0
// @author Leovikii
// @description Injects bidirectional cross-site resource search into South Plus and DLsite, featuring detailed metadata preview cards and one-click redirection to ASMR ONE.
// @description:zh 为南+论坛和 DLsite 注入双向资源检索与高亮功能,提供详尽的元数据预览卡片及 ASMR ONE 在线试听一键跳转。
// @description:zh-CN 为南+论坛和 DLsite 注入双向资源检索与高亮功能,提供详尽的元数据预览卡片及 ASMR ONE 在线试听一键跳转。
// @description:zh-TW 為南+論壇和 DLsite 注入雙向資源檢索與高亮功能,提供詳盡的元數據預覽卡片及 ASMR ONE 在線試聽一鍵跳轉。
// @license GPL-3.0-only
// @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2064%2064%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3ClinearGradient%20id%3D%22background%22%20x1%3D%228%22%20y1%3D%226%22%20x2%3D%2258%22%20y2%3D%2260%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%0A%20%20%20%20%20%20%3Cstop%20stop-color%3D%22%23ec4899%22%2F%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%220.52%22%20stop-color%3D%22%23a855f7%22%2F%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%221%22%20stop-color%3D%22%236d28d9%22%2F%3E%0A%20%20%20%20%3C%2FlinearGradient%3E%0A%20%20%20%20%3CradialGradient%20id%3D%22highlight%22%20cx%3D%220%22%20cy%3D%220%22%20r%3D%221%22%20gradientTransform%3D%22translate(18%2012)%20rotate(48)%20scale(48)%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%0A%20%20%20%20%20%20%3Cstop%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220.28%22%2F%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%220.58%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%0A%20%20%20%20%3C%2FradialGradient%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23background)%22%2F%3E%0A%20%20%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23highlight)%22%2F%3E%0A%20%20%3Crect%20x%3D%221%22%20y%3D%221%22%20width%3D%2262%22%20height%3D%2262%22%20rx%3D%2214%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-opacity%3D%220.18%22%20stroke-width%3D%222%22%2F%3E%0A%20%20%3Cg%20transform%3D%22translate(4%200)%20skewX(-6)%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-width%3D%226%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M14.5%2046V18h10.2c6.6%200%2010.4%203.1%2010.4%208.2s-3.8%208.2-10.4%208.2H14.5M25.8%2034.4%2036.8%2046%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M49.5%2018v17.1c0%207.3-3.8%2011-10%2011-3.8%200-6.6-1.8-8-5%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A
// @homepage https://github.com/Leovikii/RJ-Warp-Gate
// @homepageURL https://github.com/Leovikii/RJ-Warp-Gate
// @source https://github.com/Leovikii/RJ-Warp-Gate.git
// @supportURL https://github.com/Leovikii/RJ-Warp-Gate/issues
// @match *://*.south-plus.net/*
// @match *://*.spring-plus.net/*
// @match *://*.level-plus.net/*
// @match *://*.dlsite.com/*
// @match *://*.dlsite.com.tw/*
// @connect www.dlsite.com
// @connect api.asmr-200.com
// @connect south-plus.net
// @connect www.south-plus.net
// @connect *.south-plus.net
// @connect spring-plus.net
// @connect www.spring-plus.net
// @connect *.spring-plus.net
// @connect level-plus.net
// @connect www.level-plus.net
// @connect *.level-plus.net
// @grant GM.xmlHttpRequest
// @grant GM_addStyle
// @grant GM_deleteValue
// @grant GM_getValue
// @grant GM_info
// @grant GM_listValues
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @run-at document-start
// ==/UserScript==
(function() {
"use strict";
var s$2 = new Set();
var _css = async (t) => {
if (s$2.has(t)) return;
s$2.add(t);
((c) => {
if (typeof GM_addStyle === "function") GM_addStyle(c);
else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
})(t);
};
_css(":root{--rwg-bg:#1e1e1ef0;--rwg-border:#ffffff29;--rwg-text:#f1f5f9}.rwg-root{z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;position:relative}.rwg-popup{z-index:2147483646;width:650px;max-width:calc(100vw - 20px);max-height:min(680px,100vh - 20px);color:var(--rwg-text);background:var(--rwg-bg);-webkit-backdrop-filter:blur(14px)saturate(120%);backdrop-filter:blur(14px)saturate(120%);border:1px solid var(--rwg-border);box-sizing:border-box;pointer-events:auto;overscroll-behavior:contain;border-top:2px solid #ec489980;border-radius:18px;padding:16px;animation:.16s ease-out rwg-in;position:fixed;overflow:auto;box-shadow:0 20px 44px #00000073}.rwg-popup--work{overscroll-behavior:none;width:580px;max-height:min(520px,100vh - 20px);padding:14px;overflow:hidden}.rwg-theme-girls{border-top-color:#f973168c}.rwg-theme-default{border-top-color:var(--rwg-border)}.rwg-popup__header{border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;gap:12px;margin-bottom:10px;padding-bottom:8px;font-size:16px;display:flex}.rwg-popup__close{appearance:none;color:#fff;cursor:pointer;background:#ffffff1a;border:0;border-radius:50%;width:30px;height:30px;font-size:20px;line-height:1}.rwg-popup__close:hover{background:#fff3}.rwg-work__hero{grid-template-columns:190px minmax(0,1fr);align-items:start;gap:14px;display:grid}.rwg-work__cover-column{gap:10px;min-width:0;display:grid}.rwg-work__cover,.rwg-work__main{min-width:0}.rwg-work__title{appearance:none;width:100%;color:inherit;font:inherit;letter-spacing:-.01em;text-align:left;cursor:pointer;-webkit-line-clamp:3;background:0 0;border:0;-webkit-box-orient:vertical;margin:0 0 4px;padding:0;font-size:17px;font-weight:700;line-height:1.25;display:-webkit-box;overflow:hidden}.rwg-work__title:hover{color:#93c5fd}.rwg-work__title.is-copied{color:#4ade80}.rwg-work__circle{color:#d8b4fe;margin:0 0 7px;font-size:13px;font-weight:600}.rwg-cover{aspect-ratio:4/3;background:#ffffff0d;border-radius:10px;width:100%;position:relative;overflow:hidden}.rwg-cover img{object-fit:cover;width:100%;height:100%;display:block}.rwg-cover__placeholder{position:absolute;inset:0}.rwg-badges{flex-wrap:wrap;gap:5px;display:flex}.rwg-badge{color:#f8fafc;white-space:nowrap;background:#334155;border:1px solid #64748b;border-radius:5px;padding:3px 7px;font-size:11px;line-height:1.2;display:inline-block}.rwg-badge--sales,.rwg-badge--danger{color:#fecaca;background:#7f1d1d;border-color:#dc2626}.rwg-badge--rating{color:#fef08a;background:#713f12;border-color:#eab308}.rwg-badge--type{color:#ccfbf1;background:#134e4a;border-color:#0f766e}.rwg-badge--voice{color:#e9d5ff;background:#581c87;border-color:#9333ea}.rwg-badge--genre{color:#dbeafe;background:#17365d;border-color:#3b82f6}.rwg-info{grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:5px 8px;margin-top:8px;display:grid}.rwg-info>strong{color:#cbd5e1;white-space:nowrap;font-size:12px;line-height:1.65}.rwg-info>.rwg-badges{min-width:0}.rwg-actions{gap:9px;display:grid}.rwg-actions--cover{margin-top:0}.rwg-popup--work .rwg-actions{gap:7px}.rwg-popup--work .rwg-action{border-radius:10px;padding:8px 11px;font-size:13px}.rwg-action{box-sizing:border-box;cursor:pointer;background-clip:padding-box;border:1px solid #0000;border-radius:12px;justify-content:space-between;align-items:center;width:100%;padding:10px 14px;transition:transform .18s,filter .18s,box-shadow .18s;display:flex;box-shadow:0 4px 10px #00000040,inset 0 1px #ffffff24;color:#fff!important;text-decoration:none!important}.rwg-root a.rwg-action:not(.is-disabled){color:#fff!important}.rwg-action>strong{text-shadow:0 1px 2px #00000038;font-weight:700;color:inherit!important}.rwg-action>.rwg-action__brand{font-variant-ligatures:none;letter-spacing:-.045em;text-rendering:geometricprecision;font-size:1.08em;font-style:normal;font-weight:900;line-height:1;font-family:Arial Rounded MT Bold,Trebuchet MS,Segoe UI,sans-serif!important}.rwg-action--asmrone>.rwg-action__brand{letter-spacing:-.025em;font-size:1em}.rwg-external-icon,.rwg-unavailable-icon{fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;flex:none;width:18px;height:18px;display:block}.rwg-action>.rwg-external-icon,.rwg-action>.rwg-unavailable-icon{opacity:.9;color:inherit!important}.rwg-action:not(.is-disabled):hover{filter:brightness(1.1);transform:translateY(-1px);box-shadow:0 6px 14px #0000004d,inset 0 1px #fff3}.rwg-action:focus-visible{outline-offset:2px;outline:2px solid #bfdbfe}.rwg-action--dlsite{background-color:#043aa8;background-image:linear-gradient(#2580ff 0%,#0759e6 64%,#043aa8 100%)}.rwg-action--asmrone{background-color:#159f96;background-image:linear-gradient(#35cfc4,#159f96)}.rwg-action.is-disabled{cursor:not-allowed;background-color:#ffffff0f;background-image:none;border-color:#0000;box-shadow:inset 0 0 0 1px #ffffff0a;color:#ffffff57!important}.rwg-root a.rwg-action.is-disabled{color:#ffffff57!important}.rwg-action.is-disabled>strong{text-shadow:none}.rwg-loading{gap:10px;display:grid}.rwg-skeleton{background:linear-gradient(90deg,#ffffff0d,#ffffff29,#ffffff0d) 0 0/200% 100%;border-radius:7px;min-height:18px;animation:1.3s infinite rwg-shimmer;display:block}.rwg-work--loading{min-height:0}.rwg-skeleton--cover{aspect-ratio:4/3;border-radius:10px;width:100%;min-height:0}.rwg-skeleton--action{border-radius:10px;width:100%;min-height:36px}.rwg-skeleton-title{gap:6px;margin:2px 0 7px;display:grid}.rwg-skeleton--title{width:100%;min-height:19px}.rwg-skeleton--title-short{width:72%}.rwg-skeleton--circle{width:34%;min-height:13px;margin-bottom:8px}.rwg-skeleton-badges{flex-wrap:wrap;gap:5px;display:flex}.rwg-skeleton--badge,.rwg-skeleton--badge-narrow,.rwg-skeleton--badge-wide,.rwg-skeleton--chip{min-height:21px}.rwg-skeleton--badge{width:62px}.rwg-skeleton--badge-narrow{width:46px}.rwg-skeleton--badge-wide{width:86px}.rwg-skeleton-info{grid-template-columns:36px minmax(0,1fr);align-items:start;gap:4px 8px;margin-top:8px;display:grid}.rwg-skeleton--label{width:36px;min-height:12px}.rwg-skeleton--chip{width:64px}.rwg-skeleton--chip-wide{width:94px}.rwg-status{text-align:center;color:#cbd5e1;gap:6px;padding:18px;display:grid}.rwg-status small{color:#fca5a5}.rwg-root .rwg-fab{z-index:2147483647;border:1px solid var(--rwg-border);color:#f8fafc;cursor:grab;opacity:.78;touch-action:none;-webkit-user-select:none;user-select:none;min-height:58px;font:inherit;background:#1e1e1ef5;border-radius:30px;align-items:center;gap:10px;padding:8px 16px 8px 10px;line-height:1;transition:opacity .18s,box-shadow .18s;display:flex;box-shadow:0 8px 24px #00000059;left:var(--rwg-fab-left,auto)!important;right:var(--rwg-fab-right,24px)!important;top:var(--rwg-fab-top,auto)!important;bottom:var(--rwg-fab-bottom,24px)!important;position:fixed!important;transform:none!important}.rwg-root .rwg-fab:hover{opacity:1;box-shadow:0 10px 28px #0006;transform:none!important}.rwg-root .rwg-fab.is-dragging{cursor:grabbing;transform:none!important}.rwg-fab__brand{object-fit:cover;border-radius:8px;width:34px;height:34px;display:block;box-shadow:0 2px 7px #7c3aed52}.rwg-fab__sources{align-items:center;gap:6px;display:flex}.rwg-fab__source{box-sizing:border-box;letter-spacing:-.025em;white-space:nowrap;border:1px solid;border-radius:999px;align-items:center;gap:4px;min-height:27px;padding:4px 8px;font-family:Arial Rounded MT Bold,Trebuchet MS,Avenir Next Rounded,ui-rounded,Segoe UI,sans-serif;font-size:11px;font-weight:900;line-height:1;display:inline-flex}.rwg-fab__source>strong{color:inherit;font:inherit}.rwg-fab__source--southplus{color:#c4b5fd;background:#8b5cf61f;border-color:#a78bfa4d}.rwg-fab__source--asmrone{color:#5eead4;background:#14b8a61a;border-color:#2dd4bf47}.rwg-fab__status-dot{background:#5eead4;border-radius:50%;width:6px;height:6px;box-shadow:0 0 0 2px #2dd4bf26}.rwg-fab.is-loading{animation:1.2s infinite rwg-pulse}.rwg-resource-panel{gap:14px;display:grid}.rwg-provider-heading{justify-content:space-between;align-items:center;gap:10px;margin:4px 0 0;display:flex}.rwg-provider-heading h3{color:#c4b5fd;min-width:0;margin:0}.rwg-provider-heading--asmrone{border-bottom:1px solid #ffffff14;margin-top:0;padding-bottom:9px}.rwg-provider-heading--asmrone h3{color:#5eead4;letter-spacing:-.025em;font-family:Arial Rounded MT Bold,Trebuchet MS,Segoe UI,sans-serif;font-size:18px}.rwg-provider-controls{align-items:center;gap:7px;display:flex}.rwg-provider-control{appearance:none;box-sizing:border-box;color:#ddd6fe;cursor:pointer;background:#7c3aed2e;border:1px solid #a78bfa7a;border-radius:9px;flex:none;place-items:center;width:32px;height:32px;padding:0;transition:background-color .16s,border-color .16s,color .16s;display:grid;text-decoration:none!important}.rwg-provider-control .rwg-external-icon{width:17px;height:17px}.rwg-provider-link{background:#14b8a629;border-color:#2dd4bf66;color:#99f6e4!important}.rwg-provider-link:hover{background:#14b8a65c;border-color:#5eead4c7;color:#fff!important}.rwg-provider-retry:hover{color:#fff;background:#7c3aed61;border-color:#c4b5fdc7}.rwg-provider-control:focus-visible{outline-offset:2px;outline:2px solid #c4b5fd}.rwg-provider-retry svg{fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;width:16px;height:16px}.rwg-retry{appearance:none;color:#ede9fe;font:inherit;cursor:pointer;background:#7c3aed3d;border:1px solid #a78bfa99;border-radius:999px;justify-self:center;padding:8px 16px}.rwg-retry:hover{background:#7c3aed66}.rwg-results{overscroll-behavior:contain;scrollbar-gutter:stable;gap:8px;max-height:300px;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.rwg-results a{background:#ffffff0d;border:1px solid #ffffff1a;border-radius:8px;gap:4px;padding:10px 38px 10px 12px;display:grid;position:relative;color:#e5e7eb!important;text-decoration:none!important}.rwg-results a:hover{background:#ffffff1a;border-color:#a78bfa}.rwg-results small{color:#9ca3af}.rwg-results .rwg-external-icon{color:#a78bfa;opacity:.72;width:16px;height:16px;position:absolute;top:11px;right:11px}.rwg-results a:hover .rwg-external-icon{color:#ddd6fe;opacity:1}.rwg-rj-link{cursor:pointer;background:#f472b60f;border-bottom:1px dashed #f472b6bf;border-radius:3px;margin:0 1px;padding:1px 3px;color:#f472b6!important}.rwg-rj-link:hover{background:#f472b629}.rwg-southplus-mobile-layout .threadlist,.rwg-southplus-mobile-layout .threadlist>ul,.rwg-southplus-mobile-layout .threadlist .card_wall,.rwg-southplus-mobile-layout .threadlist .card-body,.rwg-southplus-mobile-layout .threadlist .card-title{box-sizing:border-box;min-width:0;max-width:100%}.rwg-southplus-mobile-layout .threadlist .card-title{overflow-wrap:anywhere;word-break:normal;text-overflow:ellipsis;-webkit-line-clamp:4;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.rwg-southplus-mobile-layout .threadlist .card-title *{max-width:100%;overflow-wrap:inherit;word-break:inherit;white-space:normal!important}@keyframes rwg-in{0%{opacity:0;transform:scale(.97)}}@keyframes rwg-sheet-in{0%{opacity:0;translate:0 100%}to{opacity:1;translate:0}}@keyframes rwg-out{0%{opacity:1}to{opacity:0;transform:scale(.97)}}@keyframes rwg-sheet-out{0%{opacity:1;translate:0}to{opacity:0;translate:0 100%}}@keyframes rwg-shimmer{to{background-position:-200% 0}}@keyframes rwg-pulse{50%{opacity:.5}}@media (prefers-reduced-motion:reduce){.rwg-popup,.rwg-fab,.rwg-action,.rwg-retry,.rwg-provider-control,.rwg-skeleton{transition:none!important;animation:none!important}}.rwg-popup.rwg-popup--mobile{contain:layout paint style;transform:scale(var(--rwg-popup-inverse-scale,1));transform-origin:0 100%;border-radius:20px 20px 0 0;padding:14px;animation:.18s ease-out rwg-sheet-in;width:var(--rwg-popup-width,100vw)!important;max-width:var(--rwg-popup-width,100vw)!important;left:var(--rwg-popup-left,0)!important;top:auto!important;right:auto!important;bottom:var(--rwg-popup-bottom,0)!important;max-height:var(--rwg-popup-max-height,calc(100dvh - 12px))!important;margin:0!important}.rwg-popup.rwg-popup--closing{pointer-events:none;animation:.16s ease-in forwards rwg-out}.rwg-popup.rwg-popup--mobile.rwg-popup--closing{animation:.18s ease-in forwards rwg-sheet-out}.rwg-popup.rwg-popup--mobile.rwg-popup--work{border-radius:20px 20px 0 0;padding:13px;max-height:var(--rwg-work-popup-max-height,50dvh)!important}.rwg-popup--mobile.rwg-popup--work .rwg-popup__header{gap:10px;margin-bottom:9px;padding-bottom:6px;font-size:15px}.rwg-popup--mobile.rwg-popup--work .rwg-popup__close{width:30px;height:30px;font-size:20px}.rwg-popup--mobile .rwg-work__hero{grid-template-columns:clamp(116px,34vw,136px) minmax(0,1fr);gap:11px}.rwg-popup--mobile .rwg-work__cover-column{gap:8px}.rwg-popup--mobile .rwg-work__title{-webkit-line-clamp:3;margin-bottom:4px;font-size:15px;line-height:1.25}.rwg-popup--mobile .rwg-work__circle{margin-bottom:6px;font-size:12px;line-height:1.25}.rwg-popup--mobile.rwg-popup--work .rwg-badges{gap:4px}.rwg-popup--mobile.rwg-popup--work .rwg-badge{border-radius:5px;padding:3px 6px;font-size:11px;line-height:1.25}.rwg-popup--mobile.rwg-popup--work .rwg-info{gap:3px 6px;margin-top:6px}.rwg-popup--mobile.rwg-popup--work .rwg-info>strong{font-size:11px;line-height:1.7}.rwg-popup--mobile .rwg-skeleton--action{border-radius:9px;min-height:44px}.rwg-popup--mobile .rwg-skeleton-title{gap:5px;margin-top:1px;margin-bottom:6px}.rwg-popup--mobile .rwg-skeleton--title{min-height:17px}.rwg-popup--mobile.rwg-popup--work .rwg-skeleton--circle{min-height:12px;margin-bottom:6px}.rwg-popup--mobile.rwg-popup--work .rwg-skeleton-badges{gap:4px}.rwg-popup--mobile.rwg-popup--work .rwg-skeleton--badge,.rwg-popup--mobile.rwg-popup--work .rwg-skeleton--badge-narrow,.rwg-popup--mobile.rwg-popup--work .rwg-skeleton--badge-wide,.rwg-popup--mobile.rwg-popup--work .rwg-skeleton--chip{min-height:20px}.rwg-popup--mobile.rwg-popup--work .rwg-skeleton-info{gap:3px 6px;margin-top:6px}.rwg-popup--mobile.rwg-popup--work .rwg-actions{gap:6px}.rwg-popup--mobile .rwg-actions--cover .rwg-action{border-radius:9px;min-height:44px;padding:8px 10px;font-size:12px}.rwg-popup--mobile .rwg-provider-control{border-radius:10px;width:36px;height:36px}.rwg-popup--mobile .rwg-results{max-height:min(300px,48dvh)}@media (width<=768px){.rwg-root .rwg-fab{border-radius:27px;min-height:52px;padding:7px 13px 7px 8px;bottom:12px!important;right:12px!important}.rwg-root .rwg-fab[style]{right:var(--rwg-fab-right,auto)!important;bottom:var(--rwg-fab-bottom,auto)!important}.rwg-root .rwg-fab.is-open{opacity:0;pointer-events:none}.rwg-fab__brand{width:32px;height:32px}.rwg-fab__sources{gap:4px}.rwg-fab__source{min-height:25px;padding:4px 7px;font-size:10px}}");
var localizationMap = {
rj_warp_gate_title: {
zh_CN: "RJ号折跃门",
zh_TW: "RJ號折躍門",
en_US: typeof GM_info !== "undefined" ? GM_info.script.name : "RJ Warp Gate"
},
searching: {
zh_CN: "检索中...",
zh_TW: "檢索中...",
en_US: "Searching..."
},
no_resources: {
zh_CN: "暂无资源",
zh_TW: "暫無資源",
en_US: "No resources"
},
southplus_resources: {
zh_CN: "南+ 资源",
zh_TW: "南+ 資源",
en_US: "South+ Resources"
},
go_to_asmrone: {
zh_CN: "去 ASMR ONE 试听",
zh_TW: "去 ASMR ONE 試聽",
en_US: "Listen on ASMR ONE"
},
age_rating: {
zh_CN: "年龄指定",
zh_TW: "年齡指定",
en_US: "Age Rating"
},
error_form_not_found: {
zh_CN: "无法获取搜索表单,请检查是否已登录南+账号。",
zh_TW: "無法獲取搜索表單,請檢查是否已登錄南+帳號。",
en_US: "Failed to get search form. Please check if you are logged in to South+."
},
error_no_results: {
zh_CN: "未获取到结果数据,可能是账号未登录或南+开启了防DDoS验证。",
zh_TW: "未獲取到結果數據,可能是帳號未登錄或南+開啟了防DDoS驗證。",
en_US: "Failed to fetch results. You might not be logged in, or South+ DDoS protection is active."
},
search_failed: {
zh_CN: "检索失败",
zh_TW: "檢索失敗",
en_US: "Search failed"
},
search_queue_timeout: {
zh_CN: "南+ 搜索队列繁忙,请稍后手动重试",
zh_TW: "南+ 搜尋佇列繁忙,請稍後手動重試",
en_US: "The South+ search queue is busy. Please retry manually in a moment."
},
click_to_retry: {
zh_CN: "点击重试",
zh_TW: "點擊重試",
en_US: "click to retry"
},
copy_diagnostics: {
zh_CN: "复制诊断信息",
zh_TW: "複製診斷資訊",
en_US: "Copy diagnostics"
},
diagnostics_copied: {
zh_CN: "诊断信息已复制",
zh_TW: "診斷資訊已複製",
en_US: "Diagnostics copied"
},
circle_name: {
zh_CN: "社团名",
zh_TW: "社團名",
en_US: "Circle Name"
},
click_to_copy: {
zh_CN: "点击复制",
zh_TW: "點擊複製",
en_US: "Click to copy"
},
click_to_copy_title: {
zh_CN: "点击复制标题",
zh_TW: "點擊複製標題",
en_US: "Click to copy title"
},
dl_count: {
zh_CN: "销量",
zh_TW: "銷量",
en_US: "Sales"
},
file_size: {
zh_CN: "文件容量",
zh_TW: "檔案容量",
en_US: "File Size"
},
genre: {
zh_CN: "分类",
zh_TW: "分類",
en_US: "Genre"
},
illustration: {
zh_CN: "插画",
zh_TW: "插圖",
en_US: "Illustration"
},
language_arabic: {
zh_CN: "阿拉伯语",
zh_TW: "阿拉伯語",
en_US: "Arabic"
},
language_english: {
zh_CN: "英文",
zh_TW: "英文",
en_US: "English"
},
language_english_abbr: {
zh_CN: "英",
zh_TW: "英",
en_US: "EN"
},
language_finnish: {
zh_CN: "芬兰语",
zh_TW: "芬蘭語",
en_US: "Finnish"
},
language_french: {
zh_CN: "法语",
zh_TW: "法語",
en_US: "French"
},
language_french_abbr: {
zh_CN: "法",
zh_TW: "法",
en_US: "FR"
},
language_german: {
zh_CN: "德语",
zh_TW: "德語",
en_US: "German"
},
language_german_abbr: {
zh_CN: "德",
zh_TW: "德",
en_US: "DE"
},
language_italian: {
zh_CN: "意大利语",
zh_TW: "義大利語",
en_US: "Italian"
},
language_italian_abbr: {
zh_CN: "意",
zh_TW: "意",
en_US: "IT"
},
language_japanese: {
zh_CN: "日文",
zh_TW: "日文",
en_US: "Japanese"
},
language_japanese_abbr: {
zh_CN: "日",
zh_TW: "日",
en_US: "JP"
},
language_korean: {
zh_CN: "韩语",
zh_TW: "韓語",
en_US: "Korean"
},
language_korean_abbr: {
zh_CN: "韩",
zh_TW: "韩",
en_US: "KO"
},
language_polish: {
zh_CN: "波兰语",
zh_TW: "波蘭語",
en_US: "Polish"
},
language_portuguese: {
zh_CN: "葡萄牙语",
zh_TW: "葡萄牙語",
en_US: "Portuguese"
},
language_portuguese_abbr: {
zh_CN: "葡",
zh_TW: "葡",
en_US: "PT"
},
language_russian: {
zh_CN: "俄语",
zh_TW: "俄語",
en_US: "Russian"
},
language_simplified_chinese: {
zh_CN: "简体中文",
zh_TW: "簡體中文",
en_US: "Simplified Chinese"
},
language_simplified_chinese_abbr: {
zh_CN: "简中",
zh_TW: "簡中",
en_US: "ZH"
},
language_spanish: {
zh_CN: "西班牙语",
zh_TW: "西班牙語",
en_US: "Spanish"
},
language_spanish_abbr: {
zh_CN: "西",
zh_TW: "西",
en_US: "ES"
},
language_thai: {
zh_CN: "泰语",
zh_TW: "泰語",
en_US: "Thai"
},
language_thai_abbr: {
zh_CN: "泰",
zh_TW: "泰",
en_US: "TH"
},
language_traditional_chinese: {
zh_CN: "繁体中文",
zh_TW: "繁體中文",
en_US: "Traditional Chinese"
},
language_traditional_chinese_abbr: {
zh_CN: "繁中",
zh_TW: "繁中",
en_US: "TW"
},
language_vietnamese: {
zh_CN: "越南语",
zh_TW: "越南語",
en_US: "Vietnamese"
},
language_vietnamese_abbr: {
zh_CN: "越",
zh_TW: "越",
en_US: "VN"
},
release_date: {
zh_CN: "发售日",
zh_TW: "發售日",
en_US: "Release Date"
},
scenario: {
zh_CN: "剧情",
zh_TW: "劇情",
en_US: "Scenario"
},
tag_aig: {
zh_CN: "AI生成",
zh_TW: "AI生成",
en_US: "AI Gen"
},
tag_aip: {
zh_CN: "AI部分使用",
zh_TW: "AI部分使用",
en_US: "AI Partial"
},
voice_actor: {
zh_CN: "声优",
zh_TW: "聲優",
en_US: "Voice Actor"
},
work_type_comic: {
zh_CN: "漫画",
zh_TW: "漫畫",
en_US: "Manga"
},
work_type_game: {
zh_CN: "游戏",
zh_TW: "遊戲",
en_US: "Game"
},
work_type_illustration: {
zh_CN: "CG・插画",
zh_TW: "CG・插畫",
en_US: "CG + Illustrations"
},
work_type_music: {
zh_CN: "音乐",
zh_TW: "音樂",
en_US: "Music"
},
work_type_novel: {
zh_CN: "小说",
zh_TW: "小說",
en_US: "Novel"
},
work_type_other: {
zh_CN: "其他",
zh_TW: "其他",
en_US: "Miscellaneous"
},
work_type_tool: {
zh_CN: "工具/装饰",
zh_TW: "工具/配件",
en_US: "Tools / Accessories"
},
work_type_video: {
zh_CN: "视频",
zh_TW: "影片",
en_US: "Video"
},
work_type_voice: {
zh_CN: "音声・ASMR",
zh_TW: "聲音作品・ASMR",
en_US: "Voice / ASMR"
},
work_type_voice_comic: {
zh_CN: "音声漫画",
zh_TW: "有聲漫畫",
en_US: "Voiced Comics"
},
switch_to_light_mode: {
zh_CN: "☀️ 切换至白天模式",
zh_TW: "☀️ 切換至白天模式",
en_US: "☀️ Switch to Light Mode"
},
switch_to_dark_mode: {
zh_CN: "🌙 切换至夜间模式",
zh_TW: "🌙 切換至夜間模式",
en_US: "🌙 Switch to Dark Mode"
}
};
function getLocale() {
const lang = navigator.language.toLowerCase();
if (!lang.includes("zh")) return "en_US";
return lang.includes("cn") || lang.includes("sg") || lang === "zh" ? "zh_CN" : "zh_TW";
}
function localize(key) {
return localizationMap[key][getLocale()];
}
var STORAGE_KEY = "sp_dark_mode_enabled";
var menuCommandId = null;
var styleElement = null;
var metaElement = null;
var darkThemeCss = `
/* 1. Global dark background */
html.rj-warp-gate-dark-mode,
html.rj-warp-gate-dark-mode body,
html.rj-warp-gate-dark-mode #wrap,
html.rj-warp-gate-dark-mode .wrap,
html.rj-warp-gate-dark-mode #main,
html.rj-warp-gate-dark-mode .main,
html.rj-warp-gate-dark-mode .main-wrap,
html.rj-warp-gate-dark-mode #header,
html.rj-warp-gate-dark-mode .header,
html.rj-warp-gate-dark-mode #u-wrap2 {
background: #121212 !important;
background-color: #121212 !important;
background-image: none !important;
color: #d1d5db !important;
}
/* 2. Darken main forum tables and containers */
html.rj-warp-gate-dark-mode .t_table,
html.rj-warp-gate-dark-mode .t_table td,
html.rj-warp-gate-dark-mode .t_table th,
html.rj-warp-gate-dark-mode .f_one,
html.rj-warp-gate-dark-mode .f_two,
html.rj-warp-gate-dark-mode .t_one,
html.rj-warp-gate-dark-mode .t_two,
html.rj-warp-gate-dark-mode .r_one,
html.rj-warp-gate-dark-mode .r_two,
html.rj-warp-gate-dark-mode .tr3,
html.rj-warp-gate-dark-mode .tr3 td,
html.rj-warp-gate-dark-mode .tr2,
html.rj-warp-gate-dark-mode .tr2 td,
html.rj-warp-gate-dark-mode .tr1,
html.rj-warp-gate-dark-mode .tr1 td,
html.rj-warp-gate-dark-mode .tpc_content,
html.rj-warp-gate-dark-mode .read_t,
html.rj-warp-gate-dark-mode .read_m,
html.rj-warp-gate-dark-mode .t5,
html.rj-warp-gate-dark-mode #u-sidebar,
html.rj-warp-gate-dark-mode #u-content,
html.rj-warp-gate-dark-mode #u-contentmain,
html.rj-warp-gate-dark-mode #u-contentside,
html.rj-warp-gate-dark-mode .u-table,
html.rj-warp-gate-dark-mode .u-table td,
html.rj-warp-gate-dark-mode .u-table th,
html.rj-warp-gate-dark-mode .bgA,
html.rj-warp-gate-dark-mode #u-top,
html.rj-warp-gate-dark-mode #u-top-nav,
html.rj-warp-gate-dark-mode .gonggao,
html.rj-warp-gate-dark-mode #cate_thread,
html.rj-warp-gate-dark-mode .dcsns-li,
html.rj-warp-gate-dark-mode .dcsns-li .inner,
html.rj-warp-gate-dark-mode .section-text {
background: #1e1e1e !important;
background-color: #1e1e1e !important;
border-color: #333 !important;
color: #e5e7eb !important;
}
/* 3. Slightly lighter headers and title bars for depth */
html.rj-warp-gate-dark-mode .h,
html.rj-warp-gate-dark-mode .h td,
html.rj-warp-gate-dark-mode .h th,
html.rj-warp-gate-dark-mode .h b,
html.rj-warp-gate-dark-mode .h span,
html.rj-warp-gate-dark-mode #u-top-nav ul.b li,
html.rj-warp-gate-dark-mode .pages,
html.rj-warp-gate-dark-mode .pagesone,
html.rj-warp-gate-dark-mode .u-h1,
html.rj-warp-gate-dark-mode .u-h5,
html.rj-warp-gate-dark-mode .gongul,
html.rj-warp-gate-dark-mode .gongul li {
background: #2a2a2a !important;
background-color: #2a2a2a !important;
border-color: #444 !important;
color: #f3f4f6 !important;
}
/* 4. Adjust link colors for dark background */
html.rj-warp-gate-dark-mode a {
color: #60a5fa !important;
}
html.rj-warp-gate-dark-mode a:hover {
color: #93c5fd !important;
}
/* 5. Fix inline white backgrounds and black text often included in user posts */
html.rj-warp-gate-dark-mode .tpc_content font[color="#000000"],
html.rj-warp-gate-dark-mode .tpc_content font[color="black"],
html.rj-warp-gate-dark-mode .tpc_content [style*="color: #000"],
html.rj-warp-gate-dark-mode .tpc_content [style*="color: black"],
html.rj-warp-gate-dark-mode .tpc_content [style*="color:#000"] {
color: #d1d5db !important;
}
/* 6. Fix inline white backgrounds and various white blocks (like purchase boxes, quote boxes) */
html.rj-warp-gate-dark-mode [style*="background-color: #fff"],
html.rj-warp-gate-dark-mode [style*="background-color:#fff"],
html.rj-warp-gate-dark-mode [style*="background-color: white"],
html.rj-warp-gate-dark-mode [style*="background-color:#ffffff"],
html.rj-warp-gate-dark-mode [style*="background: #fff"],
html.rj-warp-gate-dark-mode [style*="background: white"],
html.rj-warp-gate-dark-mode [style*="background:#fff"],
html.rj-warp-gate-dark-mode [style*="background-color: grey"],
html.rj-warp-gate-dark-mode [bgcolor="#ffffff"],
html.rj-warp-gate-dark-mode [bgcolor="#fff"],
html.rj-warp-gate-dark-mode blockquote,
html.rj-warp-gate-dark-mode .blockquote,
html.rj-warp-gate-dark-mode .quote,
html.rj-warp-gate-dark-mode .section-intro {
background: #2d2d2d !important;
background-color: #2d2d2d !important;
border-color: #444 !important;
color: #d1d5db !important;
}
/* 7. Darken input fields and textareas */
html.rj-warp-gate-dark-mode input[type="text"],
html.rj-warp-gate-dark-mode input[type="password"],
html.rj-warp-gate-dark-mode textarea,
html.rj-warp-gate-dark-mode select {
background: #2a2a2a !important;
background-color: #2a2a2a !important;
color: #e5e7eb !important;
border: 1px solid #444 !important;
}
/* 8. Fix harsh pure blue fonts to a bright blue suitable for dark backgrounds */
html.rj-warp-gate-dark-mode font[color="#0000FF" i],
html.rj-warp-gate-dark-mode font[color="blue" i],
html.rj-warp-gate-dark-mode [style*="color: #0000FF" i],
html.rj-warp-gate-dark-mode [style*="color:#0000FF" i],
html.rj-warp-gate-dark-mode [style*="color: blue" i],
html.rj-warp-gate-dark-mode [style*="color:blue" i] {
color: #60a5fa !important;
}
`;
function initThemeManager() {
const hostname = window.location.hostname;
if (!hostname.includes("south-plus") && !hostname.includes("spring-plus") && !hostname.includes("level-plus") && !hostname.includes("imoutolove")) return;
if (GM_getValue(STORAGE_KEY, true)) applyDarkMode();
registerMenu();
}
function registerMenu() {
if (menuCommandId !== null && typeof GM_unregisterMenuCommand !== "undefined") GM_unregisterMenuCommand(menuCommandId);
const menuTitle = GM_getValue(STORAGE_KEY, true) ? localize("switch_to_light_mode") : localize("switch_to_dark_mode");
if (typeof GM_registerMenuCommand !== "undefined") menuCommandId = GM_registerMenuCommand(menuTitle, () => {
toggleDarkMode();
});
}
function toggleDarkMode() {
const newState = !GM_getValue(STORAGE_KEY, true);
GM_setValue(STORAGE_KEY, newState);
if (newState) applyDarkMode();
else removeDarkMode();
registerMenu();
}
function applyDarkMode() {
if (document.documentElement) document.documentElement.classList.add("rj-warp-gate-dark-mode");
const insertElements = () => {
if (!styleElement) {
styleElement = document.createElement("style");
styleElement.textContent = darkThemeCss;
const target = document.head || document.documentElement;
if (target) target.appendChild(styleElement);
}
if (!metaElement) {
metaElement = document.createElement("meta");
metaElement.name = "darkreader-lock";
const target = document.head || document.documentElement;
if (target) target.appendChild(metaElement);
}
};
if (document.head || document.documentElement) insertElements();
else document.addEventListener("DOMContentLoaded", () => {
document.documentElement?.classList.add("rj-warp-gate-dark-mode");
insertElements();
}, { once: true });
}
function removeDarkMode() {
if (document.documentElement) document.documentElement.classList.remove("rj-warp-gate-dark-mode");
if (styleElement) {
styleElement.remove();
styleElement = null;
}
if (metaElement) {
metaElement.remove();
metaElement = null;
}
}
var n, l$1, u$2, i$2, r$1, o$2, e$1, f$2, c$1, a$1, s$1, h$1, p$1, v$1, d$1 = {}, w$1 = [], _ = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i, g = Array.isArray;
function m$1(n, l) {
for (var u in l) n[u] = l[u];
return n;
}
function b(n) {
n && n.parentNode && n.parentNode.removeChild(n);
}
function k$1(l, u, t) {
var i, r, o, e = {};
for (o in u) "key" == o ? i = u[o] : "ref" == o ? r = u[o] : e[o] = u[o];
if (arguments.length > 2 && (e.children = arguments.length > 3 ? n.call(arguments, 2) : t), "function" == typeof l && null != l.defaultProps) for (o in l.defaultProps) void 0 === e[o] && (e[o] = l.defaultProps[o]);
return x(l, e, i, r, null);
}
function x(n, t, i, r, o) {
var e = {
type: n,
props: t,
key: i,
ref: r,
__k: null,
__: null,
__b: 0,
__e: null,
__c: null,
constructor: void 0,
__v: null == o ? ++u$2 : o,
__i: -1,
__u: 0
};
return null == o && null != l$1.vnode && l$1.vnode(e), e;
}
function S(n) {
return n.children;
}
function C$1(n, l) {
this.props = n, this.context = l;
}
function $(n, l) {
if (null == l) return n.__ ? $(n.__, n.__i + 1) : null;
for (var u; l < n.__k.length; l++) if (null != (u = n.__k[l]) && null != u.__e) return u.__e;
return "function" == typeof n.type ? $(n) : null;
}
function I(n) {
if (n.__P && n.__d) {
var u = n.__v, t = u.__e, i = [], r = [], o = m$1({}, u);
o.__v = u.__v + 1, l$1.vnode && l$1.vnode(o), q(n.__P, o, u, n.__n, n.__P.namespaceURI, 32 & u.__u ? [t] : null, i, null == t ? $(u) : t, !!(32 & u.__u), r), o.__v = u.__v, o.__.__k[o.__i] = o, D$1(i, o, r), u.__e = u.__ = null, o.__e != t && P(o);
}
}
function P(n) {
if (null != (n = n.__) && null != n.__c) return n.__e = n.__c.base = null, n.__k.some(function(l) {
if (null != l && null != l.__e) return n.__e = n.__c.base = l.__e;
}), P(n);
}
function A$1(n) {
(!n.__d && (n.__d = !0) && i$2.push(n) && !H.__r++ || r$1 != l$1.debounceRendering) && ((r$1 = l$1.debounceRendering) || o$2)(H);
}
function H() {
try {
for (var n, l = 1; i$2.length;) i$2.length > l && i$2.sort(e$1), n = i$2.shift(), l = i$2.length, I(n);
} finally {
i$2.length = H.__r = 0;
}
}
function L(n, l, u, t, i, r, o, e, f, c, a) {
var s, h, p, v, y, _, g, m = t && t.__k || w$1, b = l.length;
for (f = T$1(u, l, m, f, b), s = 0; s < b; s++) null != (p = u.__k[s]) && (h = -1 != p.__i && m[p.__i] || d$1, p.__i = s, _ = q(n, p, h, i, r, o, e, f, c, a), v = p.__e, p.ref && h.ref != p.ref && (h.ref && J(h.ref, null, p), a.push(p.ref, p.__c || v, p)), null == y && null != v && (y = v), (g = !!(4 & p.__u)) || h.__k === p.__k ? (f = j$1(p, f, n, g), g && h.__e && (h.__e = null)) : "function" == typeof p.type && void 0 !== _ ? f = _ : v && (f = v.nextSibling), p.__u &= -7);
return u.__e = y, f;
}
function T$1(n, l, u, t, i) {
var r, o, e, f, c, a = u.length, s = a, h = 0;
for (n.__k = new Array(i), r = 0; r < i; r++) null != (o = l[r]) && "boolean" != typeof o && "function" != typeof o ? ("string" == typeof o || "number" == typeof o || "bigint" == typeof o || o.constructor == String ? o = n.__k[r] = x(null, o, null, null, null) : g(o) ? o = n.__k[r] = x(S, { children: o }, null, null, null) : void 0 === o.constructor && o.__b > 0 ? o = n.__k[r] = x(o.type, o.props, o.key, o.ref ? o.ref : null, o.__v) : n.__k[r] = o, f = r + h, o.__ = n, o.__b = n.__b + 1, e = null, -1 != (c = o.__i = O(o, u, f, s)) && (s--, (e = u[c]) && (e.__u |= 2)), null == e || null == e.__v ? (-1 == c && (i > a ? h-- : i < a && h++), "function" != typeof o.type && (o.__u |= 4)) : c != f && (c == f - 1 ? h-- : c == f + 1 ? h++ : (c > f ? h-- : h++, o.__u |= 4))) : n.__k[r] = null;
if (s) for (r = 0; r < a; r++) null != (e = u[r]) && 0 == (2 & e.__u) && (e.__e == t && (t = $(e)), K(e, e));
return t;
}
function j$1(n, l, u, t) {
var i, r;
if ("function" == typeof n.type) {
for (i = n.__k, r = 0; i && r < i.length; r++) i[r] && (i[r].__ = n, l = j$1(i[r], l, u, t));
return l;
}
n.__e != l && (t && (l && n.type && !l.parentNode && (l = $(n)), u.insertBefore(n.__e, l || null)), l = n.__e);
do
l = l && l.nextSibling;
while (null != l && 8 == l.nodeType);
return l;
}
function O(n, l, u, t) {
var i, r, o, e = n.key, f = n.type, c = l[u], a = null != c && 0 == (2 & c.__u);
if (null === c && null == e || a && e == c.key && f == c.type) return u;
if (t > (a ? 1 : 0)) {
for (i = u - 1, r = u + 1; i >= 0 || r < l.length;) if (null != (c = l[o = i >= 0 ? i-- : r++]) && 0 == (2 & c.__u) && e == c.key && f == c.type) return o;
}
return -1;
}
function z$1(n, l, u) {
"-" == l[0] ? n.setProperty(l, null == u ? "" : u) : n[l] = null == u ? "" : "number" != typeof u || _.test(l) ? u : u + "px";
}
function N(n, l, u, t, i) {
var r, o;
n: if ("style" == l) if ("string" == typeof u) n.style.cssText = u;
else {
if ("string" == typeof t && (n.style.cssText = t = ""), t) for (l in t) u && l in u || z$1(n.style, l, "");
if (u) for (l in u) t && u[l] == t[l] || z$1(n.style, l, u[l]);
}
else if ("o" == l[0] && "n" == l[1]) r = l != (l = l.replace(s$1, "$1")), o = l.toLowerCase(), l = o in n || "onFocusOut" == l || "onFocusIn" == l ? o.slice(2) : l.slice(2), n.l || (n.l = {}), n.l[l + r] = u, u ? t ? u[a$1] = t[a$1] : (u[a$1] = h$1, n.addEventListener(l, r ? v$1 : p$1, r)) : n.removeEventListener(l, r ? v$1 : p$1, r);
else {
if ("http://www.w3.org/2000/svg" == i) l = l.replace(/xlink(H|:h)/, "h").replace(/sName$/, "s");
else if ("width" != l && "height" != l && "href" != l && "list" != l && "form" != l && "tabIndex" != l && "download" != l && "rowSpan" != l && "colSpan" != l && "role" != l && "popover" != l && l in n) try {
n[l] = null == u ? "" : u;
break n;
} catch (n) {}
"function" == typeof u || (null == u || !1 === u && "-" != l[4] ? n.removeAttribute(l) : n.setAttribute(l, "popover" == l && 1 == u ? "" : u));
}
}
function V(n) {
return function(u) {
if (this.l) {
var t = this.l[u.type + n];
if (null == u[c$1]) u[c$1] = h$1++;
else if (u[c$1] < t[a$1]) return;
return t(l$1.event ? l$1.event(u) : u);
}
};
}
function q(n, u, t, i, r, o, e, f, c, a) {
var s, h, p, v, y, d, _, k, x, M, $, I, P, A, H, T, j = u.type;
if (void 0 !== u.constructor) return null;
128 & t.__u && (c = !!(32 & t.__u), o = [f = u.__e = t.__e]), (s = l$1.__b) && s(u);
n: if ("function" == typeof j) {
h = e.length;
try {
if (x = u.props, M = j.prototype && j.prototype.render, $ = (s = j.contextType) && i[s.__c], I = s ? $ ? $.props.value : s.__ : i, t.__c ? k = (p = u.__c = t.__c).__ = p.__E : (M ? u.__c = p = new j(x, I) : (u.__c = p = new C$1(x, I), p.constructor = j, p.render = Q), $ && $.sub(p), p.state || (p.state = {}), p.__n = i, v = p.__d = !0, p.__h = [], p._sb = []), M && null == p.__s && (p.__s = p.state), M && null != j.getDerivedStateFromProps && (p.__s == p.state && (p.__s = m$1({}, p.__s)), m$1(p.__s, j.getDerivedStateFromProps(x, p.__s))), y = p.props, d = p.state, p.__v = u, v) M && null == j.getDerivedStateFromProps && null != p.componentWillMount && p.componentWillMount(), M && null != p.componentDidMount && p.__h.push(p.componentDidMount);
else {
if (M && null == j.getDerivedStateFromProps && x !== y && null != p.componentWillReceiveProps && p.componentWillReceiveProps(x, I), u.__v == t.__v || !p.__e && null != p.shouldComponentUpdate && !1 === p.shouldComponentUpdate(x, p.__s, I)) {
u.__v != t.__v && (p.props = x, p.state = p.__s, p.__d = !1), u.__e = t.__e, u.__k = t.__k, u.__k.some(function(n) {
n && (n.__ = u);
}), w$1.push.apply(p.__h, p._sb), p._sb = [], p.__h.length && e.push(p);
break n;
}
null != p.componentWillUpdate && p.componentWillUpdate(x, p.__s, I), M && null != p.componentDidUpdate && p.__h.push(function() {
p.componentDidUpdate(y, d, _);
});
}
if (p.context = I, p.props = x, p.__P = n, p.__e = !1, P = l$1.__r, A = 0, M) p.state = p.__s, p.__d = !1, P && P(u), s = p.render(p.props, p.state, p.context), w$1.push.apply(p.__h, p._sb), p._sb = [];
else do
p.__d = !1, P && P(u), s = p.render(p.props, p.state, p.context), p.state = p.__s;
while (p.__d && ++A < 25);
p.state = p.__s, null != p.getChildContext && (i = m$1(m$1({}, i), p.getChildContext())), M && !v && null != p.getSnapshotBeforeUpdate && (_ = p.getSnapshotBeforeUpdate(y, d)), H = null != s && s.type === S && null == s.key ? E(s.props.children) : s, f = L(n, g(H) ? H : [H], u, t, i, r, o, e, f, c, a), p.base = u.__e, u.__u &= -161, p.__h.length && e.push(p), k && (p.__E = p.__ = null);
} catch (n) {
if (e.length = h, u.__v = null, c || null != o) {
if (n.then) {
for (u.__u |= c ? 160 : 128; f && 8 == f.nodeType && f.nextSibling;) f = f.nextSibling;
null != o && (o[o.indexOf(f)] = null), u.__e = f;
} else if (null != o) for (T = o.length; T--;) b(o[T]);
} else u.__e = t.__e;
u.__k ??= t.__k || [], n.then || B$1(u), l$1.__e(n, u, t);
}
} else null == o && u.__v == t.__v ? (u.__k = t.__k, u.__e = t.__e) : f = u.__e = G(t.__e, u, t, i, r, o, e, c, a);
return (s = l$1.diffed) && s(u), 128 & u.__u ? void 0 : f;
}
function B$1(n) {
n && (n.__c && (n.__c.__e = !0), n.__k && n.__k.some(B$1));
}
function D$1(n, u, t) {
for (var i = 0; i < t.length; i++) J(t[i], t[++i], t[++i]);
l$1.__c && l$1.__c(u, n), n.some(function(u) {
try {
n = u.__h, u.__h = [], n.some(function(n) {
n.call(u);
});
} catch (n) {
l$1.__e(n, u.__v);
}
});
}
function E(n) {
return "object" != typeof n || null == n || n.__b > 0 ? n : g(n) ? n.map(E) : void 0 !== n.constructor ? null : m$1({}, n);
}
function G(u, t, i, r, o, e, f, c, a) {
var s, h, p, v, y, w, _, m = i.props || d$1, k = t.props, x = t.type;
if ("svg" == x ? o = "http://www.w3.org/2000/svg" : "math" == x ? o = "http://www.w3.org/1998/Math/MathML" : o || (o = "http://www.w3.org/1999/xhtml"), null != e) {
for (s = 0; s < e.length; s++) if ((y = e[s]) && "setAttribute" in y == !!x && (x ? y.localName == x : 3 == y.nodeType)) {
u = y, e[s] = null;
break;
}
}
if (null == u) {
if (null == x) return document.createTextNode(k);
u = document.createElementNS(o, x, k.is && k), c && (l$1.__m && l$1.__m(t, e), c = !1), e = null;
}
if (null == x) m === k || c && u.data == k || (u.data = k);
else {
if (e = "textarea" == x && null != k.defaultValue ? null : e && n.call(u.childNodes), !c && null != e) for (m = {}, s = 0; s < u.attributes.length; s++) m[(y = u.attributes[s]).name] = y.value;
for (s in m) y = m[s], "dangerouslySetInnerHTML" == s ? p = y : "children" == s || s in k || "value" == s && "defaultValue" in k || "checked" == s && "defaultChecked" in k || N(u, s, null, y, o);
for (s in k) y = k[s], "children" == s ? v = y : "dangerouslySetInnerHTML" == s ? h = y : "value" == s ? w = y : "checked" == s ? _ = y : c && "function" != typeof y || m[s] === y || N(u, s, y, m[s], o);
if (h) c || p && (h.__html == p.__html || h.__html == u.innerHTML) || (u.innerHTML = h.__html), t.__k = [];
else if (p && (u.innerHTML = ""), L("template" == t.type ? u.content : u, g(v) ? v : [v], t, i, r, "foreignObject" == x ? "http://www.w3.org/1999/xhtml" : o, e, f, e ? e[0] : i.__k && $(i, 0), c, a), null != e) for (s = e.length; s--;) b(e[s]);
c && "textarea" != x || (s = "value", "progress" == x && null == w ? u.removeAttribute("value") : null != w && (w !== u[s] || "progress" == x && !w || "option" == x && w != m[s]) && N(u, s, w, m[s], o), s = "checked", null != _ && _ != u[s] && N(u, s, _, m[s], o));
}
return u;
}
function J(n, u, t) {
try {
if ("function" == typeof n) {
var i = "function" == typeof n.__u;
i && n.__u(), i && null == u || (n.__u = n(u));
} else n.current = u;
} catch (n) {
l$1.__e(n, t);
}
}
function K(n, u, t) {
var i, r;
if (l$1.unmount && l$1.unmount(n), (i = n.ref) && (i.current && i.current != n.__e || J(i, null, u)), null != (i = n.__c)) {
if (i.componentWillUnmount) try {
i.componentWillUnmount();
} catch (n) {
l$1.__e(n, u);
}
i.base = i.__P = i.__n = null;
}
if (i = n.__k) for (r = 0; r < i.length; r++) i[r] && K(i[r], u, t || "function" != typeof n.type);
t || b(n.__e), n.__c = n.__ = n.__e = void 0;
}
function Q(n, l, u) {
return this.constructor(n, u);
}
function R(u, t, i) {
var r, o, e, f;
t == document && (t = document.documentElement), l$1.__ && l$1.__(u, t), o = (r = "function" == typeof i) ? null : i && i.__k || t.__k, e = [], f = [], q(t, u = (!r && i || t).__k = k$1(S, null, [u]), o || d$1, d$1, t.namespaceURI, !r && i ? [i] : o ? null : t.firstChild ? n.call(t.childNodes) : null, e, !r && i ? i : o ? o.__e : t.firstChild, r, f), D$1(e, u, f), u.props.children = null;
}
n = w$1.slice, l$1 = { __e: function(n, l, u, t) {
for (var i, r, o; l = l.__;) if ((i = l.__c) && !i.__) try {
if ((r = i.constructor) && null != r.getDerivedStateFromError && (i.setState(r.getDerivedStateFromError(n)), o = i.__d), null != i.componentDidCatch && (i.componentDidCatch(n, t || {}), o = i.__d), o) return i.__E = i;
} catch (l) {
n = l;
}
throw n;
} }, u$2 = 0, C$1.prototype.setState = function(n, l) {
var u = null != this.__s && this.__s != this.state ? this.__s : this.__s = m$1({}, this.state);
"function" == typeof n && (n = n(m$1({}, u), this.props)), n && m$1(u, n), null != n && this.__v && (l && this._sb.push(l), A$1(this));
}, C$1.prototype.forceUpdate = function(n) {
this.__v && (this.__e = !0, n && this.__h.push(n), A$1(this));
}, C$1.prototype.render = S, i$2 = [], o$2 = "function" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, e$1 = function(n, l) {
return n.__v.__b - l.__v.__b;
}, H.__r = 0, f$2 = Math.random().toString(8), c$1 = "__d" + f$2, a$1 = "__a" + f$2, s$1 = /(PointerCapture)$|Capture$/i, h$1 = 0, p$1 = V(!1), v$1 = V(!0);
var AppError = class extends Error {
kind;
status;
retryAfterMs;
cause;
constructor(kind, message, options = {}) {
super(message);
this.name = "AppError";
this.kind = kind;
this.status = options.status;
this.retryAfterMs = options.retryAfterMs;
this.cause = options.cause;
}
};
function toAppError(error, fallback = "Request failed") {
if (error instanceof AppError) return error;
if (error instanceof Error) return new AppError("network", error.message, { cause: error });
return new AppError("network", fallback, { cause: error });
}
var ResourceController = class {
providers;
state = {
code: null,
work: { status: "idle" },
resources: {}
};
listeners = new Set();
generation = 0;
abortController = null;
constructor(providers) {
this.providers = providers;
}
getSnapshot = () => this.state;
subscribe = (listener) => {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
};
isBusy() {
return this.state.work.status === "loading" || Object.values(this.state.resources).some((state) => state.status === "loading");
}
getResourceProviderName(id) {
return this.providers.resource(id)?.displayName ?? id;
}
async load(code, options = {}) {
if (this.state.code === code && this.isBusy()) return;
const sameCode = this.state.code === code;
const generation = ++this.generation;
this.abortController?.abort();
this.abortController = new AbortController();
const signal = this.abortController.signal;
const loadWork = options.work !== false;
const loadResources = options.resources !== false;
const allowed = options.resourceProviderIds ? new Set(options.resourceProviderIds) : null;
const excluded = new Set(options.excludeResourceProviderIds ?? []);
const resourceProviders = loadResources ? this.providers.resourcesFor(code).filter((provider) => (!allowed || allowed.has(provider.id)) && !excluded.has(provider.id)) : [];
const resources = sameCode ? { ...this.state.resources } : {};
for (const provider of resourceProviders) {
const previous = sameCode ? this.state.resources[provider.id] : void 0;
resources[provider.id] = previous?.status === "success" ? {
status: "loading",
previous: previous.data
} : { status: "loading" };
}
const previousWork = sameCode && this.state.work.status === "success" ? this.state.work.data : void 0;
this.replace({
code,
work: loadWork ? previousWork ? {
status: "loading",
previous: previousWork
} : { status: "loading" } : sameCode ? this.state.work : { status: "idle" },
resources
});
const tasks = [];
if (loadWork) {
const metadata = this.providers.metadataFor(code)[0];
if (!metadata) this.patchIfCurrent(generation, code, { work: { status: "empty" } });
else tasks.push(metadata.getWork(code, {
force: options.force,
signal,
priority: "interaction"
}).then((data) => this.patchIfCurrent(generation, code, { work: {
status: "success",
data
} })).catch((error) => {
if (signal.aborted) return;
this.patchIfCurrent(generation, code, { work: {
status: "error",
error: toAppError(error)
} });
}));
}
for (const provider of resourceProviders) tasks.push(provider.search(code, {
force: options.force,
signal,
priority: "interaction"
}).then((data) => this.patchResourceIfCurrent(generation, code, provider.id, data.length ? {
status: "success",
data
} : { status: "empty" })).catch((error) => {
if (signal.aborted) return;
this.patchResourceIfCurrent(generation, code, provider.id, {
status: "error",
error: toAppError(error)
});
}));
await Promise.allSettled(tasks);
}
refresh(code, options = {}) {
return this.load(code, {
...options,
force: true
});
}
dispose() {
this.generation += 1;
this.abortController?.abort();
this.abortController = null;
this.listeners.clear();
}
patchIfCurrent(generation, code, patch) {
if (generation !== this.generation || this.state.code !== code) return;
this.replace({
...this.state,
...patch
});
}
patchResourceIfCurrent(generation, code, providerId, resourceState) {
if (generation !== this.generation || this.state.code !== code) return;
this.replace({
...this.state,
resources: {
...this.state.resources,
[providerId]: resourceState
}
});
}
replace(state) {
this.state = state;
for (const listener of this.listeners) listener();
}
};
function defaultRequester() {
if (typeof GM_xmlhttpRequest === "function") return {
requester: GM_xmlhttpRequest,
transport: "legacy"
};
if (typeof GM !== "undefined" && GM?.xmlHttpRequest) return {
requester: GM.xmlHttpRequest.bind(GM),
transport: "modern"
};
throw new AppError("network", "GM.xmlHttpRequest is unavailable");
}
var GmHttpClient = class {
requester;
defaultTimeoutMs;
diagnostics;
constructor(requester, defaultTimeoutMs = 1e4, diagnostics) {
this.requester = requester;
this.defaultTimeoutMs = defaultTimeoutMs;
this.diagnostics = diagnostics;
}
request(request) {
const method = request.method ?? "GET";
const startedAt = Date.now();
let activeRequester;
let transport;
if (request.signal?.aborted) {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "abort",
method,
url: request.url,
transport: this.requester ? "injected" : "unavailable",
durationMs: 0
});
return Promise.reject(new AppError("aborted", "Request aborted"));
}
try {
if (this.requester) {
activeRequester = this.requester;
transport = "injected";
} else ({requester: activeRequester, transport} = defaultRequester());
} catch (cause) {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "start",
method,
url: request.url,
transport: "unavailable"
});
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "error",
method,
url: request.url,
transport: "unavailable",
durationMs: Date.now() - startedAt,
cause
});
return Promise.reject(cause instanceof AppError ? cause : new AppError("network", "Unable to start network request", { cause }));
}
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "start",
method,
url: request.url,
transport
});
return new Promise((resolve, reject) => {
let settled = false;
let handle;
const finish = (callback) => {
if (settled) return;
settled = true;
request.signal?.removeEventListener("abort", onSignalAbort);
callback();
};
const onSignalAbort = () => {
if (handle) handle.abort();
finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "abort",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt
});
reject(new AppError("aborted", "Request aborted"));
});
};
try {
handle = activeRequester({
method,
url: request.url,
headers: request.headers,
data: request.body,
timeout: request.timeoutMs ?? this.defaultTimeoutMs,
anonymous: request.anonymous,
onload: (response) => finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "load",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt,
status: response.status,
statusText: response.statusText
});
resolve({
status: response.status,
statusText: response.statusText,
text: response.responseText,
finalUrl: response.finalUrl || request.url,
headers: response.responseHeaders ?? ""
});
}),
onerror: (cause) => finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "error",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt,
cause
});
reject(new AppError("network", "Network request failed", { cause }));
}),
ontimeout: (cause) => finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "timeout",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt,
cause
});
reject(new AppError("timeout", "Network request timed out", { cause }));
}),
onabort: (cause) => finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "abort",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt,
cause
});
reject(new AppError("aborted", "Network request aborted", { cause }));
})
});
if (!settled) request.signal?.addEventListener("abort", onSignalAbort, { once: true });
} catch (cause) {
finish(() => {
this.diagnostics?.record({
label: request.diagnosticLabel,
phase: "error",
method,
url: request.url,
transport,
durationMs: Date.now() - startedAt,
cause
});
reject(cause instanceof AppError ? cause : new AppError("network", "Unable to start network request", { cause }));
});
}
});
}
};
function assertHttpSuccess(response) {
if (response.status >= 200 && response.status < 300) return response;
if (response.status === 404) throw new AppError("not-found", "Resource not found", { status: 404 });
if (response.status === 401 || response.status === 403) throw new AppError("unauthorized", "Request is not authorized", { status: response.status });
if (response.status === 429) throw new AppError("rate-limited", "Request was rate limited", { status: 429 });
throw new AppError("http", `Unexpected HTTP status ${response.status}`, { status: response.status });
}
function parseJson(response) {
try {
return JSON.parse(response.text);
} catch (cause) {
throw new AppError("parse", "Response was not valid JSON", { cause });
}
}
var GmKeyValueStorage = class {
async get(key, fallback) {
if (typeof GM_getValue !== "function") return fallback;
return await GM_getValue(key, fallback);
}
async set(key, value) {
if (typeof GM_setValue === "function") await GM_setValue(key, value);
}
async delete(key) {
if (typeof GM_deleteValue === "function") await GM_deleteValue(key);
}
async list() {
if (typeof GM_listValues !== "function") return [];
return await GM_listValues();
}
};
var SCHEMA_VERSION = 1;
var QueryCache = class {
storage;
now;
memory = new Map();
constructor(storage, now = Date.now) {
this.storage = storage;
this.now = now;
}
async get(key) {
const memoryEntry = this.memory.get(key);
if (memoryEntry) return this.readEntry(key, memoryEntry);
const stored = await this.storage.get(key, null);
if (!stored) return void 0;
const value = await this.readEntry(key, stored);
if (value !== void 0) this.memory.set(key, stored);
return value;
}
async set(key, value, ttlMs) {
const createdAt = this.now();
const entry = {
schemaVersion: SCHEMA_VERSION,
createdAt,
expiresAt: createdAt + Math.max(0, ttlMs),
value
};
this.memory.set(key, entry);
await this.storage.set(key, entry);
}
async delete(key) {
this.memory.delete(key);
await this.storage.delete(key);
}
hasMemory(key) {
const entry = this.memory.get(key);
return Boolean(entry && entry.schemaVersion === SCHEMA_VERSION && entry.expiresAt > this.now());
}
async readEntry(key, entry) {
const now = this.now();
if (!(entry.schemaVersion !== SCHEMA_VERSION || !Number.isFinite(entry.expiresAt) || entry.expiresAt <= now || entry.createdAt > now + 5 * 6e4)) return entry.value;
await this.delete(key);
}
};
var QueryClient = class {
cache;
inFlight = new Map();
constructor(cache) {
this.cache = cache;
}
async query(key, loader, options) {
const active = this.inFlight.get(key);
if (active) return active;
if (!options.force) {
let cached;
try {
cached = await this.cache.get(key);
} catch {}
if (cached !== void 0) return cached;
const afterCache = this.inFlight.get(key);
if (afterCache) return afterCache;
}
const promise = loader().then(async (value) => {
const ttlMs = typeof options.ttlMs === "function" ? options.ttlMs(value) : options.ttlMs;
if (options.persist !== false) try {
await this.cache.set(key, value, ttlMs);
} catch {}
return value;
}).finally(() => {
if (this.inFlight.get(key) === promise) this.inFlight.delete(key);
});
this.inFlight.set(key, promise);
return promise;
}
isInFlight(key) {
return this.inFlight.has(key);
}
hasMemory(key) {
return this.cache.hasMemory(key);
}
async hasCached(key) {
if (this.inFlight.has(key) || this.cache.hasMemory(key)) return true;
try {
return await this.cache.get(key) !== void 0 || this.inFlight.has(key);
} catch {
return this.inFlight.has(key);
}
}
async invalidate(key) {
await this.cache.delete(key);
}
};
var SouthPlusRateLimiter = class {
storage;
now;
sleep;
createOwner;
leaseKey = "rwg:v1:southplus-search-lease";
waiterPrefix = "rwg:v1:southplus-search-waiter:";
constructor(storage, now = Date.now, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), createOwner = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`) {
this.storage = storage;
this.now = now;
this.sleep = sleep;
this.createOwner = createOwner;
}
async acquire(options = {}) {
const cooldownMs = options.cooldownMs ?? 10500;
const maxWaitMs = options.maxWaitMs ?? 6e4;
const pollMs = options.pollMs ?? 500;
const heartbeatMs = Math.max(5e3, pollMs * 4);
const owner = this.createOwner();
const waiterKey = `${this.waiterPrefix}${owner}`;
const enqueuedAt = this.now();
const deadline = enqueuedAt + maxWaitMs;
try {
while (this.now() <= deadline) {
if (options.signal?.aborted) throw new AppError("aborted", "South Plus search queue was cancelled");
const now = this.now();
await this.storage.set(waiterKey, {
owner,
enqueuedAt,
expiresAt: now + heartbeatMs
});
const waiters = await this.readWaiters(now);
const lease = await this.storage.get(this.leaseKey, null);
const leaseAvailable = !lease || lease.expiresAt <= now || lease.acquiredAt > now + 6e4;
if (waiters[0]?.owner === owner && leaseAvailable) {
if (options.signal?.aborted) throw new AppError("aborted", "South Plus search queue was cancelled");
const candidate = {
owner,
acquiredAt: now,
expiresAt: now + cooldownMs
};
await this.storage.set(this.leaseKey, candidate);
if ((await this.storage.get(this.leaseKey, null))?.owner === owner) return;
}
const remaining = deadline - this.now();
if (remaining <= 0) break;
await this.sleep(Math.min(pollMs, remaining));
}
} finally {
await this.storage.delete(waiterKey);
}
throw new AppError("rate-limited", "Timed out waiting for South Plus search lease", { retryAfterMs: cooldownMs });
}
async readWaiters(now) {
const keys = (await this.storage.list()).filter((key) => key.startsWith(this.waiterPrefix));
const entries = await Promise.all(keys.map(async (key) => ({
key,
value: await this.storage.get(key, null)
})));
const waiters = [];
for (const entry of entries) {
const waiter = entry.value;
if (!waiter || typeof waiter.owner !== "string" || typeof waiter.enqueuedAt !== "number" || typeof waiter.expiresAt !== "number" || waiter.expiresAt <= now || waiter.enqueuedAt > now + 6e4 || !entry.key.endsWith(waiter.owner)) {
await this.storage.delete(entry.key);
continue;
}
waiters.push(waiter);
}
return waiters.sort((left, right) => left.enqueuedAt - right.enqueuedAt || left.owner.localeCompare(right.owner));
}
};
var encode = (part) => encodeURIComponent(part);
var cacheKeys = {
work: (rjCode, locale = "default") => `rwg:v1:work:${rjCode}:${encode(locale)}`,
southPlus: (rjCode, domain) => `rwg:v1:southplus:${rjCode}:${encode(domain)}`,
asmrOne: (rjCode) => `rwg:v1:asmrone:${rjCode}`,
linkage: (rjCode, languages) => `rwg:v1:linkage:${rjCode}:${encode([...languages].sort().join(","))}`
};
function normalizeDateOnly(value) {
const trimmed = value?.trim();
if (!trimmed) return null;
const match = trimmed.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/u);
if (!match) return trimmed;
const [, year, month, day] = match;
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
}
var record = (value) => value !== null && typeof value === "object" ? value : {};
var text = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
var number = (value) => {
const parsed = typeof value === "number" ? value : Number(value);
return Number.isFinite(parsed) ? parsed : null;
};
var array = (value) => Array.isArray(value) ? value : [];
var workTypes = {
SOU: [0, "Voice / ASMR"],
ACN: [1, "Game"],
QIZ: [1, "Game"],
ADV: [1, "Game"],
RPG: [1, "Game"],
TBL: [1, "Game"],
DNV: [1, "Game"],
SLN: [1, "Game"],
TYP: [1, "Game"],
STG: [1, "Game"],
PZL: [1, "Game"],
ETC: [1, "Game"],
MNG: [2, "Manga"],
SCM: [2, "Manga"],
WBT: [2, "Manga"],
ICG: [3, "CG + Illustrations"],
NRE: [4, "Novel"],
KSV: [4, "Novel"],
MOV: [5, "Video"],
MUS: [6, "Music"],
TOL: [7, "Tools / Accessories"],
IMT: [7, "Tools / Accessories"],
AMT: [7, "Tools / Accessories"],
VCM: [8, "Voiced Comics"],
ET3: [9, "Miscellaneous"]
};
function imageUrl(value) {
const url = text(record(value).url) ?? text(value);
if (!url || url.includes("no_img_main.gif")) return null;
return url.startsWith("//") ? `https:${url}` : url;
}
function names(value) {
return array(value).map((item) => text(record(item).name) ?? text(item)).filter((item) => Boolean(item));
}
function parseDlsiteApi2Payload(payload, rjCode) {
const item = Array.isArray(payload) ? record(payload[0]) : record(payload);
const title = text(item.work_name) ?? text(item.name);
if (!title) throw new AppError("invalid-data", "DLsite response did not contain a work title");
const creators = record(item.creaters ?? item.creators);
const workTypeCode = text(item.work_type) ?? "";
const [workTypeId, workType] = workTypes[workTypeCode] ?? [-1, workTypeCode || null];
const age = number(item.age_category);
return {
rjCode,
title,
imageUrl: imageUrl(item.image_main ?? item.work_image),
circle: text(item.maker_name),
sales: number(item.dl_count),
ratingAverage: number(item.rate_average_2dp),
ratingCount: number(item.rate_count),
releaseDate: normalizeDateOnly(text(item.regist_date)),
ageRating: age === 1 ? "All" : age === 2 ? "R15" : age === 3 ? "R18" : null,
workType,
workTypeId,
fileSize: number(item.contents_file_size),
voiceActors: names(creators.voice_by),
genres: names(item.genres),
isGirls: text(item.site_id) === "girls" || String(item.options ?? "").includes("OTM")
};
}
function mergeDlsiteApi1(summary, payload) {
const data = record(record(payload)[summary.rjCode] ?? payload);
return {
...summary,
imageUrl: summary.imageUrl ?? imageUrl(data.work_image),
circle: summary.circle ?? text(data.maker_name),
sales: summary.sales ?? number(data.dl_count),
ratingAverage: summary.ratingAverage ?? number(data.rate_average_2dp),
ratingCount: summary.ratingCount ?? number(data.rate_count),
releaseDate: summary.releaseDate ?? normalizeDateOnly(text(data.regist_date)),
fileSize: summary.fileSize ?? number(data.contents_file_size)
};
}
function parseDlsiteApi1Payload(payload, rjCode) {
return parseDlsiteApi2Payload(record(record(payload)[rjCode] ?? payload), rjCode);
}
function formatFileSize(bytes) {
if (bytes === null || bytes < 0) return null;
const units = [
"B",
"KB",
"MB",
"GB",
"TB"
];
let value = bytes;
let index = 0;
while (value >= 1024 && index < units.length - 1) {
value /= 1024;
index += 1;
}
return `${Math.round(value * 100) / 100}${units[index]}`;
}
var DlsiteMetadataProvider = class {
http;
queries;
id = "dlsite";
constructor(http, queries) {
this.http = http;
this.queries = queries;
}
supports(_code) {
return true;
}
async getWork(code, context = {}) {
const key = cacheKeys.work(code);
return this.queries.query(key, async () => {
let api2 = null;
try {
api2 = await this.getApi2(code, context);
} catch (error) {
if (error instanceof AppError && error.kind === "aborted") throw error;
}
try {
const payload = await this.getApi1(code, context);
return api2 ? mergeDlsiteApi1(api2, payload) : parseDlsiteApi1Payload(payload, code);
} catch (error) {
if (error instanceof AppError && error.kind === "aborted") throw error;
if (api2) return api2;
throw error;
}
}, {
force: context.force,
ttlMs: 360 * 6e4
});
}
async prefetchWork(code, context = {}) {
await this.getApi2(code, {
...context,
priority: "prefetch"
});
}
isWorkCached(code) {
return this.queries.hasCached(this.api2CacheKey(code));
}
async getApi2(code, context) {
const key = this.api2CacheKey(code);
return this.queries.query(key, async () => {
return parseDlsiteApi2Payload(parseJson(assertHttpSuccess(await this.http.request({
url: `https://www.dlsite.com/maniax/api/=/product.json?workno=${code}`,
timeoutMs: context.priority === "prefetch" ? 6e3 : 1e4,
signal: context.signal
}))), code);
}, {
force: context.force,
ttlMs: 1440 * 6e4
});
}
async getApi1(code, context) {
return parseJson(assertHttpSuccess(await this.http.request({
url: `https://www.dlsite.com/maniax/product/info/ajax?product_id=${code}&cdn_cache_min=1`,
timeoutMs: context.priority === "prefetch" ? 6e3 : 1e4,
signal: context.signal
})));
}
api2CacheKey(code) {
return `${cacheKeys.work(code)}:api2`;
}
};
var AsmrOneResourceProvider = class {
http;
queries;
id = "asmr-one";
displayName = "ASMR ONE";
constructor(http, queries) {
this.http = http;
this.queries = queries;
}
supports(code) {
return code.startsWith("RJ");
}
search(code, context = {}) {
return this.queries.query(cacheKeys.asmrOne(code), async () => {
const response = await this.http.request({
url: `https://api.asmr-200.com/api/work/${code.slice(2)}`,
headers: { Referer: "https://www.asmr.one/" },
timeoutMs: 8e3,
signal: context.signal
});
if (response.status === 404) return [];
if (response.status < 200 || response.status >= 300) throw new AppError("http", `ASMR ONE returned ${response.status}`, { status: response.status });
return [{
id: code,
providerId: this.id,
title: code,
url: `https://www.asmr.one/work/${code}`
}];
}, {
force: context.force,
ttlMs: (value) => Array.isArray(value) && value.length === 0 ? 360 * 6e4 : 1440 * 6e4
});
}
};
function parseSouthPlusSearchForm(html, code) {
const doc = new DOMParser().parseFromString(html, "text/html");
const form = doc.querySelector("form[action*=\"search.php\"], form[name=\"schform\"]") ?? doc.forms.item(0);
if (!form || !form.querySelector("[name=\"keyword\"]")) {
const loginPage = Boolean(doc.querySelector("form[action*=\"login.php\"], input[name=\"pwuser\"], a[href^=\"login.php\"]"));
throw new AppError(loginPage ? "unauthorized" : "invalid-data", loginPage ? "South Plus login cookie was not available to the cross-site request" : "South Plus search form was not available; the forum layout may have changed");
}
const params = new URLSearchParams();
form.querySelectorAll("input, select, textarea").forEach((element) => {
if (!element.name) return;
if (element instanceof HTMLInputElement) {
const type = element.type.toLowerCase();
if ((type === "checkbox" || type === "radio") && !element.checked) return;
if (type === "submit" && params.has(element.name)) return;
}
if (element instanceof HTMLSelectElement) {
const option = element.selectedOptions.item(0) ?? element.options.item(0);
if (option) params.append(element.name, option.value);
return;
}
if (!params.has(element.name)) params.append(element.name, element.value);
});
params.set("keyword", code);
params.set("step", "2");
if (params.has("sch_time")) params.set("sch_time", "all");
return params.toString();
}
function parseSouthPlusResults(html, domain) {
if (html.includes("抱歉,没有找到匹配结果") || html.includes("没有查找匹配的内容")) return [];
const doc = new DOMParser().parseFromString(html, "text/html");
const error = doc.querySelector(".t .f_one b")?.textContent?.trim();
if (error && !error.includes("没有找到") && !error.includes("没有查找匹配")) throw new AppError(error.includes("上次搜索时间") || error.includes("连续两次搜索") || error.includes("不能少于") ? "rate-limited" : "invalid-data", error);
const results = new Map();
doc.querySelectorAll("a[href^=\"read.php?tid\"]").forEach((anchor) => {
const title = anchor.textContent?.trim();
const href = anchor.getAttribute("href");
const row = anchor.closest("tr");
if (!title || !href || !row) return;
const id = href.match(/tid[=-](\d+)/)?.[1] ?? href;
if (results.has(id)) return;
const authorCell = row.querySelectorAll("td, th").item(3);
const author = (authorCell?.querySelector("a"))?.textContent?.trim() ?? "";
const date = row.querySelector("a[href*=\"page-e.html\"]")?.textContent?.trim() ?? authorCell?.textContent?.replace(author, "").trim() ?? "";
results.set(id, {
id,
providerId: "southplus",
title,
url: new URL(href, `https://${domain}/`).href,
author: author.replace(/[\r\n]+/g, " ").trim(),
date: date.replace(/[\r\n]+/g, " ").trim()
});
});
if (results.size === 0) throw new AppError("invalid-data", "South Plus response did not contain search results");
return [...results.values()];
}
var SouthPlusResourceProvider = class {
http;
storage;
queries;
limiter;
id = "southplus";
displayName = "South Plus";
constructor(http, storage, queries, limiter) {
this.http = http;
this.storage = storage;
this.queries = queries;
this.limiter = limiter;
}
supports(_code) {
return true;
}
async search(code, context = {}) {
const domain = await this.storage.get("last_forum_domain", "www.south-plus.net");
const key = cacheKeys.southPlus(code, domain);
return this.queries.query(key, async () => {
await this.limiter.acquire({ signal: context.signal });
const searchUrl = `https://${domain}/search.php`;
const getResponse = assertHttpSuccess(await this.http.request({
url: searchUrl,
timeoutMs: 1e4,
signal: context.signal,
anonymous: false,
diagnosticLabel: "southplus.search-form"
}));
if (/\/login\.php(?:[?#]|$)/i.test(getResponse.finalUrl)) throw new AppError("unauthorized", `South Plus login is required on ${domain}`);
const body = parseSouthPlusSearchForm(getResponse.text, code);
const postResponse = assertHttpSuccess(await this.http.request({
method: "POST",
url: `${searchUrl}?step=2`,
body,
timeoutMs: 1e4,
signal: context.signal,
anonymous: false,
diagnosticLabel: "southplus.search-submit",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Referer: searchUrl,
Origin: `https://${domain}`
}
}));
if (/\/login\.php(?:[?#]|$)/i.test(postResponse.finalUrl)) throw new AppError("unauthorized", `South Plus login is required on ${domain}`);
return parseSouthPlusResults(postResponse.text, domain);
}, {
force: context.force,
ttlMs: (value) => Array.isArray(value) && value.length === 0 ? 30 * 6e4 : 720 * 6e4
});
}
};
var ProviderRegistry = class {
metadataProviders = new Map();
resourceProviders = new Map();
registerMetadata(provider) {
if (this.metadataProviders.has(provider.id)) throw new Error(`Metadata provider id already registered: ${provider.id}`);
this.metadataProviders.set(provider.id, provider);
return this;
}
registerResource(provider) {
if (this.resourceProviders.has(provider.id)) throw new Error(`Resource provider id already registered: ${provider.id}`);
this.resourceProviders.set(provider.id, provider);
return this;
}
metadataFor(code) {
return [...this.metadataProviders.values()].filter((provider) => provider.supports(code));
}
resourcesFor(code) {
return [...this.resourceProviders.values()].filter((provider) => provider.supports(code));
}
metadata(id) {
return this.metadataProviders.get(id);
}
resource(id) {
return this.resourceProviders.get(id);
}
};
var GmTextClipboard = class {
async writeText(value) {
if (typeof GM_setClipboard !== "function") throw new AppError("network", "GM_setClipboard is unavailable");
await GM_setClipboard(value, "text");
}
};
var SAFE_CAUSE_KEYS = [
"readyState",
"status",
"statusText",
"error",
"message"
];
var URL_PATTERN = /https?:\/\/[^\s"'<>]+/giu;
var CREDENTIAL_PATTERN = /\b(authorization|cookie|set-cookie)\s*[:=]\s*[^\s,;]+/giu;
function sanitizeUrl(value) {
try {
const url = new URL(value);
return `${url.origin}${url.pathname}`;
} catch {
return "(invalid-url)";
}
}
function sanitizeText(value) {
return value.replace(URL_PATTERN, (url) => sanitizeUrl(url)).replace(CREDENTIAL_PATTERN, "$1=[redacted]").slice(0, 160);
}
function safeCauseDetails(cause) {
if (typeof cause !== "object" || cause === null) return void 0;
const source = cause;
const details = {};
for (const key of SAFE_CAUSE_KEYS) {
const value = source[key];
if (typeof value === "string") details[key] = sanitizeText(value);
if (typeof value === "number" && Number.isFinite(value)) details[key] = value;
if (typeof value === "boolean") details[key] = value;
}
return Object.keys(details).length > 0 ? details : void 0;
}
var DiagnosticBuffer = class {
capacity;
now;
entries = [];
constructor(capacity = 12, now = () => new Date()) {
this.capacity = capacity;
this.now = now;
}
record(input) {
const causeDetails = safeCauseDetails(input.cause);
const status = input.status ?? (typeof causeDetails?.status === "number" ? causeDetails.status : void 0);
const failedResponse = input.phase === "load" && status !== void 0 && (status < 200 || status >= 300);
if (input.phase !== "error" && input.phase !== "timeout" && !failedResponse) return;
const statusText = input.statusText ?? (typeof causeDetails?.statusText === "string" ? causeDetails.statusText : void 0);
const details = causeDetails ? { ...causeDetails } : void 0;
if (details) {
delete details.status;
delete details.statusText;
}
this.entries.push({
timestamp: this.now().toISOString(),
label: sanitizeText(input.label ?? "unlabeled"),
phase: input.phase,
method: input.method,
url: sanitizeUrl(input.url),
transport: input.transport,
durationMs: input.durationMs === void 0 ? void 0 : Math.max(0, Math.round(input.durationMs)),
status,
statusText: statusText === void 0 ? void 0 : sanitizeText(statusText),
details: details && Object.keys(details).length > 0 ? details : void 0
});
if (this.entries.length > this.capacity) this.entries.splice(0, this.entries.length - this.capacity);
}
hasEntries() {
return this.entries.length > 0;
}
format() {
const lines = [
"RJ Warp Gate diagnostic log",
`Generated: ${this.now().toISOString()}`,
`Entries: ${this.entries.length}`
];
for (const entry of this.entries) {
const fields = [
`[${entry.timestamp}]`,
entry.label,
entry.phase,
entry.method,
entry.url,
`transport=${entry.transport}`
];
if (entry.durationMs !== void 0) fields.push(`durationMs=${entry.durationMs}`);
if (entry.status !== void 0) fields.push(`status=${entry.status}`);
if (entry.statusText) fields.push(`statusText=${JSON.stringify(entry.statusText)}`);
if (entry.details) fields.push(`details=${JSON.stringify(entry.details)}`);
lines.push(fields.join(" "));
}
return lines.join("\n");
}
};
var storage = new GmKeyValueStorage();
var diagnostics = new DiagnosticBuffer();
var http = new GmHttpClient(void 0, 1e4, diagnostics);
var queryClient = new QueryClient(new QueryCache(storage));
var limiter = new SouthPlusRateLimiter(storage);
var clipboard = new GmTextClipboard();
var dlsiteProvider = new DlsiteMetadataProvider(http, queryClient);
var asmrOneProvider = new AsmrOneResourceProvider(http, queryClient);
var southPlusProvider = new SouthPlusResourceProvider(http, storage, queryClient, limiter);
var providerRegistry = new ProviderRegistry().registerMetadata(dlsiteProvider).registerResource(asmrOneProvider).registerResource(southPlusProvider);
var RJ_CODE_PATTERN = /^(?:R[JE]|[VB]J)\d{6}(?:\d{2})?$/i;
var RJ_CODE_SEARCH_PATTERN = /(?:R[JE]|[VB]J)\d{6}(?:\d{2})?/gi;
function parseRjCode(input) {
const normalized = input.trim().toUpperCase();
return RJ_CODE_PATTERN.test(normalized) ? normalized : null;
}
function findRjCodes(input) {
const matches = input.match(RJ_CODE_SEARCH_PATTERN) ?? [];
const unique = new Set();
for (const match of matches) {
const code = parseRjCode(match);
if (code) unique.add(code);
}
return [...unique];
}
var t, r, u$1, i$1, o$1 = 0, f$1 = [], c = l$1, e = c.__b, a = c.__r, v = c.diffed, l = c.__c, m = c.unmount, p = c.__;
function s(n, t) {
c.__h && c.__h(r, n, o$1 || t), o$1 = 0;
var u = r.__H || (r.__H = {
__: [],
__h: []
});
return n >= u.__.length && u.__.push({}), u.__[n];
}
function d(n) {
return o$1 = 1, y(D, n);
}
function y(n, u, i) {
var o = s(t++, 2);
if (o.t = n, !o.__c && (o.__ = [i ? i(u) : D(void 0, u), function(n) {
var t = o.__N ? o.__N[0] : o.__[0], r = o.t(t, n);
t !== r && (o.__N = [r, o.__[1]], o.__c.setState({}));
}], o.__c = r, !r.__f)) {
var f = function(n, t, r) {
if (!o.__c.__H) return !0;
var u = !1, i = o.__c.props !== n;
if (o.__c.__H.__.some(function(n) {
if (n.__N) {
u = !0;
var t = n.__[0];
n.__ = n.__N, n.__N = void 0, t !== n.__[0] && (i = !0);
}
}), c) {
var f = c.call(this, n, t, r);
return u ? f || i : f;
}
return !u || i;
};
r.__f = !0;
var c = r.shouldComponentUpdate, e = r.componentWillUpdate;
r.componentWillUpdate = function(n, t, r) {
if (this.__e) {
var u = c;
c = void 0, f(n, t, r), c = u;
}
e && e.call(this, n, t, r);
}, r.shouldComponentUpdate = f;
}
return o.__N || o.__;
}
function h(n, u) {
var i = s(t++, 3);
!c.__s && C(i.__H, u) && (i.__ = n, i.u = u, r.__H.__h.push(i));
}
function A(n) {
return o$1 = 5, T(function() {
return { current: n };
}, []);
}
function T(n, r) {
var u = s(t++, 7);
return C(u.__H, r) && (u.__ = n(), u.__H = r, u.__h = n), u.__;
}
function j() {
for (var n; n = f$1.shift();) {
var t = n.__H;
if (n.__P && t) try {
t.__h.some(z), t.__h.some(B), t.__h = [];
} catch (r) {
t.__h = [], c.__e(r, n.__v);
}
}
}
c.__b = function(n) {
r = null, e && e(n);
}, c.__ = function(n, t) {
n && t.__k && t.__k.__m && (n.__m = t.__k.__m), p && p(n, t);
}, c.__r = function(n) {
a && a(n), t = 0;
var i = (r = n.__c).__H;
i && (u$1 === r ? (i.__h = [], r.__h = [], i.__.some(function(n) {
n.__N && (n.__ = n.__N), n.u = n.__N = void 0;
})) : (i.__h.some(z), i.__h.some(B), i.__h = [], t = 0)), u$1 = r;
}, c.diffed = function(n) {
v && v(n);
var t = n.__c;
t && t.__H && (t.__H.__h.length && (1 !== f$1.push(t) && i$1 === c.requestAnimationFrame || ((i$1 = c.requestAnimationFrame) || w)(j)), t.__H.__.some(function(n) {
n.u && (n.__H = n.u, n.u = void 0);
})), u$1 = r = null;
}, c.__c = function(n, t) {
t.some(function(n) {
try {
n.__h.some(z), n.__h = n.__h.filter(function(n) {
return !n.__ || B(n);
});
} catch (r) {
t.some(function(n) {
n.__h && (n.__h = []);
}), t = [], c.__e(r, n.__v);
}
}), l && l(n, t);
}, c.unmount = function(n) {
m && m(n);
var t, r = n.__c;
r && r.__H && (r.__H.__.some(function(n) {
try {
z(n);
} catch (n) {
t = n;
}
}), r.__H = void 0, t && c.__e(t, r.__v));
};
var k = "function" == typeof requestAnimationFrame;
function w(n) {
var t, r = function() {
clearTimeout(u), k && cancelAnimationFrame(t), setTimeout(n);
}, u = setTimeout(r, 35);
k && (t = requestAnimationFrame(r));
}
function z(n) {
var t = r, u = n.__c;
"function" == typeof u && (n.__c = void 0, u()), r = t;
}
function B(n) {
var t = r;
n.__c = n.__(), r = t;
}
function C(n, t) {
return !n || n.length !== t.length || t.some(function(t, r) {
return t !== n[r];
});
}
function D(n, t) {
return "function" == typeof t ? t(n) : t;
}
function useExternalStore(store) {
const [snapshot, setSnapshot] = d(store.getSnapshot());
h(() => {
setSnapshot(store.getSnapshot());
return store.subscribe(() => setSnapshot(store.getSnapshot()));
}, [store]);
return snapshot;
}
var clamp = (value, minimum, maximum) => Math.max(minimum, Math.min(value, Math.max(minimum, maximum)));
var LEGACY_WIDE_LAYOUT_RATIO = 1.25;
var hasMobileUserAgent = (userAgent, userAgentDataMobile = false) => userAgentDataMobile || /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent);
var resolveLayoutViewportHeight = (rootClientHeight, bodyIsScrollingElement, bodyClientHeight, innerHeight, innerWidth, visualWidth) => {
if (bodyIsScrollingElement) {
if ((visualWidth > 0 ? innerWidth / visualWidth : 1) >= LEGACY_WIDE_LAYOUT_RATIO) return innerHeight;
return bodyClientHeight > 0 ? bodyClientHeight : innerHeight;
}
return rootClientHeight > 0 ? rootClientHeight : innerHeight;
};
function calculatePopupViewportState(metrics) {
const compactWidth = metrics.layoutWidth <= 768 || metrics.visualWidth <= 768;
const compactLandscape = metrics.visualWidth <= 1024 && metrics.visualHeight <= 600;
const handheldScreen = metrics.coarsePointer && Math.min(metrics.screenWidth, metrics.screenHeight) <= 768;
const legacyHandheld = !metrics.hasViewportMeta && (metrics.coarsePointer || metrics.mobileUserAgent);
const mobile = compactWidth || compactLandscape || handheldScreen || legacyHandheld;
const pageScale = metrics.visualScale > 0 && metrics.visualScale < 1 ? metrics.visualScale : 1;
const visualHeight = Math.max(0, metrics.visualHeight * pageScale);
const maxHeight = Math.floor(Math.max(0, Math.min(visualHeight - 12, visualHeight * .82)));
return {
mobile,
left: Math.round(Math.max(0, metrics.visualOffsetLeft)),
bottom: Math.round(Math.max(0, metrics.layoutHeight - metrics.visualOffsetTop - metrics.visualHeight)),
width: Math.floor(Math.max(0, metrics.visualWidth * pageScale)),
maxHeight,
workMaxHeight: visualHeight <= 600 ? maxHeight : Math.floor(visualHeight * .5),
inverseScale: 1 / pageScale
};
}
function readPopupViewportState() {
const visualViewport = window.visualViewport;
const layoutHeight = resolveLayoutViewportHeight(document.documentElement.clientHeight, document.scrollingElement === document.body, document.body.clientHeight, window.innerHeight, window.innerWidth, visualViewport?.width ?? window.innerWidth);
return calculatePopupViewportState({
layoutWidth: window.innerWidth,
layoutHeight,
hasViewportMeta: Boolean(document.querySelector("meta[name=\"viewport\"]")),
mobileUserAgent: hasMobileUserAgent(navigator.userAgent, Boolean(navigator.userAgentData?.mobile)),
screenWidth: window.screen.width,
screenHeight: window.screen.height,
visualWidth: visualViewport?.width ?? window.innerWidth,
visualHeight: visualViewport?.height ?? window.innerHeight,
visualOffsetLeft: visualViewport?.offsetLeft ?? 0,
visualOffsetTop: visualViewport?.offsetTop ?? 0,
visualScale: visualViewport?.scale ?? 1,
coarsePointer: window.matchMedia?.("(pointer: coarse)").matches ?? false
});
}
function usePopupViewport() {
const [viewport, setViewport] = d(readPopupViewportState);
h(() => {
const update = () => setViewport(readPopupViewportState());
window.addEventListener("resize", update, { passive: true });
window.addEventListener("orientationchange", update, { passive: true });
window.visualViewport?.addEventListener("resize", update, { passive: true });
window.visualViewport?.addEventListener("scroll", update, { passive: true });
return () => {
window.removeEventListener("resize", update);
window.removeEventListener("orientationchange", update);
window.visualViewport?.removeEventListener("resize", update);
window.visualViewport?.removeEventListener("scroll", update);
};
}, []);
return T(() => ({
mobile: viewport.mobile,
style: viewport.mobile ? {
"--rwg-popup-left": `${viewport.left}px`,
"--rwg-popup-bottom": `${viewport.bottom}px`,
"--rwg-popup-width": `${viewport.width}px`,
"--rwg-popup-max-height": `${viewport.maxHeight}px`,
"--rwg-work-popup-max-height": `${viewport.workMaxHeight}px`,
"--rwg-popup-inverse-scale": String(viewport.inverseScale)
} : void 0
}), [viewport]);
}
function calculatePopupPosition(x, y, viewport, width = 650, height = 550, gap = 15, margin = 10) {
const popupWidth = Math.min(width, Math.max(0, viewport.width - margin * 2));
const availableRight = viewport.width - margin - x - gap;
const availableLeft = x - gap - margin;
const preferredLeft = availableRight >= popupWidth ? x + gap : availableLeft >= popupWidth ? x - gap - popupWidth : x - popupWidth / 2;
const availableBelow = Math.max(0, viewport.height - margin - y - gap);
const availableAbove = Math.max(0, y - gap - margin);
const placeBelow = availableBelow >= Math.min(height, availableAbove);
const maxHeight = Math.max(80, Math.min(height, placeBelow ? availableBelow : availableAbove));
return {
left: `${Math.round(clamp(preferredLeft, margin, viewport.width - popupWidth - margin))}px`,
maxHeight: `${Math.round(maxHeight)}px`,
...placeBelow ? { top: `${Math.round(y + gap)}px` } : { bottom: `${Math.round(viewport.height - y + gap)}px` }
};
}
function calculateAttachedPopupPosition(anchor, viewport, width = 360, preferredHeight = 520, gap = 12, margin = 12) {
const popupWidth = Math.min(width, Math.max(0, viewport.width - margin * 2));
const left = clamp(anchor.right - popupWidth, margin, viewport.width - popupWidth - margin);
const availableAbove = Math.max(0, anchor.top - gap - margin);
const availableBelow = Math.max(0, viewport.height - anchor.bottom - gap - margin);
const placeAbove = availableAbove >= Math.min(preferredHeight, availableBelow);
const maxHeight = Math.max(80, Math.min(preferredHeight, placeAbove ? availableAbove : availableBelow));
return {
left: `${Math.round(left)}px`,
width: `${Math.round(popupWidth)}px`,
maxHeight: `${Math.round(maxHeight)}px`,
...placeAbove ? { bottom: `${Math.round(viewport.height - anchor.top + gap)}px` } : { top: `${Math.round(anchor.bottom + gap)}px` }
};
}
function usePopupPosition(x, y, width = 650, height = 550) {
const [revision, rerender] = d(0);
h(() => {
const resize = () => rerender((value) => value + 1);
window.addEventListener("resize", resize, { passive: true });
return () => window.removeEventListener("resize", resize);
}, []);
return T(() => window.innerWidth <= 768 ? {} : calculatePopupPosition(x, y, {
width: window.innerWidth,
height: window.innerHeight
}, width, height), [
x,
y,
width,
height,
revision
]);
}
function useAttachedPopupPosition(elementRef, display, position) {
const [style, setStyle] = d({
right: "24px",
bottom: "88px",
width: "360px"
});
h(() => {
if (!display) return void 0;
const update = () => {
if (window.innerWidth <= 768) {
setStyle({});
return;
}
const rect = elementRef.current?.getBoundingClientRect();
if (!rect) return;
setStyle(calculateAttachedPopupPosition(rect, {
width: window.innerWidth,
height: window.innerHeight
}));
};
update();
window.addEventListener("resize", update, { passive: true });
return () => window.removeEventListener("resize", update);
}, [
display,
elementRef,
position?.x,
position?.y
]);
return style;
}
var f = 0;
Array.isArray;
function u(e, t, n, o, i, u) {
t || (t = {});
var a, c, p = t;
if ("ref" in p) for (c in p = {}, t) "ref" == c ? a = t[c] : p[c] = t[c];
var l = {
type: e,
props: p,
key: n,
ref: a,
__k: null,
__: null,
__b: 0,
__e: null,
__c: null,
constructor: void 0,
__v: --f,
__i: -1,
__u: 0,
__source: i,
__self: u
};
if ("function" == typeof e && (a = e.defaultProps)) for (c in a) void 0 === p[c] && (p[c] = a[c]);
return l$1.vnode && l$1.vnode(l), l;
}
var POPUP_EXIT_FALLBACK_MS = 220;
function PopupPanel({ display, title, theme = "default", className, position, onClose, dismissOnViewportScroll = false, onMouseEnter, onMouseLeave, children }) {
const viewport = usePopupViewport();
const [rendered, setRendered] = d(display);
const [closing, setClosing] = d(false);
h(() => {
if (display) {
setRendered(true);
setClosing(false);
return;
}
if (!rendered) return void 0;
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
setRendered(false);
setClosing(false);
return;
}
setClosing(true);
const fallback = window.setTimeout(() => {
setRendered(false);
setClosing(false);
}, POPUP_EXIT_FALLBACK_MS);
return () => window.clearTimeout(fallback);
}, [display, rendered]);
h(() => {
if (!display || !viewport.mobile || !dismissOnViewportScroll) return void 0;
const dismiss = () => onClose();
window.addEventListener("scroll", dismiss, { passive: true });
window.visualViewport?.addEventListener("scroll", dismiss, { passive: true });
return () => {
window.removeEventListener("scroll", dismiss);
window.visualViewport?.removeEventListener("scroll", dismiss);
};
}, [
dismissOnViewportScroll,
display,
onClose,
viewport.mobile
]);
if (!rendered) return null;
return u("section", {
class: `rwg-popup rwg-theme-${theme}${viewport.mobile ? " rwg-popup--mobile" : ""}${closing ? " rwg-popup--closing" : ""}${className ? ` ${className}` : ""}`,
style: viewport.mobile ? {
...position,
...viewport.style
} : position,
role: "dialog",
"aria-modal": "false",
"aria-label": title || "RJ Warp Gate",
"aria-hidden": closing,
onMouseEnter,
onMouseLeave,
onAnimationEnd: (event) => {
if (event.target !== event.currentTarget || !closing || display) return;
setRendered(false);
setClosing(false);
},
children: [u("header", {
class: "rwg-popup__header",
children: [u("strong", { children: title }), u("button", {
class: "rwg-popup__close",
type: "button",
onClick: onClose,
"aria-label": "Close",
children: "×"
})]
}), u("div", {
class: "rwg-popup__body",
children
})]
});
}
function ActionButton({ href, theme, label }) {
const disabled = !href;
const brand = label ?? (theme === "dlsite" ? "DLsite" : "ASMR.one");
return u("a", {
class: `rwg-action rwg-action--${theme}${disabled ? " is-disabled" : ""}`,
href: href ?? void 0,
target: disabled ? void 0 : "_blank",
rel: "noreferrer",
"aria-disabled": disabled,
onClick: disabled ? (event) => event.preventDefault() : void 0,
children: [u("strong", {
class: "rwg-action__brand",
children: brand
}), disabled ? u(UnavailableIcon, {}) : u(ExternalLinkIcon, {})]
});
}
function ExternalLinkIcon() {
return u("svg", {
class: "rwg-external-icon",
viewBox: "0 0 24 24",
"aria-hidden": "true",
children: u("path", { d: "M8 16 17 7M10 7h7v7" })
});
}
function UnavailableIcon() {
return u("svg", {
class: "rwg-unavailable-icon",
viewBox: "0 0 24 24",
"aria-hidden": "true",
children: [u("circle", {
cx: "12",
cy: "12",
r: "7"
}), u("path", { d: "m7 17 10-10" })]
});
}
var STORAGE_KEYS = {
desktop: "rj_warp_gate_fab_pos_desktop",
mobile: "rj_warp_gate_fab_pos_mobile_v2"
};
var DRAG_THRESHOLD = 5;
function clampFabPosition(position, viewport, element) {
return {
x: Math.max(0, Math.min(position.x, Math.max(0, viewport.width - element.width))),
y: Math.max(0, Math.min(position.y, Math.max(0, viewport.height - element.height)))
};
}
function snapFabPosition(position, viewport, element) {
const clamped = clampFabPosition(position, viewport, element);
return {
x: clamped.x + element.width / 2 < viewport.width / 2 ? 0 : Math.max(0, viewport.width - element.width),
y: clamped.y
};
}
function parseStoredPosition(value) {
let candidate = value;
if (typeof value === "string") try {
candidate = JSON.parse(value);
} catch {
return null;
}
if (!candidate || typeof candidate !== "object") return null;
const record = candidate;
return Number.isFinite(record.x) && Number.isFinite(record.y) ? {
x: Number(record.x),
y: Number(record.y)
} : null;
}
function useMobileFabPosition(storage) {
const buttonRef = A(null);
const drag = A(null);
const suppressClick = A(false);
const [mobile, setMobile] = d(() => window.innerWidth <= 768);
const mobileRef = A(mobile);
const [position, setPosition] = d(null);
const [dragging, setDragging] = d(false);
const elementSize = () => ({
width: buttonRef.current?.offsetWidth || 128,
height: buttonRef.current?.offsetHeight || 56
});
const viewportSize = () => ({
width: window.innerWidth,
height: window.innerHeight
});
const persist = (value) => {
storage.set(mobile ? STORAGE_KEYS.mobile : STORAGE_KEYS.desktop, value).catch(() => {});
};
h(() => {
let active = true;
const key = mobile ? STORAGE_KEYS.mobile : STORAGE_KEYS.desktop;
storage.get(key, null).then((stored) => {
const parsed = parseStoredPosition(stored);
if (!active) return;
setPosition(parsed ? clampFabPosition(parsed, viewportSize(), elementSize()) : null);
});
return () => {
active = false;
};
}, [mobile, storage]);
h(() => {
const resize = () => {
const nextMobile = window.innerWidth <= 768;
if (nextMobile !== mobileRef.current) {
mobileRef.current = nextMobile;
setMobile(nextMobile);
setPosition(null);
return;
}
setPosition((current) => current ? clampFabPosition(current, viewportSize(), elementSize()) : current);
};
window.addEventListener("resize", resize, { passive: true });
return () => window.removeEventListener("resize", resize);
}, []);
const onPointerDown = (event) => {
if (!event.isPrimary || event.button !== 0) return;
const rect = event.currentTarget.getBoundingClientRect();
const origin = {
x: rect.left,
y: rect.top
};
drag.current = {
pointerId: event.pointerId,
startX: event.clientX,
startY: event.clientY,
origin,
latest: origin,
moved: false
};
setDragging(true);
event.currentTarget.setPointerCapture?.(event.pointerId);
};
const onPointerMove = (event) => {
const current = drag.current;
if (!current || current.pointerId !== event.pointerId) return;
const dx = event.clientX - current.startX;
const dy = event.clientY - current.startY;
if (!current.moved && Math.hypot(dx, dy) <= DRAG_THRESHOLD) return;
current.moved = true;
event.preventDefault();
current.latest = clampFabPosition({
x: current.origin.x + dx,
y: current.origin.y + dy
}, viewportSize(), elementSize());
setPosition(current.latest);
};
const finishDrag = (event) => {
const current = drag.current;
if (!current || current.pointerId !== event.pointerId) return;
drag.current = null;
setDragging(false);
event.currentTarget.releasePointerCapture?.(event.pointerId);
suppressClick.current = current.moved;
if (!current.moved) return;
const finalPosition = mobile ? snapFabPosition(current.latest, viewportSize(), elementSize()) : clampFabPosition(current.latest, viewportSize(), elementSize());
setPosition(finalPosition);
persist(finalPosition);
};
return {
buttonRef,
style: T(() => position ? {
"--rwg-fab-left": `${position.x}px`,
"--rwg-fab-right": "auto",
"--rwg-fab-top": `${position.y}px`,
"--rwg-fab-bottom": "auto"
} : void 0, [position]),
position,
dragging,
onPointerDown,
onPointerMove,
onPointerUp: (event) => finishDrag(event),
onPointerCancel: (event) => finishDrag(event),
consumeClick() {
if (!suppressClick.current) return true;
suppressClick.current = false;
return false;
}
};
}
var icon_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='64'%20height='64'%20viewBox='0%200%2064%2064'%3e%3cdefs%3e%3clinearGradient%20id='background'%20x1='8'%20y1='6'%20x2='58'%20y2='60'%20gradientUnits='userSpaceOnUse'%3e%3cstop%20stop-color='%23ec4899'/%3e%3cstop%20offset='0.52'%20stop-color='%23a855f7'/%3e%3cstop%20offset='1'%20stop-color='%236d28d9'/%3e%3c/linearGradient%3e%3cradialGradient%20id='highlight'%20cx='0'%20cy='0'%20r='1'%20gradientTransform='translate(18%2012)%20rotate(48)%20scale(48)'%20gradientUnits='userSpaceOnUse'%3e%3cstop%20stop-color='%23fff'%20stop-opacity='0.28'/%3e%3cstop%20offset='0.58'%20stop-color='%23fff'%20stop-opacity='0'/%3e%3c/radialGradient%3e%3c/defs%3e%3crect%20width='64'%20height='64'%20rx='15'%20fill='url(%23background)'/%3e%3crect%20width='64'%20height='64'%20rx='15'%20fill='url(%23highlight)'/%3e%3crect%20x='1'%20y='1'%20width='62'%20height='62'%20rx='14'%20fill='none'%20stroke='%23fff'%20stroke-opacity='0.18'%20stroke-width='2'/%3e%3cg%20transform='translate(4%200)%20skewX(-6)'%20fill='none'%20stroke='%23fff'%20stroke-width='6'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20d='M14.5%2046V18h10.2c6.6%200%2010.4%203.1%2010.4%208.2s-3.8%208.2-10.4%208.2H14.5M25.8%2034.4%2036.8%2046'/%3e%3cpath%20d='M49.5%2018v17.1c0%207.3-3.8%2011-10%2011-3.8%200-6.6-1.8-8-5'/%3e%3c/g%3e%3c/svg%3e";
function DlsiteApp({ code, controller, storage, diagnostics, clipboard }) {
const state = useExternalStore(controller);
const [open, setOpen] = d(false);
const [diagnosticsCopied, setDiagnosticsCopied] = d(false);
const fab = useMobileFabPosition(storage);
const popupPosition = useAttachedPopupPosition(fab.buttonRef, open, fab.position);
const hideTimer = A(null);
const copyFeedbackTimer = A(null);
const cancelHide = () => {
if (hideTimer.current !== null) window.clearTimeout(hideTimer.current);
hideTimer.current = null;
};
const startHide = () => {
if (window.innerWidth <= 768) return;
cancelHide();
hideTimer.current = window.setTimeout(() => setOpen(false), 300);
};
h(() => {
controller.load(code, { work: false });
}, [code, controller]);
h(() => () => {
cancelHide();
if (copyFeedbackTimer.current !== null) window.clearTimeout(copyFeedbackTimer.current);
}, []);
const asmr = state.resources["asmr-one"];
const resourceEntries = Object.entries(state.resources);
const loading = resourceEntries.some(([, resource]) => resource.status === "loading");
const resourceGroups = resourceEntries.filter(([providerId]) => providerId !== "asmr-one").map(([providerId, resource]) => ({
providerId,
displayName: controller.getResourceProviderName(providerId),
state: resource,
results: resource.status === "success" ? resource.data : []
}));
const results = resourceGroups.flatMap((group) => group.results);
const asmrUrl = asmr?.status === "success" ? asmr.data[0]?.url : null;
const allDone = resourceEntries.length > 0 && resourceEntries.every(([, resource]) => resource.status !== "loading");
const hasDiagnosticError = resourceEntries.some(([, resource]) => resource.status === "error" && resource.error.kind !== "rate-limited" && resource.error.kind !== "aborted");
const asmrRetryable = allDone && (asmr?.status === "error" || asmr?.status === "empty");
const accessibleLabel = loading ? `RJ Warp Gate · ${localize("searching")}` : `RJ Warp Gate · SP ${results.length}`;
const click = () => {
if (!fab.consumeClick()) return;
if (window.innerWidth > 768) {
setOpen(true);
return;
}
setOpen((value) => !value);
};
const retry = (providerId) => {
cancelHide();
setOpen(true);
controller.refresh(code, {
work: false,
resourceProviderIds: [providerId]
});
};
const copyDiagnostics = async () => {
try {
await clipboard.writeText(diagnostics.format());
setDiagnosticsCopied(true);
if (copyFeedbackTimer.current !== null) window.clearTimeout(copyFeedbackTimer.current);
copyFeedbackTimer.current = window.setTimeout(() => setDiagnosticsCopied(false), 1200);
} catch {
setDiagnosticsCopied(false);
}
};
return u(S, { children: [u("button", {
ref: fab.buttonRef,
class: `rwg-fab${loading ? " is-loading" : ""}${fab.dragging ? " is-dragging" : ""}${open ? " is-open" : ""}`,
style: fab.style,
type: "button",
onPointerDown: fab.onPointerDown,
onPointerMove: fab.onPointerMove,
onPointerUp: fab.onPointerUp,
onPointerCancel: fab.onPointerCancel,
onMouseEnter: () => {
if (window.innerWidth > 768) {
cancelHide();
setOpen(true);
}
},
onMouseLeave: startHide,
onClick: click,
"aria-expanded": open,
"aria-label": accessibleLabel,
children: [u("img", {
class: "rwg-fab__brand",
src: icon_default,
alt: "",
"aria-hidden": "true"
}), u("span", {
class: "rwg-fab__sources",
"aria-hidden": "true",
children: [u("span", {
class: "rwg-fab__source rwg-fab__source--southplus",
children: [u("span", { children: "SP" }), u("strong", { children: loading ? "…" : results.length })]
}), asmrUrl && u("span", {
class: "rwg-fab__source rwg-fab__source--asmrone",
children: [u("span", { children: "ASMR" }), u("i", { class: "rwg-fab__status-dot" })]
})]
})]
}), u(PopupPanel, {
display: open,
title: "Search Result",
position: popupPosition,
onClose: () => setOpen(false),
onMouseEnter: cancelHide,
onMouseLeave: startHide,
children: u("div", {
class: "rwg-resource-panel",
children: [
asmr?.status === "loading" && u("span", { class: "rwg-skeleton" }),
u("div", {
class: "rwg-provider-heading rwg-provider-heading--asmrone",
children: [u("h3", { children: "ASMR.one" }), u("div", {
class: "rwg-provider-controls",
children: [asmrUrl && u("a", {
class: "rwg-provider-control rwg-provider-link",
href: asmrUrl,
target: "_blank",
rel: "noreferrer",
"aria-label": localize("go_to_asmrone"),
title: localize("go_to_asmrone"),
children: u(ExternalLinkIcon, {})
}), asmrRetryable && u(ProviderRetryButton, {
providerName: "ASMR ONE",
onClick: () => retry("asmr-one")
})]
})]
}),
asmr?.status === "error" && u("div", {
class: "rwg-status",
children: [u("strong", { children: ["ASMR ONE · ", localize("search_failed")] }), u("small", { children: asmr.error.message })]
}),
resourceGroups.map((group) => u("section", { children: [
u("div", {
class: "rwg-provider-heading",
children: [u("h3", { children: [
group.providerId === "southplus" ? localize("southplus_resources") : group.displayName,
" ",
group.results.length > 0 && `(${group.results.length})`
] }), allDone && (group.state.status === "error" || group.state.status === "empty") && u(ProviderRetryButton, {
providerName: group.displayName,
onClick: () => retry(group.providerId)
})]
}),
group.state.status === "loading" && u("div", {
class: "rwg-loading",
children: [u("span", { class: "rwg-skeleton" }), u("span", { class: "rwg-skeleton" })]
}),
group.state.status === "error" && u("div", {
class: "rwg-status",
children: [u("strong", { children: localize("search_failed") }), u("small", { children: group.state.error.kind === "rate-limited" ? localize("search_queue_timeout") : group.state.error.message })]
}),
group.state.status === "empty" && u("div", {
class: "rwg-status",
children: localize("no_resources")
}),
group.state.status === "success" && u("ul", {
class: "rwg-results",
children: group.results.map((result) => u("li", { children: u("a", {
href: result.url,
target: "_blank",
rel: "noreferrer",
children: [
u("strong", { children: result.title }),
u("small", { children: [result.author, normalizeDateOnly(result.date)].filter(Boolean).join(" ") }),
u(ExternalLinkIcon, {})
]
}) }, `${result.providerId}:${result.id}`))
})
] }, group.providerId)),
hasDiagnosticError && diagnostics.hasEntries() && u("button", {
class: "rwg-retry",
type: "button",
onClick: () => {
copyDiagnostics();
},
children: diagnosticsCopied ? localize("diagnostics_copied") : localize("copy_diagnostics")
})
]
})
})] });
}
function ProviderRetryButton({ providerName, onClick }) {
const label = `${providerName} · ${localize("click_to_retry")}`;
return u("button", {
class: "rwg-provider-control rwg-provider-retry",
type: "button",
onClick,
"aria-label": label,
title: label,
children: u("svg", {
viewBox: "0 0 24 24",
"aria-hidden": "true",
children: u("path", { d: "M19 8a7 7 0 1 0 1 6M19 4v4h-4" })
})
});
}
function initDlsite() {
const code = parseRjCode(location.href.match(/(?:R[JE]|[VB]J)\d{6}(?:\d{2})?/i)?.[0] ?? "");
if (!code) return null;
if (document.getElementById("rwg-dlsite-root")) return null;
const mount = document.createElement("div");
mount.id = "rwg-dlsite-root";
mount.className = "rwg-root";
document.body.appendChild(mount);
const controller = new ResourceController(providerRegistry);
R(u(DlsiteApp, {
code,
controller,
storage,
diagnostics,
clipboard
}), mount);
return () => {
controller.dispose();
R(null, mount);
mount.remove();
};
}
var PopupController = class {
snapshot = {
display: false,
pinned: false,
code: null,
x: 0,
y: 0
};
listeners = new Set();
getSnapshot = () => this.snapshot;
subscribe = (listener) => {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
};
open(code, rect, pinned) {
if (this.snapshot.pinned && !pinned) return;
this.set({
display: true,
pinned,
code,
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
});
}
unpinAndClose() {
this.set({
...this.snapshot,
display: false,
pinned: false
});
}
hideHover() {
if (!this.snapshot.pinned) this.set({
...this.snapshot,
display: false
});
}
set(snapshot) {
this.snapshot = snapshot;
for (const listener of this.listeners) listener();
}
};
function selectPrefetchCandidates(candidates, limit = 2) {
const byCode = new Map();
for (const candidate of candidates) {
const existing = byCode.get(candidate.code);
if (!existing || candidate.visible && !existing.visible || candidate.top < existing.top) byCode.set(candidate.code, candidate);
}
return [...byCode.values()].sort((a, b) => Number(b.visible) - Number(a.visible) || a.top - b.top || a.order - b.order).slice(0, Math.max(0, limit)).map(({ code }) => code);
}
function browserPrefetchNetworkState() {
return {
online: navigator.onLine !== false,
visible: document.visibilityState === "visible",
saveData: navigator.connection?.saveData === true,
effectiveType: navigator.connection?.effectiveType
};
}
function canPrefetch(state) {
return state.online && state.visible && !state.saveData && state.effectiveType !== "slow-2g" && state.effectiveType !== "2g";
}
var PrefetchController = class {
provider;
networkState;
interactionBusy;
budget;
consumed = 0;
cancelled = false;
constructor(provider, networkState = browserPrefetchNetworkState, interactionBusy = () => false, budget = 2) {
this.provider = provider;
this.networkState = networkState;
this.interactionBusy = interactionBusy;
this.budget = budget;
}
async run(codes) {
for (const code of codes) {
if (this.cancelled || this.consumed >= this.budget) return;
if (!canPrefetch(this.networkState()) || this.interactionBusy()) return;
if (!this.provider.supports(code) || !this.provider.prefetchWork) continue;
try {
if (await this.provider.isWorkCached?.(code)) continue;
} catch {}
if (this.cancelled || !canPrefetch(this.networkState()) || this.interactionBusy()) return;
this.consumed += 1;
try {
await this.provider.prefetchWork(code, { priority: "prefetch" });
} catch {}
}
}
cancel() {
this.cancelled = true;
}
get consumedBudget() {
return this.consumed;
}
};
function schedulePrefetch(task) {
if (typeof requestIdleCallback === "function") {
const id = requestIdleCallback(task, { timeout: 2e3 });
return () => cancelIdleCallback(id);
}
const id = window.setTimeout(task, 800 + Math.random() * 400);
return () => window.clearTimeout(id);
}
function Badge({ text, theme = "default" }) {
return u("span", {
class: `rwg-badge rwg-badge--${theme}`,
children: text
});
}
function Cover({ src, alt }) {
const [loaded, setLoaded] = d(false);
h(() => setLoaded(false), [src]);
return u("div", {
class: "rwg-cover",
children: [src && u("img", {
src,
alt,
loading: "lazy",
decoding: "async",
onLoad: () => setLoaded(true)
}), !loaded && u("span", { class: "rwg-skeleton rwg-cover__placeholder" })]
});
}
function SouthPlusApp({ popup, resources, clipboard, cancelHide, startHide }) {
const popupState = useExternalStore(popup);
const state = useExternalStore(resources);
const position = usePopupPosition(popupState.x, popupState.y, 580, 520);
const [titleCopied, setTitleCopied] = d(false);
const copyFeedbackTimer = A(null);
h(() => {
if (popupState.display && popupState.code) resources.load(popupState.code, { excludeResourceProviderIds: ["southplus"] });
}, [
popupState.code,
popupState.display,
resources
]);
h(() => () => {
if (copyFeedbackTimer.current !== null) window.clearTimeout(copyFeedbackTimer.current);
}, []);
const asmrState = state.resources["asmr-one"];
const asmrUrl = asmrState?.status === "success" ? asmrState.data[0]?.url : null;
const work = state.work.status === "success" ? state.work.data : null;
const releaseDate = normalizeDateOnly(work?.releaseDate);
const loading = state.work.status === "loading";
const error = state.work.status === "error" ? state.work.error : null;
const copyTitle = async () => {
if (!work?.title) return;
try {
await clipboard.writeText(work.title);
setTitleCopied(true);
if (copyFeedbackTimer.current !== null) window.clearTimeout(copyFeedbackTimer.current);
copyFeedbackTimer.current = window.setTimeout(() => setTitleCopied(false), 600);
} catch {}
};
return u(PopupPanel, {
display: popupState.display,
title: popupState.code ?? "RJ Warp Gate",
theme: work?.isGirls ? "girls" : "maniax",
className: "rwg-popup--work",
position,
onClose: () => popup.unpinAndClose(),
dismissOnViewportScroll: true,
onMouseEnter: cancelHide,
onMouseLeave: startHide,
children: [
loading && u(LoadingCard, {}),
error && u(StatusMessage, {
title: localize("search_failed"),
message: error.message
}),
state.work.status === "empty" && u(StatusMessage, { title: localize("no_resources") }),
work && u("div", {
class: "rwg-work",
children: u("div", {
class: "rwg-work__hero",
children: [u("div", {
class: "rwg-work__cover-column",
children: [u("div", {
class: "rwg-work__cover",
children: u(Cover, {
src: work.imageUrl,
alt: work.title
})
}), u("div", {
class: "rwg-actions rwg-actions--cover",
children: [u(ActionButton, {
theme: "dlsite",
href: `https://www.dlsite.com/maniax/work/=/product_id/${work.rjCode}.html`
}), u(ActionButton, {
theme: "asmrone",
href: asmrUrl
})]
})]
}), u("div", {
class: "rwg-work__main",
children: [
u("button", {
class: `rwg-work__title${titleCopied ? " is-copied" : ""}`,
type: "button",
title: localize("click_to_copy_title"),
onClick: () => {
copyTitle();
},
children: work.title
}),
work.circle && u("p", {
class: "rwg-work__circle",
children: work.circle
}),
u("div", {
class: "rwg-badges",
children: [
work.sales !== null && u(Badge, {
theme: "sales",
text: `${localize("dl_count")} ${work.sales}`
}),
work.ratingAverage !== null && u(Badge, {
theme: "rating",
text: `★ ${work.ratingAverage.toFixed(2)}${work.ratingCount ? ` (${work.ratingCount})` : ""}`
}),
releaseDate && u(Badge, { text: releaseDate }),
work.ageRating && u(Badge, {
theme: work.ageRating === "R18" ? "danger" : "default",
text: work.ageRating
}),
work.workType && u(Badge, {
theme: "type",
text: localizeWorkType(work.workTypeId, work.workType)
}),
work.fileSize !== null && u(Badge, { text: formatFileSize(work.fileSize) ?? "" })
]
}),
work.voiceActors.length > 0 && u(InfoGroup, {
title: localize("voice_actor"),
items: work.voiceActors,
theme: "voice"
}),
work.genres.length > 0 && u(InfoGroup, {
title: localize("genre"),
items: work.genres,
theme: "genre"
})
]
})]
})
})
]
});
}
function InfoGroup({ title, items, theme }) {
return u("div", {
class: "rwg-info",
children: [u("strong", { children: title }), u("div", {
class: "rwg-badges",
children: items.map((item) => u(Badge, {
text: item,
theme
}, item))
})]
});
}
function LoadingCard() {
return u("div", {
class: "rwg-work rwg-work--loading",
role: "status",
"aria-busy": "true",
"aria-label": localize("searching"),
children: u("div", {
class: "rwg-work__hero",
"aria-hidden": "true",
children: [u("div", {
class: "rwg-work__cover-column",
children: [u("span", { class: "rwg-skeleton rwg-skeleton--cover" }), u("div", {
class: "rwg-actions rwg-actions--cover",
children: [u("span", { class: "rwg-skeleton rwg-skeleton--action" }), u("span", { class: "rwg-skeleton rwg-skeleton--action" })]
})]
}), u("div", {
class: "rwg-work__main",
children: [
u("div", {
class: "rwg-skeleton-title",
children: [u("span", { class: "rwg-skeleton rwg-skeleton--title" }), u("span", { class: "rwg-skeleton rwg-skeleton--title rwg-skeleton--title-short" })]
}),
u("span", { class: "rwg-skeleton rwg-skeleton--circle" }),
u("div", {
class: "rwg-skeleton-badges",
children: [
u("span", { class: "rwg-skeleton rwg-skeleton--badge" }),
u("span", { class: "rwg-skeleton rwg-skeleton--badge rwg-skeleton--badge-wide" }),
u("span", { class: "rwg-skeleton rwg-skeleton--badge" }),
u("span", { class: "rwg-skeleton rwg-skeleton--badge-narrow" }),
u("span", { class: "rwg-skeleton rwg-skeleton--badge-wide" })
]
}),
u(SkeletonInfo, { chips: 2 }),
u(SkeletonInfo, { chips: 4 })
]
})]
})
});
}
function SkeletonInfo({ chips }) {
return u("div", {
class: "rwg-skeleton-info",
children: [u("span", { class: "rwg-skeleton rwg-skeleton--label" }), u("div", {
class: "rwg-skeleton-badges",
children: Array.from({ length: chips }, (_, index) => u("span", { class: `rwg-skeleton rwg-skeleton--chip${index % 3 === 0 ? " rwg-skeleton--chip-wide" : ""}` }, index))
})]
});
}
function StatusMessage({ title, message }) {
return u("div", {
class: "rwg-status",
children: [u("strong", { children: title }), message && u("small", { children: message })]
});
}
function localizeWorkType(id, fallback) {
const keys = [
"work_type_voice",
"work_type_game",
"work_type_comic",
"work_type_illustration",
"work_type_novel",
"work_type_video",
"work_type_music",
"work_type_tool",
"work_type_voice_comic",
"work_type_other"
];
return keys[id] ? localize(keys[id]) : fallback;
}
var LINK_CLASS = "rwg-rj-link";
var CODE_ATTR = "data-rwg-code";
var DL_SITE_URL = /dlsite\.com\/.*\/product_id\/(?:R[JE]|[VB]J)\d{6}(?:\d{2})?/i;
var PREFETCH_CACHE_PROBE_LIMIT = 6;
var MOBILE_LAYOUT_CLASS = "rwg-southplus-mobile-layout";
function setSouthPlusMobileLayoutClass(mobile) {
document.documentElement.classList.toggle(MOBILE_LAYOUT_CLASS, mobile);
}
function initSouthPlus() {
storage.set("last_forum_domain", location.hostname);
const mount = document.createElement("div");
mount.id = "rwg-southplus-root";
mount.className = "rwg-root";
document.body.appendChild(mount);
const updateMobileLayoutClass = () => {
setSouthPlusMobileLayoutClass(readPopupViewportState().mobile);
};
updateMobileLayoutClass();
window.addEventListener("resize", updateMobileLayoutClass, { passive: true });
window.addEventListener("orientationchange", updateMobileLayoutClass, { passive: true });
window.visualViewport?.addEventListener("resize", updateMobileLayoutClass, { passive: true });
const popup = new PopupController();
const resources = new ResourceController(providerRegistry);
let hideTimer = null;
const cancelHide = () => {
if (hideTimer !== null) window.clearTimeout(hideTimer);
hideTimer = null;
};
const startHide = () => {
if (window.innerWidth <= 768 || popup.getSnapshot().pinned) return;
cancelHide();
hideTimer = window.setTimeout(() => popup.hideHover(), 250);
};
R(u(SouthPlusApp, {
popup,
resources,
clipboard,
cancelHide,
startHide
}), mount);
enhanceSouthPlusNode(document.body);
const selected = selectPrefetchCandidates(collectCandidates(), PREFETCH_CACHE_PROBE_LIMIT);
const prefetch = new PrefetchController(dlsiteProvider, void 0, () => resources.isBusy());
const cancelScheduledPrefetch = schedulePrefetch(() => {
prefetch.run(selected);
});
const cancelPrefetchWhenHidden = () => {
if (document.visibilityState === "hidden") prefetch.cancel();
};
document.addEventListener("visibilitychange", cancelPrefetchWhenHidden);
const queued = new Set();
let scanScheduled = false;
const observer = new MutationObserver((records) => {
for (const record of records) for (const node of record.addedNodes) queued.add(node);
if (scanScheduled) return;
scanScheduled = true;
queueMicrotask(() => {
scanScheduled = false;
const nodes = [...queued];
queued.clear();
for (const node of nodes) enhanceSouthPlusNode(node);
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
const getLink = (target) => target instanceof Element ? target.closest(`.${LINK_CLASS}`) : null;
const onClick = (event) => {
const link = getLink(event.target);
if (!link) {
if (popup.getSnapshot().display && !mount.contains(event.target)) popup.unpinAndClose();
return;
}
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
const code = parseRjCode(link.getAttribute(CODE_ATTR) ?? "");
if (!code) return;
event.preventDefault();
event.stopPropagation();
cancelHide();
popup.open(code, link.getBoundingClientRect(), true);
};
const onMouseOver = (event) => {
if (window.innerWidth <= 768 || popup.getSnapshot().pinned) return;
const link = getLink(event.target);
if (!link || link.contains(event.relatedTarget)) return;
const code = parseRjCode(link.getAttribute(CODE_ATTR) ?? "");
if (!code) return;
cancelHide();
popup.open(code, link.getBoundingClientRect(), false);
};
const onMouseOut = (event) => {
const link = getLink(event.target);
if (!link || link.contains(event.relatedTarget)) return;
startHide();
};
const onKeyDown = (event) => {
if (event.key !== "Enter" && event.key !== " ") return;
const link = getLink(event.target);
if (!link || link instanceof HTMLAnchorElement) return;
const code = parseRjCode(link.getAttribute(CODE_ATTR) ?? "");
if (!code) return;
event.preventDefault();
cancelHide();
popup.open(code, link.getBoundingClientRect(), true);
};
document.addEventListener("click", onClick);
document.addEventListener("mouseover", onMouseOver);
document.addEventListener("mouseout", onMouseOut);
document.addEventListener("keydown", onKeyDown);
return () => {
cancelScheduledPrefetch();
prefetch.cancel();
observer.disconnect();
cancelHide();
document.removeEventListener("click", onClick);
document.removeEventListener("mouseover", onMouseOver);
document.removeEventListener("mouseout", onMouseOut);
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("visibilitychange", cancelPrefetchWhenHidden);
window.removeEventListener("resize", updateMobileLayoutClass);
window.removeEventListener("orientationchange", updateMobileLayoutClass);
window.visualViewport?.removeEventListener("resize", updateMobileLayoutClass);
setSouthPlusMobileLayoutClass(false);
resources.dispose();
R(null, mount);
mount.remove();
};
}
function enhanceSouthPlusNode(root) {
if (root instanceof HTMLElement && (root.closest(".rwg-root") || root.closest(`.${LINK_CLASS}`))) return;
if (root.nodeType === Node.TEXT_NODE) {
linkifyText(root);
return;
}
if (!(root instanceof Element || root instanceof DocumentFragment || root instanceof Document)) return;
if (root instanceof HTMLAnchorElement) enhanceAnchor(root);
root.querySelectorAll?.("a[href]").forEach((anchor) => enhanceAnchor(anchor));
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) {
const parent = node.parentElement;
if (!parent || !node.nodeValue || !RJ_CODE_SEARCH_PATTERN.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
RJ_CODE_SEARCH_PATTERN.lastIndex = 0;
if (parent.closest(".rwg-root, .rwg-rj-link, script, style, textarea, input, [contenteditable=\"true\"]")) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
} });
const texts = [];
while (walker.nextNode()) texts.push(walker.currentNode);
for (const text of texts) linkifyText(text);
}
function enhanceAnchor(anchor) {
if (!DL_SITE_URL.test(anchor.href)) return;
const code = findRjCodes(anchor.href).at(-1);
if (!code) return;
anchor.classList.add(LINK_CLASS);
anchor.setAttribute(CODE_ATTR, code);
}
function linkifyText(textNode) {
const value = textNode.nodeValue ?? "";
const matches = [...value.matchAll(new RegExp(RJ_CODE_SEARCH_PATTERN.source, RJ_CODE_SEARCH_PATTERN.flags))];
if (!matches.length || !textNode.parentNode) return;
const fragment = document.createDocumentFragment();
let cursor = 0;
for (const match of matches) {
const index = match.index ?? 0;
if (index > cursor) fragment.append(value.slice(cursor, index));
const code = parseRjCode(match[0]);
if (code) fragment.append(createCodeElement(code));
else fragment.append(match[0]);
cursor = index + match[0].length;
}
if (cursor < value.length) fragment.append(value.slice(cursor));
textNode.replaceWith(fragment);
}
function createCodeElement(code) {
const element = document.createElement("span");
element.className = LINK_CLASS;
element.setAttribute(CODE_ATTR, code);
element.textContent = code;
element.tabIndex = 0;
element.setAttribute("role", "button");
return element;
}
function collectCandidates() {
return [...document.querySelectorAll(`.${LINK_CLASS}`)].map((element, order) => {
const code = parseRjCode(element.getAttribute(CODE_ATTR) ?? "");
const rect = element.getBoundingClientRect();
return {
code,
top: Math.abs(rect.top),
visible: rect.bottom >= 0 && rect.top <= window.innerHeight,
order
};
});
}
function isDlsiteHost(hostname) {
return hostname === "dlsite.com" || hostname.endsWith(".dlsite.com") || hostname === "dlsite.com.tw" || hostname.endsWith(".dlsite.com.tw");
}
function init() {
if (!document.body) return;
if (isDlsiteHost(location.hostname)) {
initDlsite();
return;
}
initThemeManager();
initSouthPlus();
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init, { once: true });
else init();
})();