RJ Warp Gate

Injects bidirectional cross-site resource search into South Plus and DLsite, featuring detailed metadata preview cards and one-click redirection to ASMR ONE.

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

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

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

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

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

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

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

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

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

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

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

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

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

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

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